近期項目中需要使用到一種類似手機電池充電進度的動畫效果,以前沒學屬性動畫的時候,是用圖片+定時器的方式來完成的,最近一直在學習動畫這一塊,再加上複習一下自訂view的相關知識點,所以打算用屬性動畫和自訂view的方式來完成這個功能,將它開源出來,供有需要的人瞭解一下相關的內容。
本次實現的功能類似下面的效果:
接下來便詳細解析一下如何完成這個功能,瞭解其中的原理,這樣就能舉一反三,實現其他類似的動畫效果了。
詳細代碼請看大螢幕
https://github.com/crazyandcoder/ChargeProgress
圖形解析
一般,我們自訂view時,是將該view進行化解,分成一個一個小部分,然後在重疊起來進行繪製,對於這個項目,也是按照相同的步驟進行。我們用Word來簡單解析一下該動畫所包含的基本結構。
對於這個充電進度view,我將它分成了ABCD四個部分,下面來詳細說明各個部分的組成。
A部分
對於A而言,它是位於整個view的頂部,置中顯示,是一個圓角矩形。
B部分
對於B而言,它是整個view的重要組成部分,包含C和D兩部分,其中B主要屬性就是背景色的設定。
C部分
對於C而言,C就是每一個進度的樣式,顯示的是未完成的進度條樣式。
D部分
對於D而言,它跟C是一樣的,只不過是已經完成的進度樣式,區別在於顏色的不一樣。
其實,這個進度view圖形結構還是比較簡單的,只是一些簡單的矩形,組合而成,因此對於以上的分析,我們輕易的得出一些重要的屬性。
<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="charging_progress"><!--item個數--><attr name="cgv_item_count" format="integer" /><!--邊界寬度--><attr name="cgv_border_width" format="dimension" /><!--邊界顏色--><attr name="cgv_border_color" format="color" /><!--圓角半徑--><attr name="cgv_border_cornor_radius" format="dimension" /><!--充電內每個進度item模組的寬度--><attr name="cgv_item_width" format="dimension" /><!--充電內每個進度item模組的高度--><attr name="cgv_item_height" format="dimension" /><!--充電內每個進度item模組的前景色彩,充電中的顏色--><attr name="cgv_item_charging_src" format="color" /><!--充電內每個進度item模組的背景色,未充電的顏色--><attr name="cgv_item_charging_background" format="color" /><!--view 的背景--><attr name="cgv_background" format="color" /></declare-styleable></resources>
對於以上屬性,我們在自訂view的時候需要在xml檔案中進行設定,如果沒有設定的話,我們給出一個預設。然後我們在代碼中進行擷取這些屬性值。
//邊界寬度private float border_width;//item個數private int item_count;//邊界寬度private float item_width;//邊界高度private float item_height;//view內部的進度前景色彩private int item_charging_src;//view內部的進度背景色private int item_charging_background;//view背景色private int background;//<!--邊界顏色-->private int border_color;//圓角半徑private float border_cornor_radius;//擷取xml中設定的屬性值TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.charging_progress);border_width = array.getDimension(R.styleable.charging_progress_cgv_border_width, dp2px(2));item_height = array.getDimension(R.styleable.charging_progress_cgv_item_height, dp2px(10));item_width = array.getDimension(R.styleable.charging_progress_cgv_item_width, dp2px(20));item_charging_src = array.getColor(R.styleable.charging_progress_cgv_item_charging_src, 0xffffea00);item_charging_background = array.getColor(R.styleable.charging_progress_cgv_item_charging_background, 0xff544645);background = array.getColor(R.styleable.charging_progress_cgv_background, 0xff463938);border_color = array.getColor(R.styleable.charging_progress_cgv_border_color, 0xffb49d7c);border_cornor_radius = array.getDimension(R.styleable.charging_progress_cgv_border_cornor_radius, dp2px(2));item_count = array.getInt(R.styleable.charging_progress_cgv_item_count, 10);array.recycle();
已經擷取了自訂屬性的值,那麼接下來,我們就來具體繪製這些組合圖形。
對於一個自訂view,首先要做的就是測量view的大小,而本項目中view的寬度和高度,寬度是好計算的,我們設定view的寬度等於item_widht 乘以2 。但是對於高度的話,因為我們設定了progress的級數,也就是item_count,也設定了item的高度和寬度,所以對於高度,我們可以通過計算item_count 乘以 item_height,再加上間隔數和頂部矩形的就是整個view的高度。同時,我們設定,頂部矩形的高度等於item_height,寬度等於item_widht的一半,中間間隔等於item_height 除以2
/*** 測量view的寬和高,** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//總間隔數=(item_count+1) 乘以間隔高度(間隔高度等於item_height的一半)//總數=item_count 乘以 item_height + 總間隔數 + 頂部一個矩形(高度等於item的高度,寬度等於item的寬度的一半)mHeight = (int) (item_count * item_height + (item_count + 1) * item_height / 2 + item_height);mWidth = (int) (2 * item_width);setMeasuredDimension(mWidth, mHeight);}
有了上面的設定,接下來我們就可以按部就班的畫圖了。
對於座標中心點是設定在左上方,也就是(0,0)處。
畫頂部矩形
知道了座標系的原點,那麼頂部矩形的座標就可以計算了。
首先設定畫筆。
mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(border_width);mPaint.setColor((border_color));
由於頂部矩形的width等於item_widht的一半,所以它的width等於整個view的width的1/6,
int left = mWidth * 3 / 8;int top = 0;int right = 5 * mWidth / 8;int bottom = (int) item_height / 2;//頂部的矩形RectF topRect = new RectF(left, top, right, bottom);canvas.drawRoundRect(topRect, border_cornor_radius, border_cornor_radius, mPaint);
接下來繪製底部的矩形,也就是包含進度item的矩形
//總的進度背景RectF border = new RectF(0, bottom, mWidth, mHeight);canvas.drawRoundRect(border, border_cornor_radius, border_cornor_radius, mPaint);
接下來繪製每個item的矩形,對於每個item的座標,實際上是有規律可循的。
//繪製所有的進度for (int i = 1; i <= item_count; i++) {mPaint.setStyle(Paint.Style.FILL);mPaint.setColor((item_charging_background));RectF backRect = new RectF(mWidth / 4,(i + 1) * item_height / 2 + (i - 1) * item_height,3 * mWidth / 4,item_height / 2 + i * (3 * item_height / 2));canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);}
繪製動畫
對於交流動畫,就是從進度0到100的動畫顯示,依次顯示。其實也是對於座標的計算而已。接下來最終要的功能就是動畫的使用了,我們使用的是屬性動畫呢?因為,常規的動畫它不支援啊,很簡單。
對於Android屬性動畫的學習,可以查看這篇文章,稍微瞭解一下。《Android動畫瞭解》
1、交流動畫
/*** 繪製交流動畫** @param canvas*/private void drawACAnimaiton(Canvas canvas) {int j = getProgress() / item_count;//已經充好的進度for (int i = item_count; i >= (item_count - j); i--) {RectF backRect = new RectF(mWidth / 4,(i + 1) * item_height / 2 + (i - 1) * item_height,3 * mWidth / 4,item_height / 2 + i * (3 * item_height / 2));canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(item_charging_src);canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);}}
我們首先擷取當前的進度,然後依次給它填充背景,這就是已完成的進度表示。
然後使用動畫即可,我們設定進度為100,也就是充滿,然後設定動畫時間是10秒鐘,對於下面的動畫執行原理是什麼呢?其實很簡單,擷取當前的進度,然後從0開始,依次繪製進度,知道繪製的進度為100就是總的進度,最後再迴圈執行動畫即可。
/*** 設定交流動畫*/public void setACAnimation() {chargeType = AC;animAC = ObjectAnimator.ofInt(this, "progress", 100);animAC.setDuration(10 * 1000);animAC.setInterpolator(new LinearInterpolator());animAC.setRepeatCount(ValueAnimator.INFINITE);animAC.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {invalidate();}});animAC.start();}
2、直流動畫
對於直流動畫就稍微比較複雜了。當我們設定了進度後,需要我們預先繪製已完成的進度,然後在下一個進度進行閃爍表示動畫,那麼該如何完成呢?
首先看繪製代碼:
/*** 直流動畫** @param canvas*/private void drawDCAniamtion(Canvas canvas) {int j = getProgress() / item_count;//已經充好的進度for (int i = item_count; i > (item_count - j); i--) {RectF backRect = new RectF(mWidth / 4,(i + 1) * item_height / 2 + (i - 1) * item_height,3 * mWidth / 4,item_height / 2 + i * (3 * item_height / 2));canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(item_charging_src);canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);}//下一個進度,隱藏和顯示交替執行動畫int i = item_count - j;if (i > 0) {RectF backRect = new RectF(mWidth / 4,(i + 1) * item_height / 2 + (i - 1) * item_height,3 * mWidth / 4,item_height / 2 + i * (3 * item_height / 2));mPaint.setStyle(Paint.Style.FILL);if (show) {mPaint.setColor((item_charging_src));} else {mPaint.setColor((item_charging_background));}canvas.drawRoundRect(backRect, border_cornor_radius, border_cornor_radius, mPaint);}}
首先繪製已完成的進度,然後在繪製閃爍的部分。
/*** 直流動畫** @param progress*/public void setDCAnimation(final int progress) {chargeType = DC;animatorDC = ValueAnimator.ofFloat(0, 1);animatorDC.setInterpolator(new LinearInterpolator());animatorDC.setDuration(1000);animatorDC.setRepeatCount(-1);animatorDC.setRepeatMode(ValueAnimator.RESTART);animatorDC.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float value = (float) animation.getAnimatedValue();if (value > 0.5) {show = true;} else {show = false;}setProgress(progress);}});animatorDC.start();}
到這裡,就很明了了。對於直流動畫,我們使用屬性動畫中這個ValueAnimator類,它的意思就是從0到1平滑的過渡,在設定的時間內。我們的原理是當達到0.5以上後就設定灰色進度,當小於0.5的話就設定亮色進度,然後在重新整理一下view即可。
以上所述是小編給大家介紹的Android 自訂view和屬性動畫實現充電進度條效果,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!