在上篇筆記中對於Transition的框架和經常使用的API使用進行了分析,Transition最經常使用的是在界面過渡方面,本文繼續學習Transition在界面過渡上的使用。在界面過渡上,Transition分爲不帶共享元素的Content Transition和帶共享元素的ShareElement Transition。html
先看下content transition的一個例子,在Google Play Games上的應用:android
在通過學習後咱們也能夠設計出相似的效果,首先須要瞭解在界面過渡中涉及到的一些重要方法,從ActivtyA調用startActivity方法喚起ActivityB,到ActivityB按返回鍵返回ActivityA涉及到與Transition有關的方法git
所以,只要咱們在對應的方法中設置了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指的是共享元素從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上。咱們能夠經過Window
的setSharedElementsUseOverlay(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; } } );複製代碼