欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

精致自绘渐变进度条-Path/Paint.setXfermode

程序员文章站 2022-05-28 20:14:09
...

日常产品需求开发中进度条自绘是经常需要的,如下:

精致自绘渐变进度条-Path/Paint.setXfermode

思路

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);

精致自绘渐变进度条-Path/Paint.setXfermode

Paint setXfermode

精致自绘渐变进度条-Path/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);
    }

}