Android過渡動畫學習

概述

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

Content Transition

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

在通過學習後咱們也能夠設計出相似的效果,首先須要瞭解在界面過渡中涉及到的一些重要方法,從ActivtyA調用startActivity方法喚起ActivityB,到ActivityB按返回鍵返回ActivityA涉及到與Transition有關的方法git

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

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

所以,只要咱們在對應的方法中設置了Transition就能夠了。在默認沒有設置對應Transition的狀況下,Material-theme應用的exitTransition爲null,enterTransition爲Fade,若是reenterTransition和returnTransition未設定,則分別對應exitTransition和enterTransition。github

使用

在style中添加android:windowContentTransitions 屬性啓用窗口內容轉換(Material-theme應用默認爲true),指定該Activity的Transitionmarkdown

<!-- 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>複製代碼

也能夠在代碼中指定app

// 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:ide

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

使用xml方式指定ActivityB的enterTransition:wordpress

<?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>複製代碼

運行效果以下:oop

上圖動畫有兩個問題:

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>複製代碼

運行以下:

再看下問題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>複製代碼

效果以下:

具體流程

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的例子

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

有了上面的分析看名字應該也猜得出方法對應的功能了,若是沒有設置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>複製代碼

執行效果:

具體流程

從圖上看,好像圖片是從一個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先註釋掉,運行效果:

能夠看到,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

本文代碼

相關文章
相關標籤/搜索