Android仿BiliBili 圖片3D切換效果
?作者?|??小白彡 ?地址?|??https://www.jianshu.com/p/aa6770d29376
最近刷B站看到一個(gè)比較有意思的圖片切換效果,在查看一個(gè)用戶發(fā)的圖片的時(shí)候是平滑過渡,如果下一張圖片是另一個(gè)用戶發(fā)的,則會(huì)觸發(fā)一個(gè)3D翻轉(zhuǎn)的效果,不止是圖片翻轉(zhuǎn),連帶里面的布局也會(huì)一起翻轉(zhuǎn)。
話不多說、先上效果圖:

先分析一下這個(gè)效果由哪幾部分組成:
1.左右平移。
2.上下翻轉(zhuǎn)。
咦。。就這,就這
真就這么簡單,先看第一個(gè)左右平移,這是一個(gè)很普通的ViewPage可以做到的效果,直接使用ViewPage就可以了,下面是使用ViewPage的代碼:
ViewPager?vPage?=?findViewById(R.id.vpage);ListmFragments = new ArrayList<>(); mFragments.add(R.mipmap.one);mFragments.add(R.mipmap.two);mFragments.add(R.mipmap.three);mFragments.add(R.mipmap.four);mFragments.add(R.mipmap.five);BliPageAdapter pageAdapter = new BliPageAdapter(this, mFragments);vPage.setAdapter(pageAdapter);
這樣就可以實(shí)現(xiàn),左右平移的效果了(ViewPage的具體使用參考別的文章或官方文檔)
那第二個(gè)翻轉(zhuǎn)該怎么實(shí)現(xiàn)呢,這才是最主要的,在效果圖中可以明顯看到3D的效果,那么肯定會(huì)用到Camera(非相機(jī))來做, 那么3D效果是作用在哪個(gè)控件上呢?如果作用到ViewPage上,那么看到的畫面就全部是傾斜的,如果作用到圖片上,那么只有】圖片傾斜,里面的內(nèi)容不會(huì)傾斜,所以判斷3D效果是作用在ViewPage的每一個(gè)子View上,這樣的話,看到的畫面是正的,但里面的內(nèi)容可傾斜可不傾斜,有了這個(gè)依據(jù),就可以開始實(shí)現(xiàn)了:
1.給ViewPage設(shè)置PageTransformer用來監(jiān)聽滑動(dòng)的位置,因?yàn)?D效果會(huì)根據(jù)滑動(dòng)的位置來決定傾斜的角度。
vPage.setPageTransformer(false, new BliPageTransformer());BliPageTransformer 實(shí)現(xiàn)了ViewPager.PageTransformer接口:
public class BliPageTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {}}
2.自定義自ViewPage子View,因?yàn)橐谧覸iew的dispatchDraw中對顯示的內(nèi)容做3D變換效果,我使用的是ConstraintLayout為最外層的容器,所以我自定義BliConstraintLayout繼承自ConstraintLayout, 重 ? ?寫dispatchDraw方法:
public class BliConstraintLayout extends ConstraintLayout@Overrideprotected void dispatchDraw(Canvas canvas) {//camera保存狀態(tài)camera.save();//camera設(shè)置繞Y軸旋轉(zhuǎn)角度camera.rotateY(rotateY);//將變換應(yīng)用到canvas上camera.applyToCanvas(canvas);camera.getMatrix(matrix);if (!isLeftRotate){//設(shè)置靠左進(jìn)行旋轉(zhuǎn)matrix.preTranslate(- getWidth(), - getHeight() >> 1);matrix.postTranslate(getWidth(), getHeight() >> 1);} else {//設(shè)置靠右進(jìn)行旋轉(zhuǎn)matrix.preTranslate(0, - getHeight() >> 1);matrix.postTranslate(0, getHeight() >> 1);}canvas.setMatrix(matrix);//camera恢復(fù)狀態(tài)camera.restore();super.dispatchDraw(canvas);}
將ViewPage子View的xml文件外層容器替換成自定義的BliConstraintLayout
核心代碼就在這里,Camera的用法可以參考其他博客或官方文檔,
這里值得注意的是:
1.camera需要保存和恢復(fù)狀態(tài),不然下一次繪制會(huì)拿到上一次改變過的狀態(tài)。
2.使用matrix對變換矩陣進(jìn)行平移,因?yàn)槟J(rèn)變換都是針對坐標(biāo)點(diǎn)(0,0)的,而上面的3D效果,需要的是x軸 靠左和靠右, y軸居中.
3.這些設(shè)置都要放在 super.dispatchDraw(canvas)之前,因?yàn)樵趕uper.dispatchDraw(canvas)調(diào)用后表示界面已經(jīng)開始繪制的,所以要在繪制之前給它設(shè)置成我們需要變換的樣子。
在后頭看看BliPageTransformer中該怎么設(shè)置根據(jù)滑動(dòng)位置設(shè)置3D傾斜:
@Overridepublic void transformPage(@NonNull View page, float position) {BliConstraintLayout bliConstraintLayout = (BliConstraintLayout) page;/*** 傾斜度*/int tiltDegree = 34;float v = position * tiltDegree;bliConstraintLayout.setRotateY(v);if (position > 0){bliConstraintLayout.setIsLeftRotate(true);}else{bliConstraintLayout.setIsLeftRotate(false);}}
先看看方法里的參數(shù)
1.page : 當(dāng)前子View
2.position:先簡單了解一下position:position = -1 當(dāng)前顯示頁的上一頁。
position = 0 ?當(dāng)前顯示頁。
position = 1 當(dāng)前顯示頁的下一頁。
那么在滑動(dòng)到下一頁的過程中position的變化就是:
0 -> -1 ? : 對應(yīng)page為當(dāng)前顯示頁
1 -> 0 ? ?:對應(yīng)page為當(dāng)前顯示頁的下一頁
獲取到對應(yīng)View之后,再根據(jù)position計(jì)算對應(yīng)值:
例如我設(shè)置的最大角度為34度, 那么在 1->0 的過程中,角度會(huì)由34 -> 0
再判斷當(dāng)前View是當(dāng)前頁,還是下一頁,如果是當(dāng)前頁那么是相對布局的右邊傾斜,如果是當(dāng)前頁的下一頁那么應(yīng)該相對布局的左邊傾斜。
再將計(jì)算出的值設(shè)置到我們自定義的BliConstraintLayout 中,重繪畫布。
下面就可以運(yùn)行看看效果了:

