普通轉場動畫
1. 準備工作
方式一
在Activity.onCreate()
的setContentView()
前調用以下代碼。
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
方式二
在 AndroidManifest.xml 里:
<application
...
android:windowContentTransitions="true"/>
2. 創建Transition
方式一:通過XML創建
- 創建 res/transition/details_window_return_transition.xml 文件:
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together" android:duration="500"> <fade> <targets> <target android:excludeId="@android:id/statusBarBackground"/> <target android:excludeId="@android:id/navigationBarBackground"/> </targets> </fade> <slide android:slideEdge="top"> <targets> <target android:targetId="@id/details_header_container"/> </targets> </slide> <slide android:slideEdge="bottom"> <targets> <target android:targetId="@id/details_text_container"/> </targets> </slide> </transitionSet>
- 在代碼中載入 XML 文件定義的
Transition
:TransitionInflater transitionInflater = TransitionInflater.from(this); Transition transition = transitionInflater.inflateTransition(R.transition.details_window_return_transition);
方式二:通過代碼創建
Explode explode = new Explode();
explode.addTarget(android.R.id.statusBarBackground);
explode.excludeTarget(android.R.id.navigationBarBackground, true);
Fade fade = new Fade();
Slide slide = new Slide();
TransitionSet transitionSet = new TransitionSet()
.setDuration(500)
.addTransition(explode)
.addTransition(slide)
.addTransition(fade);
3. 設置Transition
代碼設置
設置方法
- 在
Activity
的onCreate()
或onCreateView()
中使用getWindow()
設置動畫。 - 在
Fragment
的onCreate()
或onCreateView()
中使用getActivity().getWindow()
設置動畫。
動畫種類
-
setExitTransition()
:當 A 啟動 B 時,使 A 中的View
退出場景的transition
-
setEnterTransition()
:當 A 啟動 B 時,使 B 中的View
進入場景的transition
-
setReturnTransition()
:當 B 返回 A 時,使 B 中的View
退出場景的transition
-
setReenterTransition()
:當 B 返回 A 時,使 A 中的View
進入場景的transition
XML設置
在 AndroidManifest.xml 里:
<activity
...
android:theme="@style/AppTheme.Details"/>
在 res/values/theme.xml 里:
<resources>
<style name="AppTheme.Details" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/black</item>
<item name="android:windowExitTransition">@transition/details_window_enter_transition</item>
<item name="android:windowEnterTransition">@transition/...</item>
<item name="android:windowReenterTransition">@transition/...</item>
<item name="android:windowReturnTransition">@transition/...</item>
</style>
</resources>
4. 針對ViewGroup
處理
- 默認情況下,無法將
ViewGroup
當做一個view
來處理,需要在ViewGroup
的對應 XML 文件中開啟TransitionGroup
屬性。設置ViewGroup
背景色屬性也有同樣的效果,即便背景是透明的。 - 若一個
Transition
中包含了沒有開啟TransitionGroup
屬性的ViewGroup
的targetId
,則此Transition
不會運行。
5. 啟動Activity
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), (Pair<View, String>[]) null).toBundle());
監聽Transition
動畫
getWindow().getEnterTransition().addListener(new TransitionListenerAdapter() { });
常見問題
- 如果沒有任何效果,檢查是否把 動畫種類 弄錯了。
共享元素轉場動畫
簡單方式
1. 為共享元素設置TransitionName
為兩個場景想要共享的View
調用setTransitionName()
為相同可標識的字符串。
view.setTransitionName(/* 可標識字符串 */);
2. 啟動Activity
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());
常見問題
- 狀態欄 和 導航欄 顯示不正常:可以把 導航欄 和 狀態欄 作為共享元素。
View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);
View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
// noinspection unchecked
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), pairs.toArray(new Pair[pairs.size()])).toBundle());
復雜方式
- 以下場景需要使用復雜方式來實現共享元素動畫:
- 當共享的
View
延遲加載時,如網絡請求、Fragment
或ViewPager
等場景,需要復雜方式來處理。 - 共享的
View
在 A啟動B 和 B返回A 發生了改變,導致不是同一個View
時。
- 當共享的
0. 執行流程
工具類:SharedElementTransitionHelper
1. 為共享元素設置TransitionName
為兩個場景想要共享的View
設置setTransitionName()
為相同可標識的字符串。
view.setTransitionName(/* 可標識字符串 */);
2. 手動設置共享元素
方法簡介
-
setEnterSharedElementCallback()
:- 設置的是 A->B 時, B 的動畫。
- 同時也是 B->A 時, B 的動畫,雖然動畫是相反的,但是會自動做倒序處理,可以看 執行流程 。
- 若 B 是
Activity
,則是由Activity.makeSceneTransitionAnimation()
觸發的。若 B 是Fragment
,則是在Fragment
的attached()
或detached()
觸發的。
-
setExitSharedElementCallback()
:- 設置的是 A->B 時, A 的動畫。
- 同時也是 B->A 時, A 的動畫,雖然動畫是相反的,但是會自動做倒序處理,可以看 執行流程 。
- 若 A 是
Activity
,則是由Activity.makeSceneTransitionAnimation()
觸發的。若 A 是Fragment
,則是在Fragment
的attached()
或detached()
觸發的。
步驟
- 定義
SharedElementCallback
對象。重寫SharedElementCallback.onMapSharedElements()
。 - 在
Activity
或者Fragment
中調用setEnterSharedElementCallback()
或setExitSharedElementCallback()
,重新設置SharedElementCallback
對象以實現重新設置 共享元素 。
簡單示例
背景
-
A 、 B 均為
Activity
。 - 在 B 返回 A 時, 共享元素 發生了變化。
A.java
必須調用子Activity
的setResult()
才會調用父Activity
的onActivityReenter()
。
// 在Activity.onActivityReenter()中設置
SharedElementTransitionHelper.setExitSharedElementCallbackOnce(this, new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View newSharedElement = /* 獲取新共享元素 */;
String newTransitionName = /* 獲取新TransitionName */;
names.clear();
sharedElements.clear();
names.add(newTransitionName);
sharedElements.put(newTransitionName, newSharedElement);
}
});
B.java
此處不要在Activity.supportFinishAfterTransition()
中設置,而應該在Activity.finishAfterTransition()
。
// 在Activity.finishAfterTransition()中設置
// 必須調用setResult()才會調用父Activity的onActivityReenter()
setResult(RESULT_OK);
SharedElementTransitionHelper.setEnterSharedElementCallbackOnce(this, new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View newSharedElement = /* 獲取新共享元素 */;
String newTransitionName = /* 獲取新TransitionName */;
names.clear();
sharedElements.clear();
names.add(newTransitionName);
sharedElements.put(newTransitionName, newSharedElement);
}
});
4. 延遲和開始共享元素動畫
- 用于處理在 轉場動畫 開始時, 共享元素 尚未加載的情況,此時延遲 轉場動畫 ,直至 共享元素 加載完畢。當有多個 共享元素 的時候也要確保所有這些 共享元素 全部加載完畢。
- 在場景剛剛開始的地方,如
Activity.onCreate()
:SharedElementTransitionHelper.pauseEnterTranstion(/* Activity */);
- 在能訪問到 共享元素 且 共享元素 加載完畢的地方,如
SharedElementTransitionHelper
中監聽了View
的 PreDraw 階段:SharedElementTransitionHelper.startEnterTranstionWhenViewIsReady(getActivity(), /* 共享元素 */);
5. 啟動Activity
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());
監聽共享元素動畫
getWindow().getSharedElementExitTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);
getWindow().getSharedElementEnterTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);
重疊屬性
控制兩個場景是否允許重疊。
代碼
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
效果
自定義共享元素動畫【Todo】
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<explode/>
<changeBounds/>
<changeTransform/>
<changeClipBounds/>
<changeImageTransform/>
</transitionSet>
- 路徑:
changeBounds
- 大小、縮放:
changeTransform
- 圖片矩陣變換:
ChangeImageTransform
- 裁剪區域:
ChangeClipBounds
教程
Material-Animations
Android Transition Framework
Postponed Shared Element Transitions