寫在前面
這是在簡書發(fā)表的處女座,這個想法也停留在腦海中很久了,一直拖到現在(懶癌發(fā)作2333),先自我介紹一番,一枚剛畢業(yè)不久的Android程序猿,初出茅廬的Android小生,之前一直在CSDN發(fā)表技術文章,但感覺相比之下簡書的整體體驗會稍好一些,希望能夠在簡書留下對自己技術的成長足跡,總結開發(fā)過程中的一些小小心得,這也是一個新的開始,互相學習,無量變何以質變。
?
?
正文
概述
日常開發(fā)經常會有遇到使用進度條的地方,有些場景會需要使用圓形百分比進度條來更優(yōu)雅地表示當前的進度,并賦予一些入場動畫,使得頁面更有活力(比如一些運動App,表示能量的進度條,消耗卡路里的進度條等等),給用戶一種不斷累積的視覺感:

?
需要定制的特性
1.設置圓弧半徑
2.背景圓弧的粗細
3.進度圓弧的粗細
4.設置進度顏色
5.中心文字大小 顏色 內容
6.進度值 最大值
7.動畫時間
?
實現思路
一共可以分為3部分來繪制: 底部的圓、進度弧線、中心文本,最終結合動畫達成效果。
1)繪制底部圓
底部繪制圓采用drawCircle(float cx, float cy, float radius, Paint paint)
代碼如下:
/**
* 繪制后面的整圓
*/
paint.setStyle(Paint.Style.STROKE); //設置空心
paint.setStrokeWidth(bgStrokeWidth); //設置圓環(huán)的寬度
paint.setColor(roundColor);
paint.setAntiAlias(true); //消除鋸齒
canvas.drawCircle(center, center, radius, paint);
?
?
2)繪制中心文本
中心文本采用drawText(String text, float x, float y, Paint paint)
這里需要解決一個點,如何計算文本的位置,讓文本整體居中?
可以計算整個View的中心點的坐標,可以通過Paint的 measureText 方法獲得文本的寬度,centerX-textWidth/2 即可得到文本的left,同理根據centerY - textSize/2 即可得到文本的top。
代碼如下:
/**
* 畫進度百分比文本
*/
paint.setStrokeWidth(0);
paint.setColor(textColor);
paint.setTextSize(textSize);
paint.setTypeface(Typeface.DEFAULT); //設置字體
if (!TextUtils.isEmpty(centerText)) {
//如果是設置文本內容,則直接測量文本長度并繪制
float textWidth = paint.measureText(centerText);
canvas.drawText(centerText, center - textWidth / 2, center + textSize / 2, paint); //畫出進度百分比
} else {
//如果是設置百分比,則計算百分比并繪制
int percent = (int) (((float) progressValue / (float) maxValue) * 100); //中間的進度百分比,先轉換成float在進行除法運算,不然都為0
float textWidth = paint.measureText(percent + "%"); //測量字體寬度,我們需要根據字體的寬度設置在圓環(huán)中間
if (percent != 0) {
canvas.drawText(percent + "%", center - textWidth / 2, center + textSize / 2, paint); //畫出進度百分比
}
}
?
?
3)繪制進度弧線
從效果圖中可以看出,進度條是從底部中心開始向兩邊展開,可以通過ValueAnimator讓當前進度(接下來以curProgress代稱)從0開始增長至最終的目標進度
進度條的繪制采用 canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint);
oval 弧形所在的區(qū)域,可以理解為弧形的邊界
startAngle 弧形的起始弧度 以x軸正方向為0°為起始點計算
sweepAngle 弧形的長度 以startAngle為起始點計算,順時針方向掃過的角度
useCenter 設為true時會將圓心與弧線包圍的區(qū)域也同時繪制,類似扇形效果
paint 繪制弧形的畫筆
?
畫了張示意圖方便理解:
drawArc示意圖.png
為了實現從底部向上繪制弧線,drawArc 的 sweepAngle 肯定是(progress/max)*360,startAngle則要通過計算來動態(tài)變更。
以0°為界限,180*(curProgress/maxProgress)則表示sweepAngle的一半的長度假如當前curProgress/maxProgress 大于0.5,說明進度條弧度的一邊已經超過了0°的界限,說明進度條弧度的startAngle要小于0且超過的這部分的長度 = -(180*(curProgress/maxProgress) - 90);假如當前curProgress/maxProgress 小于0.5,說明進度條弧度的一邊未超過0°的界限,說明進度條弧度的startAngle要大于0且超過的這部分的長度 = 90 - 180*(curProgress/maxProgress);最終其實都可以采用90 - 180*(curProgress/maxProgress) 的計算得到當前的startAngle
代碼如下:
/*
* 繪制有效的進度條弧線
*/
//設置圓環(huán)的寬度
paint.setStrokeWidth(progressStrokeWidth);
//設置進度的顏色
paint.setColor(progressColor);
paint.setStrokeCap(Paint.Cap.ROUND);
//用于定義的圓弧的形狀和大小的界限
RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);
paint.setStyle(Paint.Style.STROKE);
//根據進度畫圓弧
canvas.drawArc(oval, 90 -180 * ((float) progressValue / (float) maxValue), 360 * progressValue / maxValue, false, paint);
?
?
4)入場動畫
只需要不斷更新當前的progress值,從0增長到目標進度,然后不斷調用invalidate去刷新UI,代碼如下:
/**
* 弧線動畫
*
* @param last 起始值
* @param current 最終值
* @param length 動畫時間
*/
private void setAnimation(float last, float current, int length) {
ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current);
progressAnimator.setDuration(length);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
progressValue = (int) value;
invalidate();
}
});
progressAnimator.start();
}
?
?
后續(xù)
最近有點沉迷于自定義View,其實很多看似很基礎的東西還是很重要的,底層基礎決定上層建筑,比如本篇繪制弧線的部分,如何計算出弧線的位置和長度要結合Animator來合成我們的效果,這正是其巧妙之處,雖然都說不重復造輪子,但是有時間還是要研究琢磨造造輪子,畢竟那才是真正能被自己汲取的東西。
CSDN博客:IT_ZJYANG
源碼傳送門:GitHub-ZJYWidget-YCircleProgressBar
里面還有很多實用的自定義View源碼及demo,會長期維護,歡迎Star~ 如有不足之處或建議還望指正,相互學習,相互進步,如果覺得不錯動動小手點個喜歡, 謝謝~
