延遲共享元素的過(guò)渡動(dòng)畫(huà) (part 3b)

延遲共享元素的過(guò)渡動(dòng)畫(huà) (part 3b)

通過(guò)討論 Lollipop Transition API 的一個(gè)重要的特性:延遲共享元素的過(guò)渡動(dòng)畫(huà),這篇博文將繼續(xù)我們關(guān)于共享元素 Transition 的深度解析。這也是我關(guān)于 Transition 這個(gè)專欄的第四篇文章。

我們通過(guò)一個(gè)常見(jiàn)的問(wèn)題來(lái)解釋為什么需要推遲某些共享元素的過(guò)渡動(dòng)畫(huà)。

理解問(wèn)題

通常問(wèn)題的根源是框架在 Activity 生命周期非常早的時(shí)候啟動(dòng)共享元素 Transition 。回想我們的第一篇文章,Transitions 必須捕獲目標(biāo) View 的起始和結(jié)束狀態(tài)來(lái)構(gòu)建合適的動(dòng)畫(huà)。因此,如果框架在共享元素獲得它在調(diào)用它的 Activity 中所給定的大小和位置前啟動(dòng)共享元素的過(guò)渡動(dòng)畫(huà),這個(gè) Transition 將不能正確捕獲到共享元素的結(jié)束狀態(tài)值,生成動(dòng)畫(huà)也會(huì)失敗(一個(gè)過(guò)渡失敗的例子Video 3.3).

Transition 開(kāi)始前,能否計(jì)算出正確的共享元素的結(jié)束值主要依靠?jī)蓚€(gè)因素:

(1) 調(diào)用共享元素的 Activity 的布局復(fù)雜度以及布局層次結(jié)構(gòu)的深度
(2)調(diào)用共享元素Activity載入數(shù)據(jù)消耗的時(shí)間

布局越復(fù)雜,在屏幕上確定共享元素的大小位置耗時(shí)越長(zhǎng)。同樣,如果調(diào)用共享元素的 Activity 依賴一個(gè)異步的數(shù)據(jù)載入,框架仍有可能會(huì)在數(shù)據(jù)載入完成前自動(dòng)開(kāi)始共享元素 Transition。下面列出的是你可能遇到的常見(jiàn)問(wèn)題:

  • 存在于 Activity 托管的 Fragment 中的共享元素FragmentTransactions 在 commit 后并不會(huì)被立即執(zhí)行,它們會(huì)被安排到主線程中等待執(zhí)行。因此,如果共享元素存在的 Fragment 的視圖層和FragmentTransaction沒(méi)有被及時(shí)執(zhí)行,框架有可能在共享元素被正確測(cè)量大小和布局到屏幕前啟動(dòng)共享元素 Transition。<a id="b1" href="#1">(1)</a>

  • 共享元素是一個(gè)高分辨率的圖片。給 ImageView 設(shè)置一個(gè)超過(guò)其初始化邊界的高分辨率圖片,最終可能會(huì)導(dǎo)致在這個(gè)視圖層里出現(xiàn)額外的布局傳遞,由此增加在共享元素準(zhǔn)備好前就啟動(dòng) Transition 的幾率。流行的異步圖片處理庫(kù)比如 VolleyPicasso ,也不能可靠的解決這個(gè)問(wèn)題:框架不能預(yù)先了解圖片是要被下載,縮放還是在后臺(tái)線程中從磁盤讀取,也不管圖片是否處理完畢就啟動(dòng)共享元素 Transition。

  • 共享元素依賴于異步的數(shù)據(jù)加載如果共享元素所需的數(shù)據(jù)是通過(guò)AsyncTaskAsyncQueryHandler,Loader或者其他類似的東西加載,在它們獲得在調(diào)用它們的 Activity 的最終數(shù)據(jù)(大小、位置)前,框架就有可能在主線程中啟動(dòng) Transition。

現(xiàn)在你可能會(huì)想:如果有辦法能讓暫時(shí)延遲 Transition 的使用,直到我們確定了共享元素的確切大小和位置才使用它就好了。幸好 Activity Transitions API<a id="b2" href="#2">(2)</a> 為我們提供了解決方案。

在 Activity 的onCreate()中調(diào)用postponeEnterTransition() 方法來(lái)暫時(shí)阻止啟動(dòng)共享元素 Transition。之后,你需要在共享元素準(zhǔn)備好后調(diào)用 startPostponedEnterTransition 來(lái)恢復(fù)過(guò)渡效果。常見(jiàn)的模式是在一個(gè)OnPreDrawListener中啟動(dòng)延時(shí) Transition,它會(huì)在共享元素測(cè)量和布局完畢后被調(diào)用<a id="b3" href="#3">(3)</a>。



@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,在調(diào)用Activity的onActivityReenter() 方法中延緩返回 Transition<a id="b4" href="#4">(4)</a>

/**
 * 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.
}

盡管添加延時(shí)可以讓共享元素 Transition 更加流暢準(zhǔn)確,但是你也要知道在應(yīng)用中引入共享元素 Transition 的延遲可能會(huì)產(chǎn)生一些負(fù)面影響:

  • 調(diào)用postponeEnterTransition后不要忘記調(diào)用startPostponedEnterTransition
    忘記調(diào)用startPostponedEnterTransition會(huì)讓你的應(yīng)用處于死鎖狀態(tài),用戶無(wú)法進(jìn)入下個(gè)Activity。
  • 不要將共享元素 Transition 延遲設(shè)置到1s以上。延遲時(shí)間過(guò)長(zhǎng)會(huì)在應(yīng)用中產(chǎn)生不必要的卡頓,影響用戶體驗(yàn)。

感謝閱讀!希望這篇文章對(duì)你有所幫助。

<a id="1" href="#b1">1</a>: 當(dāng)然,許多應(yīng)用通過(guò)調(diào)用 FragmentManager#executePendingTransactions() 來(lái)避開(kāi)這個(gè)問(wèn)題,這樣會(huì)強(qiáng)制立即執(zhí)行FragmentTransactions而不是異步。

<a id="2" href="#b2">2</a>: 注意!postponeEnterTransition()startPostponedEnterTransition()只對(duì) Activity Transition起作用,對(duì)Fragment無(wú)效。詳細(xì)信息可以在這里找到 StackOverflow & Google+

<a id="3" href="#b3">3</a>: 小貼士:你可以先調(diào)用 View#isLayoutRequested() 來(lái)確認(rèn)是否需要調(diào)用 OnPreDrawListener,有必要的話 View#isLaidOut() 在一些情況下也能派上用場(chǎng)

<a id="4" href="#b4">4</a>: 在開(kāi)發(fā)者選項(xiàng)中啟用不保留 Activity 選項(xiàng)可以方便調(diào)試共享元素返回/重新進(jìn)入時(shí)對(duì)應(yīng)過(guò)渡動(dòng)畫(huà)的行為,這也可以幫助測(cè)試在返回的過(guò)渡效果開(kāi)始之前可能發(fā)生最糟糕的情況( Activity 需要重新構(gòu)造布局加載數(shù)據(jù)...)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容