這篇文章我們通過討論 Lollipop Transition API:延期的共享元素轉換,來繼續(xù)深入分析共享元素轉換。這是這一系列文章的第四部分,我會通過以下觀點展開:
首先我們討論由于常見問題導致推遲共享元素轉換的需要。
當處理共享元素轉換時,一個常見的問題源于這樣的事實:它們很早就出現(xiàn)在 Activity 生命周期的框架中?;仡?a rel="nofollow" >第 1 部分,轉換必須捕獲目標視圖的開始和結束狀態(tài),來構建一個正常運轉的動畫。因此,如果框架在它的共享元素給出最后的大小,位置以及被調(diào)用的 Activity 內(nèi)的大小之前,開始共享元素轉換,那么轉換會為它的共享元素捕獲錯誤的結束值,由此產(chǎn)生的動畫將完全錯誤(錯誤的輸入轉換示例,見 視頻 3.3)。
在轉換開始之前,共享元素的結束值是否被計算出來主要取決于兩個因素:(1)被調(diào)用的 activity 的布局的復雜性和深度,(2)調(diào)用的 activity 加載所需數(shù)據(jù)的時間。布局越復雜,那么決定共享元素在屏幕上顯示的位置和大小所花費的時間就越長。同樣地,如果 activity 內(nèi)共享元素的最終外觀取決于異步加載數(shù)據(jù),那么在數(shù)據(jù)傳遞回主線程之前,框架有可能自動的啟動共享元素轉換。下面列出的是一些你可能會遇到的常見的問題:
Fragment中,它寄存在被調(diào)用的 activity 中。 [FragmentTransactions 在被命令后不是立即執(zhí)行](https://developer.android.com/reference/android/app/FragmentTransaction.html#commit());當主線程的工作結束后,它們稍后執(zhí)行。所以,如果共享元素在 Fragment 的視圖層的內(nèi)部,并且 FragmentTransaction 執(zhí)行的不夠迅速,那么框架有可能在共享元素正確衡量之前就啟動共享元素轉換,并在屏幕上布局。1
共享元素是一個高分辨率的圖像。設置一個超過 ImageView 最初的范圍的高分辨率圖像,可能會引發(fā)額外的布局甚至視圖層次,因此在共享元素準備好之前就開始轉換的可能性會增加。流行的位圖加載/擴展庫的異步性質(zhì),如 Volley 和 Picasso,不會可靠的解決這個問題:框架沒有圖像被下載,擴展,和/或從后臺線程的磁盤獲取的這些先驗知識,所以無論圖像是否正在被處理,框架都會啟動共享元素轉換。
AsyncTask, AsyncQueryHandler, Loader 或類似的東西上下載數(shù)據(jù),框架可能會在數(shù)據(jù)傳回主線程之前啟動轉換。 postponeEnterTransition() 和 startPostponedEnterTransition()在這一點上你可能會想,“要是有辦法暫時延遲轉換,直到我們確定共享元素已正確測量和布局。” 嗯,你很幸運,因為Activity Transitions API2給了我們一個這樣做的方法!
想要暫時性的阻止共享元素轉換啟動,在你調(diào)用的 activity 的 onCreate() 方法中調(diào)用 [postponeEnterTransition()](https://developer.android.com/reference/android/app/Activity.html#postponeEnterTransition())。之后,當你確定共享元素已經(jīng)正確的定位和定形后,調(diào)用 [startPostponedEnterTransition()](https://developer.android.com/reference/android/app/Activity.html#startPostponedEnterTransition()) 來恢復轉換。你會發(fā)現(xiàn)一個有用的常見的模式,在 [OnPreDrawListener](https://developer.android.com/reference/android/app/Activity.html#startPostponedEnterTransition()) 中啟動延期轉換,當共享元素被測量并布局后會被調(diào)用:3
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Postpone the shared element enter transition.
postponeEnterTransition();
// TODO: Call the "scheduleStartPostponedTransition()" method
// below when you know for certain that the shared element is
// ready for the transition to begin.
}
/**
* Schedules the shared element transition to be started immediately
* after the shared element has been measured and laid out within the
* activity's view hierarchy. Some common places where it might make
* sense to call this method are:
*
* (1) Inside a Fragment's onCreateView() method (if the shared element
* lives inside a Fragment hosted by the called Activity).
*
* (2) Inside a Picasso Callback object (if you need to wait for Picasso to
* asynchronously load/scale a bitmap before the transition can begin).
*
* (3) Inside a LoaderCallback's onLoadFinished() method (if the shared
* element depends on data queried by a Loader).
*/
private void scheduleStartPostponedTransition(final View sharedElement) {
sharedElement.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}
忽略名字,這兩種方法也可以用來延遲共享元素返回轉換。簡單的推遲返回轉換,調(diào)用 activity 的 onActivityReenter() 方法:4
/**
* Don't forget to call setResult(Activity.RESULT_OK) in the returning
* activity or else this method won't be called!
*/
@Override
public void onActivityReenter(int resultCode, Intent data) {
super.onActivityReenter(resultCode, data);
// Postpone the shared element return transition.
postponeEnterTransition();
// TODO: Call the "scheduleStartPostponedTransition()" method
// above when you know for certain that the shared element is
// ready for the transition to begin.
}
盡管共享元素轉換更流利可靠,但是將延期共享元素添加到你的應用程序中有可能會引起潛在地副作用,注意到這點也很重要:
postponeEnterTransition 之后**不要忘記調(diào)用** startPostponedEnterTransition():忘記調(diào)用會使你的應用程序處于死鎖的狀態(tài),阻止用戶進入下一個 Activity 屏幕。 一如往常,感謝閱讀!如果你有任何問題,請隨意留下評論,如果你覺得文章對你有幫助,不要忘記+1和/或分享這篇文章。
1 當然,大多數(shù)應用程序可以通過調(diào)用 [FragmentManager#executePendingTransactions()](https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()) 來解決這個問題,該方法會強迫待定的 FragmentTransactions 立即執(zhí)行而不是異步執(zhí)行。
2 注意 postponeEnterTransition() 和 startPostponedEnterTransition() 方法只位Activity Transitions 工作,不為 Fragment Transitions 工作。關于解釋及可能的解決方案,見 this StackOverflow answer 和 this Google+ post.
3 專家提示:在調(diào)用 [View#isLayoutRequested()](http://developer.android.com/reference/android/view/View.html#isLayoutRequested()) 之前,是否需要分配 OnPreDrawListener,如果是必須的,[View#isLaidOut()](http://developer.android.com/reference/android/view/View.html#isLaidOut()) 在某些情況下可能會派上用場。
4 一個測試你的共享元素返回/重新輸入轉換的行為的好方法是通過進入 Developer Options 和啟用"Don't keep activities"設置。這將有助于測試最壞的情況,即在返回轉換開始之前,調(diào)用的 activity 需要重建其布局,重新查詢必要的數(shù)據(jù)等。