Android過渡動畫學習

HuYounger發表於2017-09-28

概述

上篇筆記中對於Transition的框架和常用的API使用進行了分析,Transition最常用的是在介面過渡方面,本文繼續學習Transition在介面過渡上的使用。在介面過渡上,Transition分為不帶共享元素的Content Transition和帶共享元素的ShareElement Transition。

Content Transition

先看下content transition的一個例子,在Google Play Games上的應用:

Android過渡動畫學習

在經過學習後我們也可以設計出類似的效果,首先需要了解在介面過渡中涉及到的一些重要方法,從ActivtyA呼叫startActivity方法喚起ActivityB,到ActivityB按返回鍵返回ActivityA涉及到與Transition有關的方法

Android過渡動畫學習

  • ActivityA.exitTransition()
  • ActivityB.enterTransition()

Android過渡動畫學習

  • ActivityB.returnTransition()
  • ActivityA.reenterTransition()

因此,只要我們在對應的方法中設定了Transition就可以了。在預設沒有設定對應Transition的情況下,Material-theme應用的exitTransition為null,enterTransition為Fade,如果reenterTransition和returnTransition未設定,則分別對應exitTransition和enterTransition。

使用

在style中新增android:windowContentTransitions 屬性啟用視窗內容轉換(Material-theme應用預設為true),指定該Activity的Transition

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- enable window content transitions -->
    <item name="android:windowContentTransitions">true</item>

    <!-- specify enter and exit transitions -->
    <!-- options are: explode, slide, fade -->
    <item name="android:windowEnterTransition">@transition/change_image_transform</item>
    <item name="android:windowExitTransition">@transition/change_image_transform</item>
</style>複製程式碼

也可以在程式碼中指定

// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an enter transition
getWindow().setEnterTransition(new Explode());
// set an exit transition
getWindow().setExitTransition(new Explode());複製程式碼

然後啟動Acticity

startActivity(intent,
              ActivityOptions.makeSceneTransitionAnimation(this).toBundle());複製程式碼

例子

這裡在程式碼中指定ActivityA的exitTransition:

private void setupTransition() {
        Slide slide = new Slide(Gravity.LEFT);
        slide.setDuration(1000);
        slide.setInterpolator(new FastOutSlowInInterpolator());
        getWindow().setExitTransition(slide);
    }複製程式碼

使用xml方式指定ActivityB的enterTransition:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <slide
        android:duration="1000"
        android:interpolator="@android:interpolator/fast_out_slow_in"
        android:slideEdge="bottom">
        <targets>
            <target android:targetId="@id/content_container"/>
        </targets>
    </slide>
    <slide
        android:duration="1000"
        android:interpolator="@android:interpolator/fast_out_slow_in"
        android:slideEdge="top">
        <targets>
            <target android:targetId="@id/image_container"/>
        </targets>
    </slide>
</transitionSet>複製程式碼

執行效果如下:

Android過渡動畫學習

上圖動畫有兩個問題:

1.ActivityA的exitTransition還沒完全走完ActivityB的enterTransition就執行了,ActivityB的returnTransition還沒完全走完ActivityA的reenterTransition就執行了;

2.狀態列和導航欄的動畫不太協調;

問題1是因為預設情況下enter/return transition會比exit/reenter transition完全結束前稍微快一點執行,如果想讓前者完全執行完後者再進來,可以在程式碼中呼叫Window

setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)複製程式碼

或者在xml中設定

<item name="android:windowAllowEnterTransitionOverlap">false</item> 
<item name="android:windowAllowReturnTransitionOverlap">false</item>複製程式碼

執行如下:

Android過渡動畫學習

再看下問題2,預設情況下狀態列和標題欄也會參與動畫(如果有導航欄也會,測試機預設木有導航欄),當我們把xxxoverlap屬性設為false後就看得比較明顯了,如果不想讓它們參與動畫通過excludeTarget()將其排除,在程式碼中:

private void setupTransition() {
    Slide slide = new Slide(Gravity.LEFT);
    slide.setDuration(1000);
    slide.setInterpolator(new FastOutSlowInInterpolator());
    slide.excludeTarget(android.R.id.statusBarBackground, true);
    slide.excludeTarget(android.R.id.navigationBarBackground, true);
    slide.excludeTarget(R.id.appbar,true);
    getWindow().setExitTransition(slide);
}複製程式碼

或者在xml中:

<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:slideEdge="left"
    android:interpolator="@android:interpolator/fast_out_slow_in"
    android:duration="1000">

    <targets>
        <!-- if using a custom Toolbar container, specify the ID of the AppBarLayout -->
        <target android:excludeId="@id/appbar" />
        <target android:excludeId="@android:id/statusBarBackground"/>
        <target android:excludeId="@android:id/navigationBarBackground"/>
    </targets>

</slide>複製程式碼

效果如下:

Android過渡動畫學習

具體流程

ActivityA startActivity()
1.確定需要執行exit Transition的target View
2.Transition的captureStartValues()獲取target View Visibility的值(此時為VISIBLE)
3.將target View Visibility的值設為INVISIBLE
4.Transition的captureEndValues()獲取target View Visibility的值(此時為INVISIBLE)
5.Transition的createAnimator()根據前後Visibility的屬性值變化建立動畫

