這效果炸了!自定義 仿BiliBili 圖片3D切換效果
?BATcoder技術(shù)群,讓一部分人先進大廠
大家好,我是劉望舒,騰訊最具價值專家,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)五年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。
前華為面試官、獨角獸公司技術(shù)總監(jiān)。
想要加入?BATcoder技術(shù)群,公號回復(fù)BAT?即可。
作者:小白彡 鏈接:https://www.jianshu.com/p/aa6770d29376
最近刷B站看到一個比較有意思的圖片切換效果,在查看一個用戶發(fā)的圖片的時候是平滑過渡,如果下一張圖片是另一個用戶發(fā)的,則會觸發(fā)一個3D翻轉(zhuǎn)的效果,不止是圖片翻轉(zhuǎn),連帶里面的布局也會一起翻轉(zhuǎn)。
話不多說、先上效果圖:

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

完美復(fù)刻,一鍵三連。
下面貼一下關(guān)鍵點完整代碼:
public?class?BliPageTransformer?implements?ViewPager.PageTransformer?{
????@Override
????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);
????}
????@Override
????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è)置靠左進行旋轉(zhuǎn)
????????????matrix.preTranslate(-?getWidth(),?-?getHeight()?>>?1);
????????????matrix.postTranslate(getWidth(),?getHeight()?>>?1);
????????}?else?{
????????????//設(shè)置靠右進行旋轉(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;
????}
}
收工。
? 耗時2年,Android進階三部曲第三部《Android進階指北》出版!
為了防止失聯(lián),歡迎關(guān)注我的小號
??微信改了推送機制,真愛請星標本公號??

