精致自绘渐变进度条-Path/Paint.setXfermode
程序员文章站
2022-05-28 20:14:09
...
日常产品需求开发中进度条自绘是经常需要的,如下:
思路
1,如图1,绘制左端半圆部分
通过canvas.drawArc即可
2,如图2,绘制左半圆和中间矩形部分
通过canvas.drawArc和canvas.drawRect即可
3,如图3和图4,绘制右半圆部分
绘制左半圆->绘制矩形->绘制右半圆部分(通过Path取得BEFC封闭区域部分如图4,或者通过paint的setXfermode取SRC_OUT取得该部分)
Path
- 1,基本用法
方法 | 功能 |
---|---|
addArc(RectF oval, float startAngle, float sweepAngle) | 绘制弧线,配合Paint的Style可以实现不同的填充效果 |
addCircle(float x, float y, float radius, Path.Direction dir) | 绘制圆形,其中第dir参数用来指定绘制时是顺时针还是逆时针 |
addOval(RectF oval, Path.Direction dir) | 绘制椭圆形,其中 oval作为椭圆的外切矩形区域 |
addRect(RectF rect, Path.Direction dir) | 绘制矩形 |
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir) | 绘制圆角矩形 |
lineTo(float x, float y) | 绘制直线 |
addPath(Path src) | 添加一个新的Path到当前Path |
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) | 与addArc方法相似,但也有区别,下文细述。 |
quadTo(float x1, float y1, float x2, float y2) | 绘制二次贝塞尔曲线,其中 (x1,y1)为控制点,(x2,y2)为终点 |
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) | 绘制三次贝塞尔曲线,其中(x1,y1),(x2,y2)为控制点,(x3,y3)为终点 |
- 2.rXXX方法
上面的lineTo,MoveTo,QuadTo,CubicTo方法都有与之对应的rXXX方法:
- rLineTo(float dx, float dy)
- rMoveTo(float dx, float dy)
- rQuadTo(float dx1, float dy1, float dx2, float dy2)
- rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
这些方法与之对应的原方法相比,惟一的区别在于:r方法是基于当前绘制开始点的offest,比如当前paint位于 (100,100)处,则使用rLineTo(100,100)方法绘制出来的直线是从(100,100)到(200,200)的一条直接,由此可见rXXX方法方便用来基于之前的绘制作连续绘制。
- 3.Path.op方法
//原型
op(Path path, Path.Op op)
//eg
path1.op(path2,Path.Op.DIFFERENCE);
此方法用于对两个Path对象做相应的运算组合(combine),具体的说是根据不同的op参数及path2参数来影响path1对象,有点类似于数学上的集合运算
Path path1 = new Path();
path1.addCircle(150, 150, 100, Path.Direction.CW);
Path path2 = new Path();
path2.addCircle(200, 200, 100, Path.Direction.CW);
path1.op(path2, Path.Op.DIFFERENCE);
canvas.drawPath(path1, paint1);
Paint setXfermode
关于XFermode更强大的使用,点击参见链接
渐变
LinearGradient是水平渐变渲染器,LinearGradient.TileMode.CLAMP,这种模式表示重复最后一种颜色直到该View结束的地方
详细代码如下
package com.example.administrator.myapplication;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
public class DrawCornerLine extends View {
private double mProgress;
private Paint mPaint;
private int mStartColor = Color.parseColor("#8000ff00");
private int mEndColor = Color.YELLOW;
private int mBgColor = Color.GRAY;
public DrawCornerLine(Context context) {
super(context);
init(context);
}
public DrawCornerLine(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DrawCornerLine(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
}
public void update(double progress){
mProgress = progress;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
triPathDraw(canvas);
}
private void triPathDraw(Canvas canvas) {
mPaint.setColor(mBgColor);
int height = getHeight();
int width = getWidth();
RectF rectF = new RectF(0, 0, width, height);
mPaint.setShader(null);
canvas.drawRoundRect(rectF, height / 2, height / 2, mPaint);//先绘制背景
float radius = height / 2;//左右半圆的半径
double progressW = mProgress * width;//当前进度对应的长度
RectF rectLeft = new RectF(0, 0, height, height);
//增加渐变
LinearGradient linearGradient = new LinearGradient(0, 0, (int)progressW, height, mStartColor,mEndColor, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);//设置shader
if(progressW < radius){//当进度处于图1的时候
double disW = radius - progressW;
float angle = (float) Math.toDegrees(Math.acos(disW / radius));
//绘制图1 弧AD对应的棕色区域,注意第三个参数设置为false
// 表示绘制不经过圆心(即图一效果)的月牙部分,设置为true则绘制的是扇形,angle是绘制角度
canvas.drawArc(rectLeft, 180 - angle, angle * 2, false, mPaint);
}else if(progressW <= width - radius){//当进度处于图2的时候
canvas.drawArc(rectLeft, 90, 180, true, mPaint);//绘制弧AD半圆
RectF rectMid = new RectF(radius + 1, 0, (float) progressW, height);
canvas.drawRect(rectMid, mPaint);//绘制ABCD矩形进度
}else{//图4对应部分
canvas.drawArc(rectLeft, 90, 180, true, mPaint);//绘制左端半圆
RectF rectMid = new RectF(radius + 1, 0, width - radius, height);
canvas.drawRect(rectMid, mPaint);//绘制中间的矩形部分
//得到图四中F->C->B->E->F的闭合区域,注意此处是从头F为起点,减少坐标计算
double disW = progressW - (width - radius);
float angle = (float) Math.toDegrees(Math.acos(disW / radius));
RectF rectRight = new RectF(width - height, 0, width, height);
Path path = new Path();
path.arcTo(rectRight, angle, 90 - angle);
path.lineTo(width - radius, 0);
path.arcTo(rectRight, 270, 90 - angle);
path.close();
canvas.drawPath(path, mPaint);//绘制path
// //如图三:根据半圆BC和矩形GHIJ,根据paint.setXfermode取SRC_OUT,就能得到封闭区域BEFC的部分
// int sc = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);//相当于新建一个图层
// RectF rectDst = new RectF((float) progressW, 0, width, height);//图3中的
// mPaint.setColor(Color.GRAY);//此处可以用任意颜色,但该颜色不能包含透明度
// canvas.drawRect(rectDst, mPaint);//绘制Dst
// mPaint.setColor(mStartColor);
// mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
// RectF rectSrc = new RectF(width - height, 0, width, height);
// canvas.drawArc(rectSrc, -90, 180, true, mPaint);//绘制Src
// mPaint.setXfermode(null);
// canvas.restoreToCount(sc);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int defaultWidth = (int) (ScreenUtils.getScreenWithDisplay(getContext()).mWidth * 0.9);
int defaultHeight = (int) ScreenUtils.dpToPx(50);
int width = getMeasureSize(defaultWidth, widthMeasureSpec);
int height = getMeasureSize(defaultHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int getMeasureSize(int defaultSize, int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if(mode == MeasureSpec.UNSPECIFIED){
return defaultSize;
}else if(mode == MeasureSpec.AT_MOST){
return Math.min(defaultSize, size);
}
return size;
}
private int calGradientColor(float progress, int colorLeft, int colorRight) {
int redS = Color.red(colorLeft);
int greenS = Color.red(colorLeft);
int blueS = Color.blue(colorLeft);
int redE = Color.red(colorRight);
int greenE = Color.green(colorRight);
int blueE = Color.blue(colorRight);
int dstRed = (int) (redS * (1 - progress) + progress * redE);
int dstGreen = (int) (greenS * (1 - progress) + progress * greenE);
int dstBlue = (int) (blueS * (1 - progress) + progress * blueE);
return Color.argb(255, dstRed, dstGreen, dstBlue);
}
}