ActivityB Activity 開始
1.確定需要執行enter Transition的target View
2.Transition的captureStartValues()獲取獲取target View Visibility的,初始化為INVISIBLE
3.將target View Visibility的值設為VISIBLE
4.Transition的captureEndValues()獲取target View Visibility的值(此時為VISIBLE)
5.Transition的createAnimator()根據前後Visibility的屬性值變化建立動畫

ShareElement Transition

shareElement Transition的例子

Android過渡動畫學習

shareElement Transition指的是共享元素從activity/fragment到其他activity/fragment時的動畫

Android過渡動畫學習

有了上面的分析看名字應該也猜得出方法對應的功能了,如果沒有設定exit/enter shared element transitions,預設為 @android:transition/move,上面的Content Transition是根據Visibility的變化建立動畫,而shareElement Transition是根據大小,位置,和外觀的變化建立動畫,如chanageBounds、changeTransform、ChangeClipBounds、 ChangeImageTransform等,具體API使用和效果可以參考上篇。指定shareElement Transition可以通過程式碼形式:

getWindow().setSharedElementEnterTransition();
getWindow().setSharedElementExitTransition();
getWindow().setSharedElementReturnTransition();
getWindow().setSharedElementReenterTransition();複製程式碼

也可以通過xml形式:

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- specify shared element transitions -->
    <item name="android:windowSharedElementEnterTransition">
      @transition/change_image_transform</item>
    <item name="android:windowSharedElementExitTransition">
      @transition/change_image_transform</item>
</style>複製程式碼

然後啟動Acticity

Intent intent = new Intent(this, DetailsActivity.class);
// Pass data object in the bundle and populate details activity.
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
ActivityOptionsCompat options = ActivityOptionsCompat.
    makeSceneTransitionAnimation(this, (View)ivProfile, "profile");
startActivity(intent, options.toBundle());複製程式碼

在佈局檔案中對於要共享的View新增android:transitionName且保持一致,如果要共享的View有點多,可以通過Pair,Pair<View,String> 儲存著共享View和View的名稱,使用如下

Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
Pair<View, String> p1 = Pair.create((View)ivProfile, "profile");
Pair<View, String> p2 = Pair.create(vPalette, "palette");
Pair<View, String> p3 = Pair.create((View)tvName, "text");
ActivityOptionsCompat options = ActivityOptionsCompat.
    makeSceneTransitionAnimation(this, p1, p2, p3);
startActivity(intent, options.toBundle());複製程式碼

例子

在ActivityB的theme中新增SharedElementEnterTransition

<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform
</item>複製程式碼

change_image_transform.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds
        android:duration="1000"
        android:interpolator="@android:interpolator/fast_out_slow_in"/>
    <changeImageTransform
        android:duration="1000"
        android:interpolator="@android:interpolator/fast_out_slow_in"/>
</transitionSet>複製程式碼

執行效果:

Android過渡動畫學習

具體流程

從圖上看,好像圖片是從一個ActivityA"傳遞"到另一個ActivityB,實際上真正負責繪製都發生在ActivityB上:

1.ActivityA呼叫startActivity()後ActivityB處於透明狀態

2.Transition收集ActivityA中共享View的初識狀態,並傳遞給ActivityB

3.Transition收集ActivityB中共享View的最終狀態

4.Transition根據狀態改變建立動畫

5.Transition隱藏ActivityA,隨著ActivityB的共享View運動到指定位置,ActivityB的背景在ActivityA上淡入,並隨著動畫完成而完全可見。

我們可以通過修改Activity背景淡入淡出時間來驗證,在ActivityB中加入

getWindow().setTransitionBackgroundFadeDuration(2000);複製程式碼

為了更直觀,把ActivityA的exitTransition先註釋掉,執行效果:

Android過渡動畫學習

可以看到,ActivityB確實像蓋在ActivityA上,這裡用到了 ViewOverlay,原理簡單來說就是在其他View上draw,共享View利用該技術可以實現畫在其他View上。我們可以通過WindowsetSharedElementsUseOverlay(false)來關閉該功能,不過這樣一來會使最終結果和你預想的不一致,預設該值為true。

延遲載入

上面分析Transition會獲取共享檢視前後的狀態值來建立動畫,如果我們的圖片是網上下載的,那麼很有可能圖片的準確大小需要下載下來才能確定,Activity Transitions API提供了一對方法暫時推遲過渡,直到我們確切地知道共享元素已經被適當的渲染和放置。在onCreate中呼叫postponeEnterTransition()(API >= 21)或者supportPostponeEnterTransition()(API < 21)延遲過渡;當圖片的狀態確定後,呼叫startPostponedEnterTransition()(API >= 21)或supportStartPostponedEnterTransition()(API < 21)恢復過渡,常見處理:

// ... load remote image with Glide/Picasso here

supportPostponeEnterTransition();
ivBackdrop.getViewTreeObserver().addOnPreDrawListener(
    new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            ivBackdrop.getViewTreeObserver().removeOnPreDrawListener(this);
            supportStartPostponedEnterTransition();
            return true;
        }
    }
);複製程式碼

Thanks to

本文程式碼

相關文章