延遲共享元素的過渡動畫 (part 3b)
延遲共享元素的過渡動畫 (part 3b)
>
* 原文連結 : Postponed Shared Element Transitions (part 3b)
* 譯者 : tiiime
* 校對者: chaossss
共享元素過渡延時 (part 3b)
這篇文章繼續我們關于共享元素 Transition 的深度分析,通過讨論Lollipop Transition API
的一個重要的特性:延遲共享元素的過渡動畫。這是我關于這個話題的第四篇文章。
- Part 1: 在 Activity 和 Fragment 中使用 Transition
- Part 2: 深入了解 Transition
- Part 3a: 深入了解共享元素的 Transition
- Part 3b: 延遲共享元素的 Transition
- Part 3c: 共享元素回調實踐 (coming soon!)
- Part 4: Activity & Fragment 過渡動畫示例(coming soon!)
我們通過一個常見的問題來論述為什麼需要推遲某些共享元素的過渡動畫。
了解問題
通常問題的根源是架構在 Activity 生命周期非常早的時候啟動共享元素 Transition 。回想我們的第一篇文章,
Transitions 必須捕獲目标 View 的起始和結束狀态來建構合适的動畫。是以,如果架構在共享元素獲得它在調用它的 App 所給定的大小和位置前啟動共享元素的過渡動畫,這個 Transition 将不能
正确捕獲到共享元素的結束狀态值,生成動畫也會失敗(一個過渡失敗的例子Video 3.3).
Your browser does not implement html5 video.
Transition 開始前,能否計算出正确的共享元素的結束值主要依靠兩個因素:(1) 調用共享元素 Activity
布局的複雜度和層次結構的深度 (2)調用共享元素Activity載入資料消耗的時間。布局越複雜,在螢幕上确定
共享元素的大小位置耗時越長。同樣,如果調用共享元素的 Activity 依賴一個異步的資料載入,架構
仍有可能會在資料載入完成前自動開始共享元素 Transition。下面列出的是你可能遇到的常見問題:
-
共享元素存在于 Activity 托管的 Fragment 中 ( a Fragment hosted by the called
activity)。
FragmentTransactions 在commit後不會被立即執行;它們被安排到
主線程等待執行。
是以,如果共享元素存在的 Fragment 的視圖層和FragmentTransaction沒有被及時執行,架構有可能在
共享元素被正确測量大小和布局到螢幕前啟動共享元素 Transition。(1)
-
共享元素是一個高分辨率的圖檔。給 ImageView 設定一個超過其初始化邊界的高分辨率圖檔,
最終可能會導緻在這個視圖層裡額外的布局傳遞,由此增加在共享元素準備好前就
啟動 Transition 的幾率。流行的異步圖檔處理庫比如
和Volley
Picasso
,
也不能可靠的解決這個問題:架構不能預先了解圖檔是要被下載下傳,縮放還是在背景線程中從磁盤讀取,隻是不管
圖檔是否處理完畢就啟動共享元素 Transition。
-
共享元素依賴于異步的資料加載如果共享元素所需的資料是通過AsyncTask,
AsyncQueryHandler,Loader或者其他類似的東西加載,在它們最終傳回資料前 Activity
就能被确定,架構仍有可能在資料傳回主線程前啟動 Transition
postponeEnterTransition() and startPostponedEnterTransition()
現在你可能會想:如果有辦法能讓暫時延遲 Transition 的使用,直到我們确定了共享元素的确切大小和
位置才使用它就好了。幸好
Activity Transitions API(2)為我們提供了解決方案。
在 Activity 的
onCreate()
中調用
postponeEnterTransition()
方法來暫時阻止啟動共享元素 Transition。之後,你需要在共享元素準備好後調用
startPostponedEnterTransition
來恢複過渡效果。
常見的模式是在一個
OnPreDrawListener
中啟動延時 Transition,
它會在共享元素測量和布局完畢後被調用(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;
}
});
}
忽略方法名,這裡還有第二種方法可以延遲共享元素傳回 Transition,在調用Activity的
onActivityReenter()
方法中延緩傳回 Transition(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.
}
盡管添加延時可以讓共享元素 Transition 更加流暢準确,但是你也要知道在應用中引入共享元素 Transition 的延遲還有一些副作用:
- 調用
後不要忘記調用postponeEnterTransition
startPostponedEnterTransition
。
忘記調用
會讓你的應用處于死鎖狀态,使用者無法進入下個Activity。startPostponedEnterTransition
- 不要将共享元素 Transition 延遲設定到1s以上。延遲時間過長會在應用中産生不必要的卡頓,影響使用者體驗。
感謝閱讀!希望這篇文章對你有所幫助。
1: 當然,許多應用通過調用
FragmentManager#executePendingTransactions()
來避開這個問題,這樣會強制立即執行FragmentTransactions而不是異步。
2: 注意!
postponeEnterTransition()
和
startPostponedEnterTransition()
隻對 Activity Transition起作用,對Fragment無效。詳細資訊可以在這裡找到StackOverflow&Google+
3: 小貼士:你可以先調用
View#isLayoutRequested()
來确認是否需要調用
OnPreDrawListener
,有必要的話
View#isLaidOut()
在一些情況下也能派上用場
4: 在開發者選項中啟用不保留 Activity 選項可以友善調試共享元素的傳回/重新進入過渡動畫行為,這可以幫助測試傳回的過渡效果開始之前最糟糕的情況( Activity 需要重新構造布局加載資料…)