深入理解Android L新特性之 頁面內容&共享元素過渡動畫
今天我們來聊聊Android L(5.0)
引入的新特性:頁面內容過渡動畫和頁面共享動畫,這兩個特性都是基於我們前面已經說過的Transition
動畫,如果你對Transition
動畫不太屬性,請先看我前面的兩篇文章。在5.0之前,我們從一個Activity A
進入到另外一個Activity B
時,如果需要設定一個動畫,我們通常可以通過overridePendingTransition(int enterAnim, int exitAnim)
來設定一個B的進入動畫和A的退出動畫,但是這個方式的缺點也很明顯,我們設定的動畫只能針對頁面中所有元素。
在5.0以後為了提升Android
在頁面切換時更好的使用者體驗,頁面間過渡動畫應運而生,更加強大靈活的API
在開始所有內容之前,我首先要請大家樹立一個基本原則:頁面過渡動畫是基於Transition
動畫來實現的!,文中的所有demo都可以從這裡下載。ok,我們進入主題吧。
基本概念
為了簡化描述,我們首先定義一些標記,在Activity A
開啟Activity B
這個場景中:
Activity A
:我們稱它為calling Activity
,簡稱calling
Activity B
:我們稱為called Activity
,以後簡稱called
calling->called
:用來描述calling
開啟called
called->>calling
:用來描述called
返回到calling
在頁面過渡中,我們有兩種動畫可以設定:1)頁面內容過渡動畫,指定整個頁面的變化(類似於之前的overridePendingTransition
);2)共享元素過渡動畫,我們可以指定calling
中的一些View
平滑過渡到called
頁面中,下面我們分開討論這兩個東西。
頁面內容過渡動畫
在內容過渡動畫中,我們可以指定四種動畫,分別為exit
(離開)enter
(進入)return
(返回)reenter
(重新進入),分別對應於下圖中的四種狀態:
AE4DCA1972E923DB60C7E070F68B2099.jpeg
可以看到exit
和reenter
作用在calling Activity
上,enter
和return
作用在called Activity
上。
和以前一樣,我們還是先來一個簡單的例子,再次安利一下,本系列文章中的程式碼都可以從這裡直接下載
有人跟我反饋說文章中的
gif
不知道從哪裡開始的,因為它是一直在重複,額從這個例子開始,我會在頁面開始加一個begin
的黑色頁面,同時右下角有時間軸
content_transiton1.gif
我們前面定義過,calling
表示第一個頁面,called
表示第二個頁面。
calling->called
之後,calling
執行了一個淡入淡出的動畫,什麼?不知道Fade
!?,那你先去讀一下我的這篇文章-什麼是過渡動畫。比如,我們在calling
中可以直接這樣指定:
Fade fade = new Fade();
getWindow().setExitTransition(fade);` //指定退出動畫
實際上,這裡一共有四個動畫:
calling exit
: 一個淡出效果,通過Fade
實現called enter
:一個從上往下的Slide
效果called return
:從左往右消失calling reenter
:從底部往上出現
上面例子中,我們直接指定了四種動畫效果。這四種效果是兩兩一對的:
exit
對應reenter
,enter
對應return
。如果我們只指定了exit
或者enter
,那麼與這兩個對應的另外一個動畫將預設使用關聯動畫的反向執行。此外,還需要注意一點的是,上例中能夠這麼看清楚這四個動畫的原因是我將動畫疊加執行去掉了,你可以通過setAllowEnterTransitionOverlap
改變這個設定。
注意,為了啟動頁面過渡效果,calling
需要將startActivity(intent)
改成呼叫startActivity(Intent intent, Bundle options)
,後面的options可以通過ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
來獲取;
在called
中我們需要將呼叫finish()
改成呼叫finishAfterTransition()
我們前面說了,這個頁面過渡基於Transition
來做的,那Transition
動畫無非就是兩點唄:1)記錄動畫前後的檢視樹的狀態變化;2)定義兩個檢視樹之間的過渡效果。第二條我們前面已經分析過了,現在來看看系統是如何來記錄兩個場景的不同狀態的。
其實對於整個頁面的進入和退出,系統採取的辦法也很簡單,以退出為例,在將calling
真正隱藏之前,系統直接呼叫TransitionManager.beginDelayedTransition(decorView,exitTransition);
,然後再呼叫decorView.setVisibility(INVISIBLE);
,這樣退出動畫就自然執行了。
是不是很簡單??
這裡其實說明了如果我們自己定義一個過渡動畫,那麼必須要處理
View
的可見性變化,因為只有處理了可見性變化才能觸發動畫,如果不明白這裡,可以去檢視我前面的文章。
共享元素動畫
怎麼描述共享元素動畫呢?所謂共享就是calling
中的一個view
和called
中的一個view
可以做一個平滑的過渡,下面動圖中的紅色的小圓圈就是一個共享元素,在實際應用中,大家可以看一下微信朋友圈圖片開啟的動畫就是一個典型的共享元素動畫。
share_transiton.gif
如果仔細的朋友可以看到,我們不僅僅將view
在兩個頁面間共享,而且在共享之前,我們有顏色變化的動畫,這裡也涉及了四個動畫,和之前內容過渡一一對應。
它們分別是
calling share exit
: 顏色由紅色變成綠色called share enter
:大小由小變大called share return
:大小由大變小calling share reenter
:顏色由綠色變成紅色
我們來看一下如何來讓兩個頁面之間的View
做一個過渡:
- 在兩個Activity的佈局中為需要進行過渡變化的View設定
android:transitionName
,android:transitionName
必須成對出現。 startActivity
時,我們通過startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this,view,transitionName).toBundle());
關聯兩個Activity中間的View。
我在前面曾經讓你想想看,我們startActivity
的第二個引數的作用是啥,因為共享元素也是基於過渡動畫來做的,而我們需要過渡的View
又處在兩個不同的Activity
中,但是一個動畫肯定不能在兩個Activity
中間無縫過渡,那系統是怎麼做到的呢?
其實著僅僅是一個障眼法而已,比如上面例子中,我們將一個View
從calling
移動到了called
,但是實際上這個動畫僅僅是在called
中執行,而我們startActivity
第二個引數的Bundle
實際上是將calling
中的共享元素的相關資訊傳遞給called
,called
在進入時,會先吧這些共享元素的資訊取出來,然後在直接操作當前檢視樹中相關聯的View
的屬性和calling
中元素一樣,記錄一下狀態,再恢復成called
本來的佈局,記錄一下狀態,然後就可以愉快地開始動畫了,真相就是這個樣子。
除了Activity
,其實Android
還提供了Fragment
切換時進行頁面過渡動畫的相關API
,和Android
完全一致,這裡就不去說明了。Material Design
引入了很多UI的新技能,其實本人不太喜歡專研UI方面的東西,但是有時候產品需求壓下來,還是得調研,多瞭解一些新的知識總歸是好的。
多謝大家關注這個Transition
動畫系列,到這裡基本上就已經完全結束了,如果你覺得自己有一些收穫,請點選底下的LIKE
吧!
2016-08-07補充:
在分享元素實戰應用中有兩個比較重要的場景,官方文件對這個場景的解決方案並不明確,在這裡補充一下。
補充一 更新共享元素對應關係
很多情況下,我們需要共享元素的頁面滿足下面的條件:calling
是一個列表頁面,called
是一個詳情頁,我們在列表中選擇一項開啟,然後會有一個元素共享過渡到詳情頁,而我們在詳情頁是可以左右滑動切換元素的,如下圖所示:
share_transiton000.gif
這個系統自帶的相簿APP,可以看到我們進入大圖預覽時,點選的是圖片4,然後我們左右滑動,切換圖片之後返回列表頁,這個時候過渡動畫直接返回到了圖片1,那這是怎麼做到的呢?
其實這裡無非在過渡動畫返回時,需要告訴系統,我的繫結關係改變了,我們可以在calling
設定一個SharedElementCallback
,然後在回撥的onMapSharedElements
更新一下對應關係,大概的邏輯程式碼如下:
setExitSharedElementCallback(new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
super.onMapSharedElements(names, sharedElements);
sharedElements.put("avator",getItemByPosition(curentPos));
}
});
我們在celled
中左右滑動修改了列表Index
之後,需要通過某種方式告知calling
,你可以通過startActivityForResult()
也可以通過EventBus
之類的第三方庫,但是需要注意的是,onMapSharedElements
在exit
和reenter
時都會觸發!
延遲共享元素
想想這種場景吧,called
中的共享元素在進入時,並沒有到達最終的位置(比如這個資料需要從網路獲取資料之後,才展示它真正的大小位置),此時如果過渡動畫如果過早的執行,那麼過渡動畫獲取的called
中共享view
的狀態並不正確,所以我們有時候希望called
中的共享元素完全layout
完畢之後再執行,幸好API
提供了支援。
在called
的onCreate
中我們可以這樣處理:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 延遲共享動畫的執行
postponeEnterTransition();
}
然後在共享元素的最終佈局確定後,你可以執行startPostponedEnterTransition
來啟動共享元素動畫,我們一般可以通過下面的工具方法來啟動延遲動畫:
private void scheduleStartPostponedTransition(final View sharedElement) {
sharedElement.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
//啟動動畫
sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}
但是使用延遲共享動畫有兩點需要注意:
1. 呼叫postponeEnterTransition
必須要在適當的時候呼叫startPostponedEnterTransition
,否則Activity
的過渡就會卡到這裡,不能執行下去
2.基於1,我們在這兩個方法間實際上也不能間隔太久(前面說的等待網路返回只是舉個例子,不要在實際上使用等待網路返回的共享元素),如果間隔太大,那頁面的過渡會直到呼叫startPostponedEnterTransition
才能進行頁面切換!
轉載地址:https://www.jianshu.com/p/50f62d9e60e1