Android自定義View app更新動畫詳解
為了做一個有溫度的IT男,我決定在以后的文章中給大家分享一些看到的,聽到的一些東西,如果你不喜歡請留言讓我知道,如果你喜歡請點個贊。你也可留言寫下自己想分享的東西,溫暖你我他。這次分享的是一首歌,毛不易的《消愁》,分享這首歌主要是這首歌的歌詞,借用薛之謙的評價:“我是研究歌詞的人,我研究了十幾年,但是你寫到我想給你跪!”,下面貼部分歌詞供大家欣賞
一杯敬朝陽,一杯敬月光
喚醒我的向往,溫柔了寒窗
于是可以不回頭的逆風(fēng)飛翔
不怕心頭有雨,眼底有霜
一杯敬故鄉(xiāng),一杯敬遠方
守著我的善良,催著我成長
所以南北的路從此不再漫長
靈魂不再無處安放
好了,言歸正傳,本篇文章是實現(xiàn)項目中的更新功能,效果如下

觀察動畫,可以分為幾個階段:
- 初始化階段 顯示立即升級按鈕,在點擊立即升級按鈕后,執(zhí)行放大再縮小至消失動畫
- 準備階段 進度條背景從中間向兩端擴散,然后進度提示圖片顯示,進度提示文字顯示0%
- 更新階段 進度更新時,進度提示圖片和文字旋轉(zhuǎn)向前移動,如果一定時間內(nèi)進度沒更新的話,進度提示圖片和文字要置回水平狀態(tài)
- 成功階段,進度提示圖片縮放消失,進度條背景從兩端向中間縮小至消失
- 安裝階段 馬上安裝圖片放大顯示
1.首選看初始化階段,我們要判斷用戶是否點擊了立即升級按鈕,我們通過監(jiān)聽onTouchEvent事件判斷手指是否落在可點擊區(qū)域
//如果點擊生效,執(zhí)行動畫
if (rectClickRange.contains(event.getX(), event.getY()))
startBtnDisappear();//立即更新按鈕消失動畫
其中rectClickRange是我們定義的可點擊區(qū)域,也就是立即更新圖片的顯示區(qū)域
rectClickRange = new RectF(getWidth() / 2 - startDrawable.getWidth() / 2, getHeight() / 2 - startDrawable.getHeight() / 2, getWidth() / 2 + startDrawable.getWidth() / 2, getHeight() / 2 + startDrawable.getHeight() / 2);//startDrawable是立即更新圖片
點擊生效后我們執(zhí)行立即更新按鈕消失動畫,代碼如下
/**
* 點擊立即升級的時候,立即升級按鈕執(zhí)行消失動畫
* 動畫效果是按鈕放大一點之后縮小至消失
* 根據(jù)效果選擇插值器AnticipateInterpolator(開始的時候向后然后向前甩)
* 將bitmapscale設(shè)置到立即升級圖片上
* 動畫結(jié)束后狀態(tài)更新為準備狀態(tài)
*/
private void startBtnDisappear() {
initStateData();
ValueAnimator va = ValueAnimator.ofInt(0, 1);
va.setInterpolator(new AnticipateInterpolator());
va.setDuration(800);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
bitmapScale = 1 - animation.getAnimatedFraction();
invalidate();
}
});
va.addListener(new MyAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
cancleValueAnimator(va_List);
state = PREPARE;
toPrepare();
}
});
va.start();
va_List.add(va);
}
然后在onDraw里面繪制立即升級按鈕動畫,代碼如下:
matrix.reset();
matrix.setScale(bitmapScale, bitmapScale);//縮放圖片
matrix.preTranslate(0, 0);
matrix.postTranslate(width / 2 - startDrawable.getWidth() / 2 * bitmapScale, height / 2 - startDrawable.getHeight() / 2 * bitmapScale);//不斷的改變縮放的中心點
canvas.drawBitmap(startDrawable, matrix, bitmapPaint);
2.接著我們看一下準備階段,我們通過畫path,并不斷的改變path的起點和終點達到所需要的動畫效果,代碼如下:
/**
* PREPARE狀態(tài)
* 進度條從中間向兩端擴散
* 具體做法是不斷改變path的起點和終點坐標
* 動畫結(jié)束的時候開始下載更新
*/
private void toPrepare() {
final ValueAnimator va = ValueAnimator.ofFloat(0, width / 2 - pbPaint.getStrokeWidth() * 3 - pbProgerssDrawable.getWidth());
va.setInterpolator(new LinearInterpolator());
va.setDuration(200);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
startX = (int) (width / 2 - value);
endX = (int) (width / 2 + value);
if (animation.getAnimatedFraction() == 1) prepareDone = true;
invalidate();
}
});
va.addListener(new MyAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (startDownLoadListener != null && !isSetListener) {
isSetListener = true;
postDelayed(new Runnable() {
@Override
public void run() {
state = UPDATEING;
startDownLoadListener.downLoad();//動畫結(jié)束,通知界面開始下載apk
text = progress * 100 / max + "%";
}
}, 200);
}
}
});
va.start();
va_List.add(va);
}
具體的繪制代碼如下
pbPath.reset();
pbPath.moveTo(startX, height / 2);
pbPath.lineTo(endX, height / 2);
canvas.drawPath(pbPath, pbPaint);//繪制path
//進度條完全顯示后,畫進度提示圖片和文字
if (prepareDone) {
canvas.drawBitmap(pbProgerssDrawable, startX - pbProgerssDrawable.getWidth() / 2, height / 2 - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth(), bitmapPaint);
String text = "0%";
textPaint.getTextBounds(text.toCharArray(), 0, text.toCharArray().length, textRect);
canvas.drawText(text, startX - textRect.right / 2, height / 2 - pbProgerssDrawable.getHeight() / 2 - pbPaint.getStrokeWidth() + textRect.bottom, textPaint);
}
3.這個時候界面就開始下載apk(代碼不貼了),然后通知view來更新進度,更新的動畫是圖片和文字旋轉(zhuǎn)向前移動(我們的做法是將畫布旋轉(zhuǎn)),如果一定時間進度沒有變化,更新的圖片和文字置回正常狀態(tài)(我們通過啟動線程不斷的將畫布旋轉(zhuǎn)回來并更新view,如果這個階段進度有更新的話,我們把線程remove掉),繪制代碼如下
pbPath.reset();
pbPath.moveTo(startX, height / 2);
pbPath.lineTo(endX, height / 2);
pm.setPath(pbPath, false);
//不斷截取進度到pbPathSec并繪制
if (progressOffsetX >= pm.getLength()) {
pm.getSegment(0, pm.getLength(), pbPathSec, true);
pm.getPosTan(pm.getLength(), POS, null);
} else {
pm.getSegment(0, progressOffsetX, pbPathSec, true);
pm.getPosTan(progressOffsetX, POS, null);
}
matrix.reset();
matrix.setTranslate(POS[0] - pbProgerssDrawable.getWidth() / 2, POS[1] - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth());
canvas.drawPath(pbPath, pbPaint);
canvas.drawPath(pbPathSec, pbUpdatePaint);
canvas.save();
//如果進度沒有到達100%,并且進度在更新的時候,畫布旋轉(zhuǎn),然后畫進度提示圖片和文字
if (progressOffsetX < pm.getLength() && !isRotate) {
canvas.rotate(-15, POS[0], POS[1] - pbPaint.getStrokeWidth() / 2);
}
canvas.drawBitmap(pbProgerssDrawable, matrix, bitmapPaint);
if (progressOffsetX >= pm.getLength())
progressOffsetX = pm.getLength();
text = (int) (progressOffsetX * 100 / pm.getLength()) + "%";
textPaint.getTextBounds(text.toCharArray(), 0, text.toCharArray().length, textRect);
canvas.drawText(text, progressOffsetX + startX - textRect.right / 2, height / 2 - pbProgerssDrawable.getHeight() / 2 - pbPaint.getStrokeWidth() + textRect.bottom, textPaint);
//我們啟動一個線程,如果300毫秒進度沒有更新,將畫布旋轉(zhuǎn)回來畫進度提示圖片和文字
if (progressOffsetX < pm.getLength()) postDelayed(rotateRunnable, 300);
else toSucc();
canvas.restore();
其中rotateRunnable的代碼如下
//每隔一段時間刷新界面,如果進度沒有更新,將畫布旋轉(zhuǎn)回來
private Runnable rotateRunnable = new Runnable() {
@Override
public void run() {
isRotate = true;
invalidate();
}
};
4.當進度達到100%的時候,我們將進度提示圖片縮放至消失,并且進度背景執(zhí)行兩端向中間縮小動畫,也是改變path的起始點,代碼如下
//下載進度達到100時,進度提示圖片進行縮放
private void toSuccBitmapScale() {
cancleValueAnimator(va_List);
ValueAnimator va = ValueAnimator.ofFloat(1, 0);
va.setInterpolator(new AccelerateInterpolator());
va.setDuration(100);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
bitmapScale = (Float) animation.getAnimatedValue();
state = SUCCESS;
invalidate();
}
});
va.start();
va.addListener(new MyAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
toSuccPathAnim();
}
});
va_List.add(va);
}
//成功后進度條縮放動畫
private void toSuccPathAnim() {
cancleValueAnimator(va_List);
ValueAnimator va = ValueAnimator.ofInt(0, (endX - startX) / 2);
va.setInterpolator(new AccelerateInterpolator());
va.setDuration(300);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
transx = (int) animation.getAnimatedValue();
state = SUCCESS;
invalidate();
}
});
va.start();
va.addListener(new MyAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
toInstall();
}
});
va_List.add(va);
}
繪制代碼如下
pbPath.reset(); pbPath.moveTo(startX + transx, height / 2);//不斷的改變起點 pbPath.lineTo(endX - transx, height / 2);//改變終點 pm.setPath(pbPath, false); pm.getSegment(0, (endX - startX), pbPathSec, true); pm.getPosTan(endX - startX, POS, null); matrix.reset(); matrix.preTranslate(POS[0] - pbProgerssDrawable.getWidth() / 2, POS[1] - pbProgerssDrawable.getHeight() - pbPaint.getStrokeWidth()); matrix.postScale(bitmapScale, bitmapScale, POS[0], POS[1] - pbPaint.getStrokeWidth()); canvas.drawPath(pbPath, pbUpdatePaint);//path縮放動畫 canvas.drawBitmap(pbProgerssDrawable, matrix, bitmapPaint);//bitmap縮放動畫
5.最后就是顯示馬上安裝圖片動畫了,一個簡單的縮放
//顯示馬上安裝圖片動畫
private void toInstall() {
cancleValueAnimator(va_List);
ValueAnimator va = ValueAnimator.ofInt(0, 1);
va.setInterpolator(new LinearInterpolator());
va.setDuration(400);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
bitmapScale = animation.getAnimatedFraction();
state = INSTALL;
invalidate();
}
});
va.start();
va_List.add(va);
}
//繪制代碼如下
matrix.reset();
matrix.setScale(bitmapScale, bitmapScale);
matrix.preTranslate(0, 0);
matrix.postTranslate(width / 2 - succDrawable.getWidth() / 2 * bitmapScale, height / 2 - succDrawable.getHeight() / 2 * bitmapScale);
canvas.drawBitmap(succDrawable, matrix, bitmapPaint);
回過頭來看看,其實當我們把動畫不斷的分解之后,發(fā)現(xiàn)其實每個動畫并沒有那么難,我們這里用到的有path繪制及截取,getPosTan(獲取路徑上某點的坐標及其切線的坐標),利用Matrix做動畫,使用屬性動畫ValueAnimator。本篇還有好多功能沒有實現(xiàn),比如下載失敗動畫,失敗后恢復(fù)至初始化動畫,不過任何輪子都不一定能完全適合你,學(xué)習(xí)到知識之后自己造一個適合自己的才是最重要。
github地址:https://github.com/MrAllRight/BezierView
- android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面
- Android用webView包裝WebAPP方法
- Android APP之WebView校驗SSL證書的方法
- Android中TabLayout+ViewPager 簡單實現(xiàn)app底部Tab導(dǎo)航欄
- 詳解Android中ListView實現(xiàn)圖文并列并且自定義分割線(完善仿微信APP)
- Android App使用RecyclerView實現(xiàn)上拉和下拉刷新的方法
- Android App開發(fā)中使用RecyclerView替代ListView的實踐
- Android App中使用ViewPager實現(xiàn)滑動分頁的要點解析
- Android App中ViewPager所帶來的滑動沖突問題解決方法
相關(guān)文章
Android listview動態(tài)加載列表項實現(xiàn)代碼
這篇文章主要為大家詳細介紹了Android listview動態(tài)加載列表項實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-06-06
Retrofit2.0添加Header的方法總結(jié)(推薦)
這篇文章主要介紹了Retrofit2.0添加Header的方法總結(jié)(推薦),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09
Android自定義View葉子旋轉(zhuǎn)完整版(六)
這篇文章主要為大家詳細介紹了Android自定義View葉子旋轉(zhuǎn)完整版,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
Android百度地圖實現(xiàn)搜索和定位及自定義圖標繪制并點擊時彈出泡泡
這篇文章主要介紹了Android百度地圖實現(xiàn)搜索和定位及自定義圖標繪制并點擊時彈出泡泡的相關(guān)資料,需要的朋友可以參考下2016-01-01
Flutter listview如何實現(xiàn)下拉刷新上拉加載更多功能
這篇文章主要給大家介紹了關(guān)于Flutter listview如何實現(xiàn)下拉刷新上拉加載更多功能的相關(guān)資料,對于新聞列表數(shù)據(jù)的更新和加載更多是必不可少的,而實現(xiàn)下拉刷新與上劃加載更多的方式有很多種,需要的朋友可以參考下2021-08-08