完美復(fù)刻,一鍵三連。
下面貼一下關(guān)鍵點(diǎn)完整代碼:
public class BliPageTransformer implements ViewPager.PageTransformer {public void transformPage(@NonNull View page, float position) {BliConstraintLayout bliConstraintLayout = (BliConstraintLayout) page;/*** 傾斜度*/int tiltDegree = 34;float v = position * tiltDegree;bliConstraintLayout.setRotateY(v);if (position > 0){bliConstraintLayout.setIsLeftRotate(true);}else{bliConstraintLayout.setIsLeftRotate(false);}}}
public class BliConstraintLayout extends ConstraintLayout {private float rotateY = 0;private boolean isLeftRotate = true;private Matrix matrix = new Matrix();private Camera camera = new Camera();public BliConstraintLayout(@NonNull Context context) {super(context);}public BliConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public BliConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}protected void dispatchDraw(Canvas canvas) {//camera保存狀態(tài)camera.save();//camera設(shè)置繞Y軸旋轉(zhuǎn)角度camera.rotateY(rotateY);//將變換應(yīng)用到canvas上camera.applyToCanvas(canvas);camera.getMatrix(matrix);if (!isLeftRotate){//設(shè)置靠左進(jìn)行旋轉(zhuǎn)matrix.preTranslate(- getWidth(), - getHeight() >> 1);matrix.postTranslate(getWidth(), getHeight() >> 1);} else {//設(shè)置靠右進(jìn)行旋轉(zhuǎn)matrix.preTranslate(0, - getHeight() >> 1);matrix.postTranslate(0, getHeight() >> 1);}canvas.setMatrix(matrix);//camera恢復(fù)狀態(tài)camera.restore();super.dispatchDraw(canvas);}public void setRotateY(float rotateY){this.rotateY = rotateY;invalidate();}public void setIsLeftRotate(boolean isLeft){this.isLeftRotate = isLeft;}}
想要源碼的童鞋【龍旋】公眾號(hào)后臺(tái)對話框回復(fù)關(guān)鍵字:3D切換,即可獲取下載鏈接。
到這里就完成啦。
