Android自定義圓形倒計(jì)時(shí)進(jìn)度條
本文實(shí)例為大家分享了Android倒計(jì)時(shí)進(jìn)度條展示的具體代碼,供大家參考,具體內(nèi)容如下
效果預(yù)覽

源代碼傳送門(mén):https://github.com/yanzhenjie/CircleTextProgressbar
實(shí)現(xiàn)與原理
這個(gè)文字圓形的進(jìn)度條我們?cè)诤芏郃PP中看到過(guò),比如APP歡迎頁(yè)倒計(jì)時(shí),下載文件倒計(jì)時(shí)等。
分析下原理,可能有的同學(xué)一看到這個(gè)自定義View就慌了,這個(gè)是不是要繼承View啊,是不是要繪制啊之類(lèi)的,答案是:是的。但是我們也不要擔(dān)心,實(shí)現(xiàn)這個(gè)效果實(shí)在是so easy。下面就跟我一起來(lái)看看核心分析和代碼吧。
原理分析
首先我們觀察上圖,需要幾個(gè)部分組成:
1. 外面逐漸增加/減少的圓形進(jìn)度條。
2. 圓形進(jìn)度條中間的展示文字。
3. 圓形進(jìn)度條外面包裹的圓。
4. 圓形進(jìn)度條中間的填充色。
5. 字體顏色/填充顏色點(diǎn)擊變色:ColorStateList類(lèi)。
我們分析得出需要四個(gè)部分。一看有文字,那么第一個(gè)想到的自然是TextView啦,正好可以少做一個(gè)字體顏色的記錄。中間的填充顏色(原型暫且不考慮)點(diǎn)擊時(shí)變色,需要ColorStateList類(lèi)來(lái)記錄。剩下的進(jìn)度條、輪廓圓和填充圓是需要我們繪制的。
我封裝的CircleTextProgressbar特色
CircleTextProgressbar支持自動(dòng)倒計(jì)時(shí),自動(dòng)減少進(jìn)度,自動(dòng)增加進(jìn)度等。
如果需要自動(dòng)走進(jìn)度的話(huà),設(shè)置完你自定義的屬性后調(diào)用start()方法就可以自動(dòng)倒計(jì)時(shí)了,如果想走完后再走一遍自動(dòng)進(jìn)度調(diào)用一下reStart()就OK了。
如果不想自動(dòng)走進(jìn)度,你可以通過(guò)setProgress()來(lái)像系統(tǒng)的progress一樣修改進(jìn)度值。
// 和系統(tǒng)普通進(jìn)度條一樣,0-100。 progressBar.setProgressType(CircleTextProgressbar.ProgressType.COUNT); // 改變進(jìn)度條。 progressBar.setProgressLineWidth(30);// 進(jìn)度條寬度。 // 設(shè)置倒計(jì)時(shí)時(shí)間毫秒,默認(rèn)3000毫秒。 progressBar.setTimeMillis(3500); // 改變進(jìn)度條顏色。 progressBar.setProgressColor(Color.RED); // 改變外部邊框顏色。 progressBar.setOutLineColor(Color.RED); // 改變圓心顏色。 progressBar.setInCircleColor(Color.RED); // 如果需要自動(dòng)倒計(jì)時(shí),就會(huì)自動(dòng)走進(jìn)度。 progressBar.start(); // 如果想自己設(shè)置進(jìn)度,比如100。 progressBar.setProgress(100);
踩坑的過(guò)程
其實(shí)好久沒(méi)有寫(xiě)過(guò)自定義View了,有些東西還真忘記了,所以寫(xiě)這個(gè)View的時(shí)候又把之前的坑踩了一遍,為了避免其它同學(xué)也被坑,這里把我踩的坑也記錄下。
View繪制區(qū)域
這里我遇到一個(gè)問(wèn)題,因?yàn)槲覀兝^承的TextView文字多了就是長(zhǎng)的,那么繪制出來(lái)的圓長(zhǎng)寬是一樣的,所以在TextView上繪制出來(lái)的圓只能看到一部分或者是橢圓的。所以我們要把View的繪制區(qū)域擴(kuò)大。當(dāng)時(shí)我第一個(gè)想到的是layout()方法,因?yàn)楫?dāng)View的父布局onLayout()的時(shí)候會(huì)調(diào)用View的layout()來(lái)讓子View布局,我重寫(xiě)了layout方法:
@Override
public void layout(int left, int top, int right, int bottom) {
int w = right - left;
int h = bottom - top;
int size = w > h ? w : h;
if (w > h) {
bottom += (size - h);
} else {
right += (size - w);
}
super.layout(left, top, right, bottom);
}
這段代碼的原理就是寬和高,那個(gè)大,就把view擴(kuò)大到這么最大的這個(gè)值。
當(dāng)放了一個(gè)View在Layout時(shí),效果出來(lái)沒(méi)問(wèn)題,但是我放多個(gè)View到LinearLayout中的時(shí)候發(fā)現(xiàn)幾個(gè)View重疊了,哦舍特。我恍然大悟啊,這尼瑪人家Layout已經(jīng)把我繪制區(qū)域的寬高指定了,我強(qiáng)行去占領(lǐng)別的View的了。so,我應(yīng)該重寫(xiě)onMeasure()啊,在測(cè)量寬高的時(shí)候就告訴父Layout我要多大的地盤(pán):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int size = width > height ? width : height;
setMeasuredDimension(size, size);
}
這段代碼的意思更容易理解,就是看super.onMeasure測(cè)量的時(shí)候的寬高哪個(gè)大,就把寬高都設(shè)置成最大的這個(gè)值。告訴父Layout我要多大的地盤(pán),那么等我繪制的時(shí)候我想怎么玩就怎么玩。
繪制View的實(shí)現(xiàn)
好了,來(lái)到了關(guān)鍵的地方,前面的都搞定了就看我們?cè)趺蠢L制我們的幾個(gè)圓圈圈了。畫(huà)圓圈圈就要重寫(xiě)onDraw()方法啦。
首先需要一個(gè)畫(huà)筆:
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);// 抗鋸齒
拿到繪制區(qū)域
我們可以通過(guò)getDrawingRect(Rect)獲取到繪制區(qū)域,通過(guò)繪制區(qū)域計(jì)算出這個(gè)區(qū)域可以繪制圓的半徑。
Rect bounds = new Rect();
@Override
protected void onDraw(Canvas canvas) {
getDrawingRect(bounds);//獲取view的邊界
int size = bounds.height() > bounds.width() ? bounds.width() : bounds.height();
float outerRadius = size / 2; // 計(jì)算出繪制圓的半徑
}
繪制填充圓
那么剛才提到過(guò)點(diǎn)擊的時(shí)候變色,所以我們要用到ColorStateList,這里做一個(gè)初始化,并且支持在xml中定義這個(gè)屬性:
// 默認(rèn)透明填充。
ColorStateList inCircleColors = ColorStateList.valueOf(Color.TRANSPARENT);
private void initialize(Context ctx, AttributeSet attributeSet) {
TypedArray typedArray = ctx.obtainStyledAttributes(attributeSet, R.styleable.Progressbar);
inCircleColors = typedArray.getColorStateList(R.styleable.Progressbar_circle_color);
typedArray.recycle();
}
不明白如何自定View xml屬性的同學(xué)請(qǐng)求自行Google。
根據(jù)點(diǎn)擊、Check、Select狀態(tài)繪制填充圓的顏色,因?yàn)槭翘畛洌赃@里Paint的Style是FILL:
int circleColor = inCircleColors.getColorForState(getDrawableState(), 0); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(circleColor); canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth, mPaint);
圓心是繪制區(qū)域的圓心,半徑是繪制區(qū)域圓的半徑減去外部輪廓圓線(xiàn)的寬度。這樣正好填充圓和外部輪廓圓不重疊。
繪制外部邊框圓
這個(gè)就簡(jiǎn)單了,因?yàn)槭强招牡木€(xiàn),所以Style是STROKE,然后設(shè)置線(xiàn)的寬度,畫(huà)筆的顏色:
mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(outLineWidth); mPaint.setColor(outLineColor); canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth / 2, mPaint);
圓心是繪制區(qū)域的圓心,半徑是繪制區(qū)域圓的半徑減去外部輪廓圓線(xiàn)的寬度的一半,這樣剛好外部輪廓線(xiàn)和內(nèi)部填充圓緊靠著。
繪制TextView的字
為了我們的繪制和TextView自身的繪制不重疊,我們干掉了super.onDraw(canvas);,所以這里我們要把TextView的字也要寫(xiě)上去。
首先拿到TextView的默認(rèn)畫(huà)筆,設(shè)置TextView本身的字體顏色,抗鋸齒,為了美觀我們強(qiáng)行讓文字居中:
//畫(huà)字 Paint paint = getPaint(); paint.setColor(getCurrentTextColor()); paint.setAntiAlias(true); paint.setTextAlign(Paint.Align.CENTER); float textY = bounds.centerY() - (paint.descent() + paint.ascent()) / 2; canvas.drawText(getText().toString(), bounds.centerX(), textY, paint);
繪制進(jìn)度條
進(jìn)度條可不是一個(gè)圓了喔,準(zhǔn)確的說(shuō)它是一個(gè)圓弧,
畫(huà)筆使用默認(rèn)畫(huà)筆,設(shè)置顏色、Style為STROKE,設(shè)置線(xiàn)的寬度,最后是指定繪制區(qū)域和圓心,角度:
RectF mArcRect = new RectF();
Rect bounds = new Rect();
@Override
protected void onDraw(Canvas canvas) {
getDrawingRect(bounds);//獲取view的邊界
...
// 繪制進(jìn)度條圓弧。
mPaint.setColor(progressLineColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(progressLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
int deleteWidth = progressLineWidth + outLineWidth;
// 指定繪制區(qū)域
mArcRect.set(bounds.left + deleteWidth / 2, bounds.top + deleteWidth / 2,
bounds.right -deleteWidth / 2, bounds.bottom - deleteWidth / 2);
canvas.drawArc(mArcRect, 0, 360 * progress / 100, false, mPaint);
}
這里難點(diǎn)在指定繪制區(qū)域,因?yàn)椴荒馨淹獠枯喞€(xiàn)覆蓋了,所以要貼近外部輪廓線(xiàn)的內(nèi)部畫(huà),所以要最外層繪制圓的區(qū)域,所以要減去(外部圓線(xiàn)的寬 + 進(jìn)度條線(xiàn)的寬) / 2得出來(lái)的界線(xiàn)就是進(jìn)度條的邊界。
繪制和測(cè)量的完整代碼
到這里關(guān)鍵代碼都擼完了,你可以自己寫(xiě)一個(gè)試試了,我這里把完整的onDraw()和onMeasure()的源碼貼出來(lái):
private int outLineColor = Color.BLACK;
private int outLineWidth = 2;
private ColorStateList inCircleColors = ColorStateList.valueOf(Color.TRANSPARENT);
private int circleColor;
private int progressLineColor = Color.BLUE;
private int progressLineWidth = 8;
private Paint mPaint = new Paint();
private RectF mArcRect = new RectF();
private int progress = 100;
final Rect bounds = new Rect();
@Override
protected void onDraw(Canvas canvas) {
//獲取view的邊界
getDrawingRect(bounds);
int size = bounds.height() > bounds.width() ? bounds.width() : bounds.height();
float outerRadius = size / 2;
//畫(huà)內(nèi)部背景
int circleColor = inCircleColors.getColorForState(getDrawableState(), 0);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(circleColor);
canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth, mPaint);
//畫(huà)邊框圓
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(outLineWidth);
mPaint.setColor(outLineColor);
canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth / 2, mPaint);
//畫(huà)字
Paint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.setAntiAlias(true);
paint.setTextAlign(Paint.Align.CENTER);
float textY = bounds.centerY() - (paint.descent() + paint.ascent()) / 2;
canvas.drawText(getText().toString(), bounds.centerX(), textY, paint);
//畫(huà)進(jìn)度條
mPaint.setColor(progressLineColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(progressLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
int deleteWidth = progressLineWidth + outLineWidth;
mArcRect.set(bounds.left + deleteWidth / 2, bounds.top + deleteWidth / 2,
bounds.right - deleteWidth / 2, bounds.bottom - deleteWidth / 2);
canvas.drawArc(mArcRect, 0, 360 * progress / 100, false, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int lineWidth = 4 * (outLineWidth + progressLineWidth);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int size = (width > height ? width : height) + lineWidth;
setMeasuredDimension(size, size);
}
目前已知的兼容問(wèn)題修復(fù)
1.目前CircleTextProgressbar在ReletiveLayot中高度會(huì)變大,導(dǎo)致進(jìn)度條會(huì)有一點(diǎn)點(diǎn)扁。修復(fù)方法如下:
如果你要在ReletiveLayot中使用CircleTextProgressbar,就不要重寫(xiě)onMeasure()方法,然后在xml中指定CircleTextProgressbar的寬高就好,比如都指定為50dp,然后就沒(méi)有問(wèn)題啦。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android自定義倒計(jì)時(shí)控件示例
- android實(shí)現(xiàn)倒計(jì)時(shí)功能代碼
- Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的常用方法小結(jié)
- Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開(kāi)屏布局
- Android 實(shí)現(xiàn)閃屏頁(yè)和右上角的倒計(jì)時(shí)跳轉(zhuǎn)實(shí)例代碼
- Android實(shí)現(xiàn)時(shí)間倒計(jì)時(shí)功能
- Android自帶倒計(jì)時(shí)控件Chronometer使用方法詳解
- Android中使用TextView實(shí)現(xiàn)高仿京東淘寶各種倒計(jì)時(shí)效果
- Android 列表倒計(jì)時(shí)的實(shí)現(xiàn)的示例代碼(CountDownTimer)
- Android實(shí)現(xiàn)圓圈倒計(jì)時(shí)
相關(guān)文章
Android 判斷網(wǎng)絡(luò)狀態(tài)實(shí)例詳解
這篇文章主要介紹了Android 判斷網(wǎng)絡(luò)狀態(tài)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
Android Button的基本用法詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android Button的基本用法詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
Flutter利用ORM框架簡(jiǎn)化本地?cái)?shù)據(jù)庫(kù)管理詳解
使用?sqflite?相對(duì)來(lái)說(shuō)還是有點(diǎn)復(fù)雜,比如遇到數(shù)據(jù)不兼容的時(shí)候需要手動(dòng)轉(zhuǎn)換,增加了不少繁瑣的代碼。本篇我們就來(lái)介紹一個(gè)?ORM?框架,來(lái)簡(jiǎn)化數(shù)據(jù)庫(kù)的管理,感興趣的可以了解一下2023-04-04
Android中Home鍵的監(jiān)聽(tīng)和攔截示例
本篇文章主要介紹了Android中Home鍵的監(jiān)聽(tīng)和攔截示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Android性能調(diào)優(yōu)利器StrictMode應(yīng)用分析
StrictMode意思為嚴(yán)格模式,是用來(lái)檢測(cè)程序中違例情況的開(kāi)發(fā)者工具。最常用的場(chǎng)景就是檢測(cè)主線(xiàn)程中本地磁盤(pán)和網(wǎng)絡(luò)讀寫(xiě)等耗時(shí)的操作。這篇文章給大家介紹Android性能調(diào)優(yōu)利器StrictMode應(yīng)用分析,感興趣的朋友一起看看吧2018-01-01
Android ListView和Adapter數(shù)據(jù)適配器的簡(jiǎn)單介紹
這篇文章主要介紹了Android ListView和Adapter數(shù)據(jù)適配器的簡(jiǎn)單介紹,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
5分鐘快速實(shí)現(xiàn)Android爆炸破碎酷炫動(dòng)畫(huà)特效的示例
本篇文章主要介紹了5分鐘快速實(shí)現(xiàn)Android爆炸破碎酷炫動(dòng)效的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12

