1. 程式人生 > >精緻自繪漸變進度條-Path/Paint.setXfermode

精緻自繪漸變進度條-Path/Paint.setXfermode

日常產品需求開發中進度條自繪是經常需要的,如下:

image

思路

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

image

Paint setXfermode

image

關於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);
    }

}