精緻自繪漸變進度條-Path/Paint.setXfermode
阿新 • • 發佈:2019-02-07
日常產品需求開發中進度條自繪是經常需要的,如下:
思路
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);
}
}