Android開發自定義控制元件實現一個圓形進度條【帶數值和動畫】
阿新 • • 發佈:2018-12-27
實現一個如下圖所示的自定義控制元件,可以直觀地展示某個球隊在某個賽季的積分數和勝場、負場、平局數
首先對畫布進行區域劃分,整個控制元件分上下兩部分
上邊是個大的圓環,圓環中間兩行文字,沒什麼難度,選好圓心座標和半徑後直接繪製即可,繪製文字也是如此。
下部分是三個小的圓弧進度條,弧的末端繪製一個小的實心圓
首先選好座標和半徑,然後先繪製三個圓環作為弧形進度條的背景
之後從12點鐘開始繪製進度弧,知道了圓環的圓心和半徑,也知道了弧對應於12點鐘和圓環圓心的偏移角度
通過三角函式可以計算出進度弧終點座標,以進度弧終點座標為圓心繪製一個小的實心圓即可
動畫效果通過Handler的postDelayed方法觸發重繪即可實現
在專案中的效果如圖所示:
測試程式碼如下:
final Random random=new Random(); final ScoreBoardView myView=(ScoreBoardView)findViewById(R.id.custom_view); myView.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ myView.setColor(Color.BLUE); myView.setScore(random.nextInt(28)); myView.setWinDrawLose(random.nextInt(12),random.nextInt(15),random.nextInt(26)); } });
完整程式碼如下:
public class ScoreBoardView extends View { private Context context; /*弧的顏色*/ private int mColor; /*積分數,勝場數,平局數,負場數*/ private int mScore, mWinNumber, mDrawNumber, mLoseNumber; /*傳入數字的最大值*/ private final int FULL_SCORE = 30; /*動畫插值器*/ DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); /*動畫持續時間(重新整理次數)*/ private int mDuration = 10; /*動畫重新整理過程中的當前值*/ private int mCurrentTime = 0; private TypedValue typedValue; private TypedValue typedValue1; private Handler mHandler = new Handler(); private Runnable mAnimation = new Runnable() { @Override public void run() { if (mCurrentTime < mDuration) { mCurrentTime++; /*導致重繪呼叫onDraw,onDraw最後再次postDelay執行此動畫,直到達到指定的次數*/ ScoreBoardView.this.invalidate(); } } }; /*繪製圖形*/ private Paint paint = new Paint(); /*繪製文字*/ private Paint paintText = new Paint(); public ScoreBoardView(Context context) { super(context); this.context=context; init(); } public ScoreBoardView(Context context, AttributeSet attrs) { super(context, attrs); this.context=context; init(); } public ScoreBoardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context=context; init(); } private void init() { /*資料初始化,預設屬性*/ mColor = Color.rgb(95, 112, 72); mScore = 0; mWinNumber = 0; mDrawNumber = 0; mLoseNumber = 0; typedValue=new TypedValue(); typedValue1=new TypedValue(); context.getTheme().resolveAttribute(R.attr.maintextclor, typedValue, true); context.getTheme().resolveAttribute(R.attr.maintextclor_reverse,typedValue1,true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /*獲取控制元件總的寬高*/ float totalWidth = getWidth(); float totalHeight = getHeight(); /* * DecelerateInterpolator:動畫從開始到結束,變化率是一個減速的過程。 * AccelerateInterpolator:動畫從開始到結束,變化率是一個加速的過程。 * CycleInterpolator:動畫從開始到結束,變化率是迴圈給定次數的正弦曲線 * AccelerateDecelerateInterpolator:動畫從開始到結束,變化率是先加速後減速的過程。 * LinearInterpolator:動畫從開始到結束,變化率是線性變化。 * */ /*計算當前時刻動畫進度值*/ float AnimCurrentValue =mDecelerateInterpolator.getInterpolation(1.0f * mCurrentTime / mDuration); /*圖形畫筆設定*/ paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); /*積分數,上邊的大圓*/ paint.setStrokeWidth(4); paint.setColor(mColor); /*積分大圓的中心座標和半徑*/ float score_radius = totalHeight * 1 / 5, score_circle_x = totalWidth / 2, score_circle_y = totalHeight / 3; /*繪製圓弧*/ canvas.drawCircle(score_circle_x, score_circle_y, score_radius, paint); /*文字畫筆基本設定*/ paintText.setAntiAlias(true); paintText.setStyle(Paint.Style.STROKE); /*文字從中間開始繪製*/ /*Paint.Align.CENTER:The text is drawn centered horizontally on the x,y origin*/ paintText.setTextAlign(Paint.Align.CENTER); /*文字畫筆大小和顏色設定*/ paintText.setTextSize(score_radius * 3 / 4); paintText.setColor(getResources().getColor(typedValue.resourceId)); /*圓心位置繪製積分數值*/ canvas.drawText("" + mScore, score_circle_x, score_circle_y, paintText); /*縮小字型繪製文字資訊*/ paintText.setTextSize(score_radius * 1 / 4); paintText.setAlpha(80); /*圓心下邊繪製文字*/ canvas.drawText("積分", score_circle_x, score_circle_y + score_radius / 2, paintText); /*設定畫筆,畫下邊的三個小圓*/ paint.setStrokeWidth(4); paint.setAlpha(255); /*下邊三個小圓的半徑*/ float small_radius = totalHeight / 10; /*三個小圓的圓心的x座標*/ float[] circleXs = new float[]{totalWidth / 2 - score_radius * 3 / 2, totalWidth / 2, totalWidth / 2 + score_radius * 3 / 2}; /*三個小圓的圓心的y座標*/ float circleY = totalHeight * 3 / 4; /*計算三個小圓弧掃過的角度*/ float[] theta_values = new float[]{360 * mWinNumber / FULL_SCORE* AnimCurrentValue, 360 * mDrawNumber / FULL_SCORE* AnimCurrentValue, 360 * mLoseNumber / FULL_SCORE* AnimCurrentValue}; /*設定畫筆顏色,繪製外圍圓環*/ paint.setColor(getResources().getColor(typedValue1.resourceId)); /*分別繪製三個外圍圓環*/ canvas.drawCircle(circleXs[0], circleY, small_radius, paint);//畫WIN背景圓 canvas.drawCircle(circleXs[1], circleY, small_radius, paint);//畫DRAW背景圓 canvas.drawCircle(circleXs[2], circleY, small_radius, paint);//畫LOSE背景圓 /*更改畫筆顏色,繪製圓弧進度條*/ paint.setColor(mColor); /*drawArc的起始角度是3點鐘方向,因此要從12點鐘方向開始繪製,起始角度為270度*/ canvas.drawArc(new RectF(circleXs[0] - small_radius, circleY - small_radius, circleXs[0] + small_radius, circleY + small_radius), 270, theta_values[0], false, paint);//畫WIN圓形進度條 canvas.drawArc(new RectF(circleXs[1] - small_radius, circleY - small_radius, circleXs[1] + small_radius, circleY + small_radius), 270, theta_values[1], false, paint);//畫DRAW圓形進度條 canvas.drawArc(new RectF(circleXs[2] - small_radius, circleY - small_radius, circleXs[2] + small_radius, circleY + small_radius), 270, theta_values[2], false, paint);//畫LOSE圓形進度條 /*繪製圓弧結束處的小圓點,實心圓*/ paint.setStyle(Paint.Style.FILL); /*已知半徑、圓心位置、便宜角度,根據三角函式很方便計算出小實心圓圓心座標*/ canvas.drawCircle(circleXs[0] + small_radius * (float) Math.sin(theta_values[0] * Math.PI / 180), circleY - small_radius * (float) Math.cos(theta_values[0] * Math.PI / 180), 6, paint);//畫WIN末尾小圓點 canvas.drawCircle(circleXs[1] + small_radius * (float) Math.sin(theta_values[1] * Math.PI / 180), circleY - small_radius * (float) Math.cos(theta_values[1] * Math.PI / 180), 6, paint);//畫DRAW末尾小圓點 canvas.drawCircle(circleXs[2] + small_radius * (float) Math.sin(theta_values[2] * Math.PI / 180), circleY - small_radius * (float) Math.cos(theta_values[2] * Math.PI / 180), 6, paint);//畫LOSE末尾小圓點 /*繪製文字*/ paintText.setColor(getResources().getColor(typedValue.resourceId)); paintText.setTextSize(small_radius * 2 / 3); /*測量文字大小,確定繪製文字時垂直方向的位置*/ Paint.FontMetrics fontMetrics = paint.getFontMetrics(); float textBaseLineOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; /*在三個小圓的正中心位置繪製相應的數字*/ canvas.drawText("" + (int)(mWinNumber * AnimCurrentValue), circleXs[0], circleY + textBaseLineOffset, paintText); canvas.drawText("" + (int)(mDrawNumber * AnimCurrentValue), circleXs[1], circleY + textBaseLineOffset, paintText); canvas.drawText("" + (int)(mLoseNumber * AnimCurrentValue), circleXs[2], circleY + textBaseLineOffset, paintText); /*調整字型大小,繪製文字資訊*/ paintText.setTextSize(small_radius * 4 / 9); canvas.drawText("勝場", circleXs[0], circleY - small_radius*4/3, paintText); canvas.drawText("平局", circleXs[1], circleY - small_radius*4/3, paintText); canvas.drawText("負場", circleXs[2], circleY - small_radius*4/3, paintText); /*20ms重新整理一次資料*/ mHandler.postDelayed(mAnimation, 20);//啟動動畫 } public void setColor(int mColor) { this.mColor = mColor; invalidate(); } public void setScore(int score) { this.mScore = score; invalidate(); } public void setWinDrawLose(int win,int draw,int lose) { this.mWinNumber = win; this.mDrawNumber = draw; this.mLoseNumber = lose; mCurrentTime =0; invalidate(); } }