Android 自定義view和屬性動畫實現(xiàn)充電進度條效果
近期項目中需要使用到一種類似手機電池充電進度的動畫效果,以前沒學屬性動畫的時候,是用圖片+定時器的方式來完成的,最近一直在學習動畫這一塊,再加上復(fù)習一下自定義view的相關(guān)知識點,所以打算用屬性動畫和自定義view的方式來完成這個功能,將它開源出來,供有需要的人了解一下相關(guān)的內(nèi)容。
本次實現(xiàn)的功能類似下面的效果:

接下來便詳細解析一下如何完成這個功能,了解其中的原理,這樣就能舉一反三,實現(xiàn)其他類似的動畫效果了。
詳細代碼請看大屏幕
https://github.com/crazyandcoder/ChargeProgress
圖形解析
一般,我們自定義view時,是將該view進行化解,分成一個一個小部分,然后在重疊起來進行繪制,對于這個項目,也是按照相同的步驟進行。我們用Word來簡單解析一下該動畫所包含的基本結(jié)構(gòu)。

對于這個充電進度view,我將它分成了ABCD四個部分,下面來詳細說明各個部分的組成。
A部分
對于A而言,它是位于整個view的頂部,居中顯示,是一個圓角矩形。
B部分
對于B而言,它是整個view的重要組成部分,包含C和D兩部分,其中B主要屬性就是背景色的設(shè)置。
C部分
對于C而言,C就是每一個進度的樣式,顯示的是未完成的進度條樣式。
D部分
對于D而言,它跟C是一樣的,只不過是已經(jīng)完成的進度樣式,區(qū)別在于顏色的不一樣。
其實,這個進度view圖形結(jié)構(gòu)還是比較簡單的,只是一些簡單的矩形,組合而成,因此對于以上的分析,我們輕易的得出一些重要的屬性。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="charging_progress"> <!--item個數(shù)--> <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" /> <!--充電內(nèi)每個進度item模塊的寬度--> <attr name="cgv_item_width" format="dimension" /> <!--充電內(nèi)每個進度item模塊的高度--> <attr name="cgv_item_height" format="dimension" /> <!--充電內(nèi)每個進度item模塊的前景色,充電中的顏色--> <attr name="cgv_item_charging_src" format="color" /> <!--充電內(nèi)每個進度item模塊的背景色,未充電的顏色--> <attr name="cgv_item_charging_background" format="color" /> <!--view 的背景--> <attr name="cgv_background" format="color" /> </declare-styleable> </resources>
對于以上屬性,我們在自定義view的時候需要在xml文件中進行設(shè)置,如果沒有設(shè)置的話,我們給出一個默認。然后我們在代碼中進行獲取這些屬性值。
//邊界寬度 private float border_width; //item個數(shù) private int item_count; //邊界寬度 private float item_width; //邊界高度 private float item_height; //view內(nèi)部的進度前景色 private int item_charging_src; //view內(nèi)部的進度背景色 private int item_charging_background; //view背景色 private int background; //<!--邊界顏色--> private int border_color; //圓角半徑 private float border_cornor_radius; //獲取xml中設(shè)定的屬性值 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();
已經(jīng)獲取了自定義屬性的值,那么接下來,我們就來具體繪制這些組合圖形。
對于一個自定義view,首先要做的就是測量view的大小,而本項目中view的寬度和高度,寬度是好計算的,我們設(shè)置view的寬度等于item_widht 乘以2 。但是對于高度的話,因為我們設(shè)置了progress的級數(shù),也就是item_count,也設(shè)置了item的高度和寬度,所以對于高度,我們可以通過計算item_count 乘以 item_height,再加上間隔數(shù)和頂部矩形的就是整個view的高度。同時,我們設(shè)定,頂部矩形的高度等于item_height,寬度等于item_widht的一半,中間間隔等于item_height 除以2
/**
* 測量view的寬和高,
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//總間隔數(shù)=(item_count+1) 乘以間隔高度(間隔高度等于item_height的一半)
//總數(shù)=item_count 乘以 item_height + 總間隔數(shù) + 頂部一個矩形(高度等于item的高度,寬度等于item的寬度的一半)
mHeight = (int) (item_count * item_height + (item_count + 1) * item_height / 2 + item_height);
mWidth = (int) (2 * item_width);
setMeasuredDimension(mWidth, mHeight);
}
有了上面的設(shè)置,接下來我們就可以按部就班的畫圖了。
對于坐標中心點是設(shè)定在左上角,也就是(0,0)處。
畫頂部矩形
知道了坐標系的原點,那么頂部矩形的坐標就可以計算了。
首先設(shè)置畫筆。
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的坐標,實際上是有規(guī)律可循的。
//繪制所有的進度
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的動畫顯示,依次顯示。其實也是對于坐標的計算而已。接下來最終要的功能就是動畫的使用了,我們使用的是屬性動畫呢?因為,常規(guī)的動畫它不支持啊,很簡單。
對于Android屬性動畫的學習,可以查看這篇文章,稍微了解一下?!禔ndroid動畫了解》
1、交流動畫
/**
* 繪制交流動畫
*
* @param canvas
*/
private void drawACAnimaiton(Canvas canvas) {
int j = getProgress() / item_count;
//已經(jīng)充好的進度
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);
}
}
我們首先獲取當前的進度,然后依次給它填充背景,這就是已完成的進度表示。
然后使用動畫即可,我們設(shè)置進度為100,也就是充滿,然后設(shè)置動畫時間是10秒鐘,對于下面的動畫執(zhí)行原理是什么呢?其實很簡單,獲取當前的進度,然后從0開始,依次繪制進度,知道繪制的進度為100就是總的進度,最后再循環(huán)執(zhí)行動畫即可。
/**
* 設(shè)置交流動畫
*/
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() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
animAC.start();
}
2、直流動畫
對于直流動畫就稍微比較復(fù)雜了。當我們設(shè)置了進度后,需要我們預(yù)先繪制已完成的進度,然后在下一個進度進行閃爍表示動畫,那么該如何完成呢?
首先看繪制代碼:
/**
* 直流動畫
*
* @param canvas
*/
private void drawDCAniamtion(Canvas canvas) {
int j = getProgress() / item_count;
//已經(jīng)充好的進度
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);
}
//下一個進度,隱藏和顯示交替執(zhí)行動畫
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() {
@Override
public 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平滑的過渡,在設(shè)定的時間內(nèi)。我們的原理是當達到0.5以上后就設(shè)定灰色進度,當小于0.5的話就設(shè)置亮色進度,然后在刷新一下view即可。
以上所述是小編給大家介紹的Android 自定義view和屬性動畫實現(xiàn)充電進度條效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
ExpandableListView實現(xiàn)手風琴效果
這篇文章主要為大家詳細介紹了ExpandableListView實現(xiàn)手風琴效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
Android基于廣播事件機制實現(xiàn)簡單定時提醒功能代碼
這篇文章主要介紹了Android基于廣播事件機制實現(xiàn)簡單定時提醒功能代碼,較為詳細的分析了Android廣播事件機制及提醒功能的相關(guān)實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
Android開發(fā)Jetpack?Compose元素Modifier特性詳解
這篇文章主要為大家介紹了Android開發(fā)Jetpack?Compose元素Modifier特性詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Android編程調(diào)用Camera和相冊功能詳解
這篇文章主要介紹了Android編程調(diào)用Camera和相冊功能,結(jié)合實例形式分析了Android的拍照及相冊調(diào)用功能相關(guān)實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下2017-02-02
Android判斷NavigationBar是否顯示的方法(獲取屏幕真實的高度)
有些時候,我們需要知道當前手機上是否顯示了NavigationBar,也就是屏幕底部的虛擬按鍵。這篇文章主要介紹了Android判斷NavigationBar是否顯示的方法(獲取屏幕真實的高度),需要的朋友可以參考下本文2017-01-01

