前言
這兩天做的頁面中有一個比較有意思的積分環(huán)秽浇,采用的是漸變形式浮庐,具體如下圖所示。
真實效果圖如下所示柬焕,
下面就讓我們來一步步實現(xiàn)它审残。
對自定義View還不了解的可以參考我之前寫的三篇文章:
實現(xiàn)
首先梭域,我們要明確我們要控制這個自定義View的哪些屬性,可以分析出搅轿,我們需要控制環(huán)的粗細病涨,環(huán)的進度,進度環(huán)的起始和結束色璧坟,背景環(huán)的起始和結束色既穆,環(huán)的起始角度,環(huán)掃過的角度雀鹃,有了這些參數幻工,那么也就確定了其樣式,順便黎茎,我們再加一個是否顯示動畫的參數会钝,具體attrs.xml
文件內容如下所示。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressRing">
<!--進度起始色-->
<attr name="pr_progress_start_color" format="color" />
<!--進度結束色-->
<attr name="pr_progress_end_color" format="color" />
<!--背景起始色-->
<attr name="pr_bg_start_color" format="color" />
<!--背景中間色-->
<attr name="pr_bg_mid_color" format="color" />
<!--背景結束色-->
<attr name="pr_bg_end_color" format="color" />
<!--進度值 介于0-100-->
<attr name="pr_progress" format="integer" />
<!--進度寬度-->
<attr name="pr_progress_width" format="dimension" />
<!--起始角度-->
<attr name="pr_start_angle" format="integer" />
<!--掃過的角度-->
<attr name="pr_sweep_angle" format="integer" />
<!--是否顯示動畫-->
<attr name="pr_show_anim" format="boolean" />
</declare-styleable>
</resources>
下一步新建一個類ProgressRing
工三,繼承自View
,我們實現(xiàn)其第二個構造函數
public ProgressRing(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
既然我們定義了那么多屬性先鱼,那么在這個構造函數中我們就需要用到那些屬性了俭正,具體操作如下所示。
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressRing);
progressStartColor = ta.getColor(R.styleable.ProgressRing_pr_progress_start_color, Color.YELLOW);
progressEndColor = ta.getColor(R.styleable.ProgressRing_pr_progress_end_color, progressStartColor);
bgStartColor = ta.getColor(R.styleable.ProgressRing_pr_bg_start_color, Color.LTGRAY);
bgMidColor = ta.getColor(R.styleable.ProgressRing_pr_bg_mid_color, bgStartColor);
bgEndColor = ta.getColor(R.styleable.ProgressRing_pr_bg_end_color, bgStartColor);
progress = ta.getInt(R.styleable.ProgressRing_pr_progress, 0);
progressWidth = ta.getDimension(R.styleable.ProgressRing_pr_progress_width, 8f);
startAngle = ta.getInt(R.styleable.ProgressRing_pr_start_angle, 150);
sweepAngle = ta.getInt(R.styleable.ProgressRing_pr_sweep_angle, 240);
showAnim = ta.getBoolean(R.styleable.ProgressRing_pr_show_anim, true);
ta.recycle();
這里startAngle
為何為150呢焙畔,也就是起始角度為150度掸读,這150代表什么呢?因為我之后畫圓環(huán)調用的是drawArc
函數宏多,我們跳轉進去查看一下便可發(fā)現(xiàn)如下說明The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0 degrees (3 o'clock on a watch.)
儿惫,也就是說,零度角是在時鐘三點鐘方向伸但,沿著順時針方向角度依次增大肾请,如果還不懂的話,那就看我下面這張圖的分析更胖。
所以sweepAngle
為240大家也都知道了吧铛铁。
當然這都是默認值,具體數值你們可以自己定義却妨,從而實現(xiàn)不同的圓弧饵逐。
我們進度最大為100,我們可以得到單位角度unitAngle = (float) (sweepAngle / 100.0);
我們分別設置兩支畫筆彪标,bgPaint
和progressPaint
倍权,一支畫背景,一支畫進度捞烟,并對他們設置樣式薄声,具體如下所示当船。
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private Paint progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeCap(Paint.Cap.ROUND);
bgPaint.setStrokeWidth(progressWidth);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
progressPaint.setStrokeWidth(progressWidth);
現(xiàn)在,我們最初的準備工作都做完了奸柬,下面就是來確定畫的大小和畫的東西了生年,畫的位置,我們需要在onMeasure
中確定畫的大小廓奕,代碼比較簡單抱婉,如下所示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMeasureWidth = getMeasuredWidth();
mMeasureHeight = getMeasuredHeight();
if (pRectF == null) {
float halfProgressWidth = progressWidth / 2;
pRectF = new RectF(halfProgressWidth + getPaddingLeft(),
halfProgressWidth + getPaddingTop(),
mMeasureWidth - halfProgressWidth - getPaddingRight(),
mMeasureHeight - halfProgressWidth - getPaddingBottom());
}
}
接下來就是最重要的怎么畫了桌粉,當然是在我們的onDraw
函數中了蒸绩。我們先畫不支持動畫顯示的,從簡單的開始铃肯,我們先畫進度部分患亿,由于涉及到漸變,我們需要在起始色和結束色中間獲取過渡色押逼,我們調用如下函數即可步藕。
public int getGradient(float fraction, int startColor, int endColor) {
if (fraction > 1) fraction = 1;
int alphaStart = Color.alpha(startColor);
int redStart = Color.red(startColor);
int blueStart = Color.blue(startColor);
int greenStart = Color.green(startColor);
int alphaEnd = Color.alpha(endColor);
int redEnd = Color.red(endColor);
int blueEnd = Color.blue(endColor);
int greenEnd = Color.green(endColor);
int alphaDifference = alphaEnd - alphaStart;
int redDifference = redEnd - redStart;
int blueDifference = blueEnd - blueStart;
int greenDifference = greenEnd - greenStart;
int alphaCurrent = (int) (alphaStart + fraction * alphaDifference);
int redCurrent = (int) (redStart + fraction * redDifference);
int blueCurrent = (int) (blueStart + fraction * blueDifference);
int greenCurrent = (int) (greenStart + fraction * greenDifference);
return Color.argb(alphaCurrent, redCurrent, greenCurrent, blueCurrent);
}
這里可能有小伙伴要說安卓API中就有漸變,比如LinearGradient
挑格、SweepGradient
咙冗、RadialGrdient
,不過其局限性比較大漂彤,所以我還是選擇如上的方式獲取漸變色雾消。
我們從起始角startAngle
開始畫,畫到startAngle + progress * unitAngle
即可挫望,每次增加1弧度即可立润,相關代碼如下所示。
for (int i = 0, end = (int) (progress * unitAngle); i <= end; i++) {
progressPaint.setColor(getGradient(i / (float) end, progressStartColor, progressEndColor));
canvas.drawArc(pRectF,
startAngle + i,
1,
false,
progressPaint);
}
下圖顯示了進度為70的進度樣子媳板。
畫完了進度后桑腮,我們把進度相關代碼注釋掉,現(xiàn)在我們只關心畫背景色蛉幸,背景色我們只需要畫能看到部分即可到旦,不需要畫進度后面的背景色,這樣還提升了繪制效率巨缘。由于我們背景色還包含一個中間色添忘,所以還需要區(qū)分下左面和右面。相關代碼如下所示若锁。
float halfSweep = sweepAngle / 2;
for (int i = sweepAngle, st = (int) (progress * unitAngle); i > st; --i) {
if (i - halfSweep > 0) {
bgPaint.setColor(getGradient((i - halfSweep) / halfSweep, bgMidColor, bgEndColor));
} else {
bgPaint.setColor(getGradient((halfSweep - i) / halfSweep, bgMidColor, bgStartColor));
}
canvas.drawArc(pRectF,
startAngle + i,
1,
false,
bgPaint);
}
下圖顯示了進度為30的背景色樣子搁骑。
好了,我們把進度相關代碼取消注釋,再運行一遍便可得到最終效果仲器。
下面我們來為其加上動畫煤率,所謂動畫,就是讓其從進度為0開始畫乏冀,一點點畫到progress即可蝶糯,我們設置一個變量curProgress
來表示當前進度,當curProgress < progress
時辆沦,curProgress
自增昼捍,再調用postInvalidate()
即可,具體完整代碼如下所示肢扯。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2017/07/10
* desc :
* </pre>
*/
public class ProgressRing extends View {
private int progressStartColor;
private int progressEndColor;
private int bgStartColor;
private int bgMidColor;
private int bgEndColor;
private int progress;
private float progressWidth;
private int startAngle;
private int sweepAngle;
private boolean showAnim;
private int mMeasureHeight;
private int mMeasureWidth;
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private Paint progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private RectF pRectF;
private float unitAngle;
private int curProgress = 0;
public ProgressRing(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressRing);
progressStartColor = ta.getColor(R.styleable.ProgressRing_pr_progress_start_color, Color.YELLOW);
progressEndColor = ta.getColor(R.styleable.ProgressRing_pr_progress_end_color, progressStartColor);
bgStartColor = ta.getColor(R.styleable.ProgressRing_pr_bg_start_color, Color.LTGRAY);
bgMidColor = ta.getColor(R.styleable.ProgressRing_pr_bg_mid_color, bgStartColor);
bgEndColor = ta.getColor(R.styleable.ProgressRing_pr_bg_end_color, bgStartColor);
progress = ta.getInt(R.styleable.ProgressRing_pr_progress, 0);
progressWidth = ta.getDimension(R.styleable.ProgressRing_pr_progress_width, 8f);
startAngle = ta.getInt(R.styleable.ProgressRing_pr_start_angle, 150);
sweepAngle = ta.getInt(R.styleable.ProgressRing_pr_sweep_angle, 240);
showAnim = ta.getBoolean(R.styleable.ProgressRing_pr_show_anim, true);
ta.recycle();
unitAngle = (float) (sweepAngle / 100.0);
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeCap(Paint.Cap.ROUND);
bgPaint.setStrokeWidth(progressWidth);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
progressPaint.setStrokeWidth(progressWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMeasureWidth = getMeasuredWidth();
mMeasureHeight = getMeasuredHeight();
if (pRectF == null) {
float halfProgressWidth = progressWidth / 2;
pRectF = new RectF(halfProgressWidth + getPaddingLeft(),
halfProgressWidth + getPaddingTop(),
mMeasureWidth - halfProgressWidth - getPaddingRight(),
mMeasureHeight - halfProgressWidth - getPaddingBottom());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!showAnim) {
curProgress = progress;
}
drawBg(canvas);
drawProgress(canvas);
if (curProgress < progress) {
curProgress++;
postInvalidate();
}
}
// 只需要畫進度之外的背景即可
private void drawBg(Canvas canvas) {
float halfSweep = sweepAngle / 2;
for (int i = sweepAngle, st = (int) (curProgress * unitAngle); i > st; --i) {
if (i - halfSweep > 0) {
bgPaint.setColor(getGradient((i - halfSweep) / halfSweep, bgMidColor, bgEndColor));
} else {
bgPaint.setColor(getGradient((halfSweep - i) / halfSweep, bgMidColor, bgStartColor));
}
canvas.drawArc(pRectF,
startAngle + i,
1,
false,
bgPaint);
}
}
private void drawProgress(Canvas canvas) {
for (int i = 0, end = (int) (curProgress * unitAngle); i <= end; i++) {
progressPaint.setColor(getGradient(i / (float) end, progressStartColor, progressEndColor));
canvas.drawArc(pRectF,
startAngle + i,
1,
false,
progressPaint);
}
}
public void setProgress(@IntRange(from = 0, to = 100) int progress) {
this.progress = progress;
invalidate();
}
public int getProgress() {
return progress;
}
public int getGradient(float fraction, int startColor, int endColor) {
if (fraction > 1) fraction = 1;
int alphaStart = Color.alpha(startColor);
int redStart = Color.red(startColor);
int blueStart = Color.blue(startColor);
int greenStart = Color.green(startColor);
int alphaEnd = Color.alpha(endColor);
int redEnd = Color.red(endColor);
int blueEnd = Color.blue(endColor);
int greenEnd = Color.green(endColor);
int alphaDifference = alphaEnd - alphaStart;
int redDifference = redEnd - redStart;
int blueDifference = blueEnd - blueStart;
int greenDifference = greenEnd - greenStart;
int alphaCurrent = (int) (alphaStart + fraction * alphaDifference);
int redCurrent = (int) (redStart + fraction * redDifference);
int blueCurrent = (int) (blueStart + fraction * blueDifference);
int greenCurrent = (int) (greenStart + fraction * greenDifference);
return Color.argb(alphaCurrent, redCurrent, greenCurrent, blueCurrent);
}
}
最后我們在布局中如下引用
<com.blankj.progressring.ProgressRing
android:layout_width="320dp"
android:layout_height="320dp"
app:pr_bg_end_color="#00ffffff"
app:pr_bg_mid_color="#33ffffff"
app:pr_bg_start_color="#00ffffff"
app:pr_progress="80"
app:pr_progress_end_color="#D9B262"
app:pr_progress_start_color="#00ffffff"
app:pr_progress_width="8dp" />
最終效果如下所示妒茬。
結語
這次自定義View的實戰(zhàn)講解還是很具體細致的,希望大家能學到些什么蔚晨,比如如何一步步分析問題乍钻,解決問題的。還有就是能把初始化放在onDraw
之外的就都放onDraw
外铭腕,在onDraw
中只做onDraw
相關的银择,還有一些小細節(jié)就自己去發(fā)現(xiàn)吧。代碼已上傳GitHub累舷,喜歡的記得star哦欢摄。