Android自定義View app更新動(dòng)畫(huà)詳解
為了做一個(gè)有溫度的IT男,我決定在以后的文章中給大家分享一些看到的,聽(tīng)到的一些東西,如果你不喜歡請(qǐng)留言讓我知道,如果你喜歡請(qǐng)點(diǎn)個(gè)贊。你也可留言寫(xiě)下自己想分享的東西,溫暖你我他。這次分享的是一首歌,毛不易的《消愁》,分享這首歌主要是這首歌的歌詞,借用薛之謙的評(píng)價(jià):“我是研究歌詞的人,我研究了十幾年,但是你寫(xiě)到我想給你跪!”,下面貼部分歌詞供大家欣賞
一杯敬朝陽(yáng),一杯敬月光
喚醒我的向往,溫柔了寒窗
于是可以不回頭的逆風(fēng)飛翔
不怕心頭有雨,眼底有霜
一杯敬故鄉(xiāng),一杯敬遠(yuǎn)方
守著我的善良,催著我成長(zhǎng)
所以南北的路從此不再漫長(zhǎng)
靈魂不再無(wú)處安放
好了,言歸正傳,本篇文章是實(shí)現(xiàn)項(xiàng)目中的更新功能,效果如下
觀察動(dòng)畫(huà),可以分為幾個(gè)階段:
- 初始化階段 顯示立即升級(jí)按鈕,在點(diǎn)擊立即升級(jí)按鈕后,執(zhí)行放大再縮小至消失動(dòng)畫(huà)
- 準(zhǔn)備階段 進(jìn)度條背景從中間向兩端擴(kuò)散,然后進(jìn)度提示圖片顯示,進(jìn)度提示文字顯示0%
- 更新階段 進(jìn)度更新時(shí),進(jìn)度提示圖片和文字旋轉(zhuǎn)向前移動(dòng),如果一定時(shí)間內(nèi)進(jìn)度沒(méi)更新的話(huà),進(jìn)度提示圖片和文字要置回水平狀態(tài)
- 成功階段,進(jìn)度提示圖片縮放消失,進(jìn)度條背景從兩端向中間縮小至消失
- 安裝階段 馬上安裝圖片放大顯示
1.首選看初始化階段,我們要判斷用戶(hù)是否點(diǎn)擊了立即升級(jí)按鈕,我們通過(guò)監(jiān)聽(tīng)onTouchEvent事件判斷手指是否落在可點(diǎn)擊區(qū)域
//如果點(diǎn)擊生效,執(zhí)行動(dòng)畫(huà)
if (rectClickRange.contains(event.getX(), event.getY()))
startBtnDisappear();//立即更新按鈕消失動(dòng)畫(huà)
其中rectClickRange是我們定義的可點(diǎn)擊區(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是立即更新圖片
點(diǎn)擊生效后我們執(zhí)行立即更新按鈕消失動(dòng)畫(huà),代碼如下
/** * 點(diǎn)擊立即升級(jí)的時(shí)候,立即升級(jí)按鈕執(zhí)行消失動(dòng)畫(huà) * 動(dòng)畫(huà)效果是按鈕放大一點(diǎn)之后縮小至消失 * 根據(jù)效果選擇插值器AnticipateInterpolator(開(kāi)始的時(shí)候向后然后向前甩) * 將bitmapscale設(shè)置到立即升級(jí)圖片上 * 動(dòng)畫(huà)結(jié)束后狀態(tài)更新為準(zhǔn)備狀態(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里面繪制立即升級(jí)按鈕動(dòng)畫(huà),代碼如下: matrix.reset(); matrix.setScale(bitmapScale, bitmapScale);//縮放圖片 matrix.preTranslate(0, 0); matrix.postTranslate(width / 2 - startDrawable.getWidth() / 2 * bitmapScale, height / 2 - startDrawable.getHeight() / 2 * bitmapScale);//不斷的改變縮放的中心點(diǎn) canvas.drawBitmap(startDrawable, matrix, bitmapPaint);
2.接著我們看一下準(zhǔn)備階段,我們通過(guò)畫(huà)path,并不斷的改變path的起點(diǎn)和終點(diǎn)達(dá)到所需要的動(dòng)畫(huà)效果,代碼如下:
/** * PREPARE狀態(tài) * 進(jìn)度條從中間向兩端擴(kuò)散 * 具體做法是不斷改變path的起點(diǎn)和終點(diǎn)坐標(biāo) * 動(dòng)畫(huà)結(jié)束的時(shí)候開(kāi)始下載更新 */ 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();//動(dòng)畫(huà)結(jié)束,通知界面開(kāi)始下載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 //進(jìn)度條完全顯示后,畫(huà)進(jìn)度提示圖片和文字 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.這個(gè)時(shí)候界面就開(kāi)始下載apk(代碼不貼了),然后通知view來(lái)更新進(jìn)度,更新的動(dòng)畫(huà)是圖片和文字旋轉(zhuǎn)向前移動(dòng)(我們的做法是將畫(huà)布旋轉(zhuǎn)),如果一定時(shí)間進(jìn)度沒(méi)有變化,更新的圖片和文字置回正常狀態(tài)(我們通過(guò)啟動(dòng)線(xiàn)程不斷的將畫(huà)布旋轉(zhuǎn)回來(lái)并更新view,如果這個(gè)階段進(jìn)度有更新的話(huà),我們把線(xiàn)程remove掉),繪制代碼如下
pbPath.reset(); pbPath.moveTo(startX, height / 2); pbPath.lineTo(endX, height / 2); pm.setPath(pbPath, false); //不斷截取進(jìn)度到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(); //如果進(jìn)度沒(méi)有到達(dá)100%,并且進(jìn)度在更新的時(shí)候,畫(huà)布旋轉(zhuǎn),然后畫(huà)進(jì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); //我們啟動(dòng)一個(gè)線(xiàn)程,如果300毫秒進(jìn)度沒(méi)有更新,將畫(huà)布旋轉(zhuǎn)回來(lái)畫(huà)進(jìn)度提示圖片和文字 if (progressOffsetX < pm.getLength()) postDelayed(rotateRunnable, 300); else toSucc(); canvas.restore();
其中rotateRunnable的代碼如下 //每隔一段時(shí)間刷新界面,如果進(jìn)度沒(méi)有更新,將畫(huà)布旋轉(zhuǎn)回來(lái) private Runnable rotateRunnable = new Runnable() { @Override public void run() { isRotate = true; invalidate(); } };
4.當(dāng)進(jìn)度達(dá)到100%的時(shí)候,我們將進(jìn)度提示圖片縮放至消失,并且進(jìn)度背景執(zhí)行兩端向中間縮小動(dòng)畫(huà),也是改變path的起始點(diǎn),代碼如下
//下載進(jìn)度達(dá)到100時(shí),進(jìn)度提示圖片進(jìn)行縮放 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); } //成功后進(jìn)度條縮放動(dòng)畫(huà) 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);//不斷的改變起點(diǎn) pbPath.lineTo(endX - transx, height / 2);//改變終點(diǎn) 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縮放動(dòng)畫(huà) canvas.drawBitmap(pbProgerssDrawable, matrix, bitmapPaint);//bitmap縮放動(dòng)畫(huà)
5.最后就是顯示馬上安裝圖片動(dòng)畫(huà)了,一個(gè)簡(jiǎn)單的縮放
//顯示馬上安裝圖片動(dòng)畫(huà) 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);
回過(guò)頭來(lái)看看,其實(shí)當(dāng)我們把動(dòng)畫(huà)不斷的分解之后,發(fā)現(xiàn)其實(shí)每個(gè)動(dòng)畫(huà)并沒(méi)有那么難,我們這里用到的有path繪制及截取,getPosTan(獲取路徑上某點(diǎn)的坐標(biāo)及其切線(xiàn)的坐標(biāo)),利用Matrix做動(dòng)畫(huà),使用屬性動(dòng)畫(huà)ValueAnimator。本篇還有好多功能沒(méi)有實(shí)現(xiàn),比如下載失敗動(dòng)畫(huà),失敗后恢復(fù)至初始化動(dòng)畫(huà),不過(guò)任何輪子都不一定能完全適合你,學(xué)習(xí)到知識(shí)之后自己造一個(gè)適合自己的才是最重要。
github地址:https://github.com/MrAllRight/BezierView
- android使用ViewPager組件實(shí)現(xiàn)app引導(dǎo)查看頁(yè)面
- Android用webView包裝WebAPP方法
- Android APP之WebView校驗(yàn)SSL證書(shū)的方法
- Android中TabLayout+ViewPager 簡(jiǎn)單實(shí)現(xiàn)app底部Tab導(dǎo)航欄
- 詳解Android中ListView實(shí)現(xiàn)圖文并列并且自定義分割線(xiàn)(完善仿微信APP)
- Android App使用RecyclerView實(shí)現(xiàn)上拉和下拉刷新的方法
- Android App開(kāi)發(fā)中使用RecyclerView替代ListView的實(shí)踐
- Android App中使用ViewPager實(shí)現(xiàn)滑動(dòng)分頁(yè)的要點(diǎn)解析
- Android App中ViewPager所帶來(lái)的滑動(dòng)沖突問(wèn)題解決方法
相關(guān)文章
Android listview動(dòng)態(tài)加載列表項(xiàng)實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android listview動(dòng)態(tài)加載列表項(xiàng)實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06Retrofit2.0添加Header的方法總結(jié)(推薦)
這篇文章主要介紹了Retrofit2.0添加Header的方法總結(jié)(推薦),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09Android自定義View仿支付寶芝麻信用分儀表盤(pán)
前幾天支付寶剛剛升級(jí)到v9.9,看了一眼里面的芝麻信用分,儀表盤(pán)挺好看的,所以想著來(lái)寫(xiě)一個(gè)這個(gè)版本的儀表盤(pán),不說(shuō)完全一模一樣,只是為了猜測(cè)支付寶在做這個(gè)的時(shí)候是如何設(shè)計(jì)的,在此記錄一下,有需要的可以參考借鑒。2016-09-09Android自定義View葉子旋轉(zhuǎn)完整版(六)
這篇文章主要為大家詳細(xì)介紹了Android自定義View葉子旋轉(zhuǎn)完整版,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android實(shí)現(xiàn)多媒體之播放音樂(lè)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多媒體之播放音樂(lè)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android百度地圖實(shí)現(xiàn)搜索和定位及自定義圖標(biāo)繪制并點(diǎn)擊時(shí)彈出泡泡
這篇文章主要介紹了Android百度地圖實(shí)現(xiàn)搜索和定位及自定義圖標(biāo)繪制并點(diǎn)擊時(shí)彈出泡泡的相關(guān)資料,需要的朋友可以參考下2016-01-01Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能
這篇文章主要給大家介紹了關(guān)于Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能的相關(guān)資料,對(duì)于新聞列表數(shù)據(jù)的更新和加載更多是必不可少的,而實(shí)現(xiàn)下拉刷新與上劃加載更多的方式有很多種,需要的朋友可以參考下2021-08-08Android基礎(chǔ)開(kāi)發(fā)之手勢(shì)識(shí)別
這篇文章主要為大家詳細(xì)介紹了Android基礎(chǔ)開(kāi)發(fā)之手勢(shì)識(shí)別的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06Android 序列化的存儲(chǔ)和讀取總結(jié)及簡(jiǎn)單使用
這篇文章主要介紹了Android 序列化的存儲(chǔ)和讀取總結(jié)及簡(jiǎn)單使用的相關(guān)資料,Serializable接口和Parcelable接口,本文對(duì)這兩種方式進(jìn)行簡(jiǎn)單的總結(jié)和使用,需要的朋友可以參考下2016-12-12