Android仿Flipboard動(dòng)畫(huà)效果的實(shí)現(xiàn)代碼
1.上原圖
前幾天在 Hencoder
征稿看到的Filpboard 里的的動(dòng)畫(huà)效果:
Filipboard.gif
先bb一句:在看本文的同時(shí),如果覺(jué)得我寫的模糊看不太懂的可以直接拉到文末,配合完整代碼再一步一步看。
2.實(shí)現(xiàn) 整體思路:
用手機(jī)拍下來(lái),逐幀觀看了許久,恍然大悟,就是一張紙,折起一邊之后,讓其對(duì)折線繞中心點(diǎn)旋轉(zhuǎn)。
關(guān)聯(lián)自定義 View
: Camera
來(lái)控制對(duì)折幅度, canves
控制旋轉(zhuǎn)。
具體:
每當(dāng)對(duì)折線旋轉(zhuǎn)的時(shí)候,圖標(biāo)總是一邊是折起來(lái)的,一邊是平鋪的,且中心對(duì)稱,所以將它整體分為兩部分來(lái)繪制。
使用 canves
的 ClipRect()
方法可以輕松實(shí)現(xiàn)切割畫(huà)布。
應(yīng)該也能順著想到給 canves
做動(dòng)畫(huà)讓折疊線(切割線)動(dòng)起來(lái),這里會(huì)有一個(gè)問(wèn)題,即:對(duì)折線是動(dòng)起來(lái)了,可繪制內(nèi)容也會(huì)跟著動(dòng)起來(lái),那我們要的是只有分割線在中間旋轉(zhuǎn)實(shí)時(shí)分割圖標(biāo),所以有個(gè)小技巧,
先這樣:
canvas.rotate(-degreeZ); canvas.clipRect(0, -centerY, centerX, centerY);
再這樣:
canvas.rotate(degreeZ); canvas.drawBitmap(bitmap, x, y, paint);
嗯對(duì),在旋轉(zhuǎn)畫(huà)板的代碼之后做切割,切割之后呢又讓他以完全相同的角度反向旋轉(zhuǎn),再繪制圖標(biāo),這樣就可以實(shí)現(xiàn)之前讓切割線旋轉(zhuǎn),而圖標(biāo)不動(dòng)的目的了。
先貼這部分代碼,明天寫剩余部分:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int centerX = getWidth() / 2; int centerY = getHeight() / 2; int x = centerX - bitmapWidth / 2; int y = centerY - bitmapHeight / 2; // 不動(dòng)的另一部分 canvas.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); canvas.clipRect(-centerX, -centerY, 0, centerY); canvas.rotate(degreeZ); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); canvas.restore(); }
這時(shí)候看到的效果應(yīng)該是這樣的:
切割線旋轉(zhuǎn)
接下來(lái)寫和它中心對(duì)稱的另一半:
因?yàn)槭侵行膶?duì)稱,旋轉(zhuǎn)速度一致,只需要變更切割的部分為對(duì)稱部分,所以這部分代碼只需要將前一部分代碼復(fù)制然后修改這一行 :
canvas.clipRect(-centerX, -centerY, 0, centerY);
為:
canvas.clipRect(0,-centerY,centerX,centerY);
就可以實(shí)現(xiàn)目的。
完整代碼:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int centerX = getWidth() / 2; int centerY = getHeight() / 2; int x = centerX - bitmapWidth / 2; int y = centerY - bitmapHeight / 2; // 不動(dòng)的另一部分 canvas.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); canvas.clipRect(-centerX, -centerY, 0, centerY); canvas.rotate(degreeZ); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); canvas.restore(); // 被折起來(lái)的另一部分 canvas.save(); canvas.translate(centerX,centerY); canvas.rotate(-degreeZ); canvas.clipRect(0,-centerY,centerX,centerY); canvas.rotate(degreeZ); canvas.translate(-centerX,-centerY); canvas.drawBitmap(bitmap,x,y,paint); canvas.restore(); }
這時(shí)候運(yùn)行代碼的效果就是 沒(méi)有效果。因?yàn)槲覀兝L制的兩部分一直是中心對(duì)稱,雖然我們知道它在動(dòng),但是看起來(lái)仿佛只是繪制了一張靜態(tài)的圖標(biāo)。
不著急,這時(shí)候就需要給它對(duì)折起來(lái)的這一半加上 Camera
,讓它 “折起來(lái)”。
在代碼里添加 Camera
經(jīng)典四步:
camera.save(); camera.rotateY(degreeY); camera.applyToCanvas(canvas); camera.restore();
當(dāng)然不考慮直接加上這些代碼是不行的,因?yàn)槲覀兪?對(duì)折",意味著 折起來(lái)的部分也要和 切割線 同步旋轉(zhuǎn),
所以這些代碼應(yīng)該這樣插入進(jìn)去:
// 被折起來(lái)的另一部分 canvas.save(); camera.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); camera.rotateY(degreeY); camera.applyToCanvas(canvas); canvas.clipRect(0, -centerY, centerX, centerY); canvas.rotate(degreeZ); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); camera.restore(); canvas.restore();
這時(shí)候再運(yùn)行代碼:
已然完成百分之80
嘿,原動(dòng)畫(huà)的要求已經(jīng)完成80%了。現(xiàn)在去加上最后的動(dòng)畫(huà): 之前不動(dòng)的一半也被折起來(lái)了,這個(gè)很簡(jiǎn)單修改不動(dòng)的那一部分的代碼:
// 不動(dòng)的另一部分 canvas.save(); camera.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); canvas.clipRect(-centerX, -centerY, 0, centerY); canvas.rotate(degreeZ); camera.rotateX(degreeY2); camera.applyToCanvas(canvas); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); camera.restore(); canvas.restore();
運(yùn)行代碼,ok,完成~
對(duì)了,還有個(gè)細(xì)節(jié),仔細(xì)觀察發(fā)現(xiàn) 中間旋轉(zhuǎn)動(dòng)畫(huà)
是 旋轉(zhuǎn)速度
越來(lái)越 慢
的,所以使用 LinearOutSlowInInterpolator
這個(gè)插補(bǔ)器。
關(guān)于屬性動(dòng)畫(huà),還有后面的更多的定制性以及細(xì)節(jié)優(yōu)化,比如重寫 onMeasure()
,動(dòng)畫(huà)屬性抽離就不說(shuō)了,整體思路以及一些細(xì)節(jié)思考我感覺(jué)都講出來(lái)了,不知道是不是只有我一個(gè)人懂的那種。好懶啊,想睡覺(jué)。想下班,想吃雞。
完成版
最后推薦一下大佬 扔物線
的學(xué)習(xí)網(wǎng)站 hencoder
:http://hencoder.com/ 不僅免費(fèi),而且賊6~
雖然我猜也沒(méi)多少人能堅(jiān)持看到這里,好慘,捂臉。
項(xiàng)目碼云地址: https://gitee.com/alucode/AsFilpBoard
完整代碼:
public class FlipBoardView extends View { private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private Bitmap bitmap; private Camera camera = new Camera(); int degreeZ; int degreeY; int degreeY2; //中間旋轉(zhuǎn)動(dòng)畫(huà) private ObjectAnimator animator = ObjectAnimator.ofInt(this, "degreeZ", 0, 270); //第一段折起動(dòng)畫(huà) private ObjectAnimator animator1 = ObjectAnimator.ofInt(this, "degreeY", 0, -45); //最后一段折起動(dòng)畫(huà) private ObjectAnimator animator2 = ObjectAnimator.ofInt(this, "degreeY2", 0, -45); AnimatorSet animatorSet; public FlipBoardView(Context context) { super(context); } public FlipBoardView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public FlipBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } { //糊臉修正,給camera 做z軸距離適配,避免繪制糊臉 DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); float newZ = -displayMetrics.density * 6; camera.setLocation(0, 0, newZ); bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.maps); animator.setDuration(1000); animator.setStartDelay(500); animator.setInterpolator(new LinearOutSlowInInterpolator()); animator1.setDuration(800); animator1.setStartDelay(500); animator1.setInterpolator(new LinearInterpolator()); animator2.setDuration(500); animator2.setStartDelay(500); animator2.setInterpolator(new LinearInterpolator()); animatorSet = new AnimatorSet(); animatorSet.playSequentially(animator1, animator, animator2); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } degreeZ = 0; degreeY = 0; degreeY2 = 0; animatorSet.start(); } }); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); animatorSet.start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); animatorSet.end(); } @SuppressWarnings("unused") public void setDegreeZ(int degreeZ) { this.degreeZ = degreeZ; invalidate(); } @SuppressWarnings("unused") public void setDegreeY(int degreeY) { this.degreeY = degreeY; invalidate(); } public void setDegreeY2(int degreeY2) { this.degreeY2 = degreeY2; invalidate(); } /** * 原理:從折 線分為兩部分繪制,其實(shí)是繪制了兩個(gè)bitmap 一個(gè)動(dòng)一個(gè)不動(dòng) 然后截取拼湊 * * @param canvas ca */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int centerX = getWidth() / 2; int centerY = getHeight() / 2; int x = centerX - bitmapWidth / 2; int y = centerY - bitmapHeight / 2; canvas.save(); camera.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); camera.rotateY(degreeY); camera.applyToCanvas(canvas); canvas.clipRect(0, -centerY, centerX, centerY); canvas.rotate(degreeZ); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); camera.restore(); canvas.restore(); // 不動(dòng)的另一部分 canvas.save(); camera.save(); canvas.translate(centerX, centerY); canvas.rotate(-degreeZ); canvas.clipRect(-centerX, -centerY, 0, centerY); canvas.rotate(degreeZ); camera.rotateX(degreeY2); camera.applyToCanvas(canvas); canvas.translate(-centerX, -centerY); canvas.drawBitmap(bitmap, x, y, paint); camera.restore(); canvas.restore(); } /** * onMeasure() 日常重寫 * @param widthMeasureSpec w * @param heightMeasureSpec h */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width; int height; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { width = getPaddingLeft() + bitmap.getWidth() + getPaddingRight(); } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { height = getPaddingTop() + bitmap.getHeight() + getPaddingBottom(); } setMeasuredDimension(width, height); } }
總結(jié)
以上所述是小編給大家介紹的Android仿Flipboard動(dòng)畫(huà)效果的實(shí)現(xiàn)代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
android實(shí)現(xiàn)okHttp的get和post請(qǐng)求的簡(jiǎn)單封裝與使用
這篇文章主要介紹了android實(shí)現(xiàn)okHttp的get和post請(qǐng)求的簡(jiǎn)單封裝與使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Android中SQLite數(shù)據(jù)庫(kù)知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享了關(guān)于Android中SQLite數(shù)據(jù)庫(kù)知識(shí)點(diǎn)總結(jié),有需要的朋友們跟著學(xué)習(xí)下。2019-02-02android實(shí)現(xiàn)widget時(shí)鐘示例分享
這篇文章主要介紹了android實(shí)現(xiàn)widget時(shí)鐘示例,需要的朋友可以參考下2014-03-03Android EditText限制輸入字符類型的方法總結(jié)
這篇文章主要介紹了Android EditText限制輸入字符類型的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-03-03Android UI實(shí)現(xiàn)廣告Banner輪播效果
這篇文章主要為大家詳細(xì)介紹了Android UI實(shí)現(xiàn)廣告Banner輪播效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Android 圖文詳解Binder進(jìn)程通信底層原理
Android系統(tǒng)中,多進(jìn)程間的通信都是依賴于底層Binder IPC機(jī)制,Binder機(jī)制是一種RPC方案。例如:當(dāng)進(jìn)程A中的Activity與進(jìn)程B中的Service通信時(shí),就使用了binder機(jī)制2021-10-10