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

image
需要定製的特性
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); //設定圓環的寬度 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 + "%");//測量字型寬度,我們需要根據字型的寬度設定在圓環中間 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弧形所在的區域,可以理解為弧形的邊界
startAngle弧形的起始弧度 以x軸正方向為0°為起始點計算
sweepAngle弧形的長度 以startAngle為起始點計算,順時針方向掃過的角度
useCenter設為true時會將圓心與弧線包圍的區域也同時繪製,類似扇形效果
paint繪製弧形的畫筆
畫了張示意圖方便理解:

drawArc示意圖.png
為了實現從底部向上繪製弧線,drawArc的sweepAngle肯定是(progress/max)*360,startAngle則要通過計算來動態變更。
以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
程式碼如下:
/* * 繪製有效的進度條弧線 */ //設定圓環的寬度 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(); }
後續
最近有點沉迷於自定義View,其實很多看似很基礎的東西還是很重要的,底層基礎決定上層建築,比如本篇繪製弧線的部分,如何計算出弧線的位置和長度要結合Animator來合成我們的效果,這正是其巧妙之處,雖然都說不重複造輪子,但是有時間還是要研究琢磨造造輪子,畢竟那才是真正能被自己汲取的東西。
CSDN部落格: IT_ZJYANG
原始碼傳送門: GitHub-ZJYWidget-YCircleProgressBar
裡面還有很多實用的自定義View原始碼及demo,會長期維護,歡迎Star~ 如有不足之處或建議還望指正,相互學習,相互進步,如果覺得不錯動動小手給個Star, 謝謝~