概述
在上篇筆記中對於Transition的框架和常用的API使用進行了分析,Transition最常用的是在介面過渡方面,本文繼續學習Transition在介面過渡上的使用。在介面過渡上,Transition分為不帶共享元素的Content Transition和帶共享元素的ShareElement Transition。
Content Transition
先看下content transition的一個例子,在Google Play Games上的應用:
在經過學習後我們也可以設計出類似的效果,首先需要了解在介面過渡中涉及到的一些重要方法,從ActivtyA呼叫startActivity方法喚起ActivityB,到ActivityB按返回鍵返回ActivityA涉及到與Transition有關的方法
- ActivityA.exitTransition()
- ActivityB.enterTransition()
- 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>複製程式碼
執行效果如下:
上圖動畫有兩個問題:
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上。我們可以通過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;
}
}
);複製程式碼