Android Activity共享元素動畫示例解析
正文
所謂Activity
共享元素動畫,就是從ActivityA
跳轉到ActivityB
通過控制某些元素(View
)從ActivityA
開始幀的位置跳轉到ActivityB
結束幀的位置,應用過度動畫
Activity
的共享元素動畫,其動畫核心是使用的Transition
記錄共享元素的開始幀、結束幀,然後使用TransitionManager
過度動畫管理類調用beginDelayedTransition
方法 應用過度動畫
註意:Android5.0才開始支持共享元素動畫
所以咱們先介紹一下TransitionManager
的一些基礎知識
TransitionManager介紹
TransitionManager
是 Android5.0
開始提供的一個過渡動畫管理類,功能非常強大;其可應用在兩個Activity
之間、Fragment
之間、View
之間應用過渡動畫
TransitionManager
有兩個比較重要的類Scene(場景)
和Transition(過渡)
, 咱們先來介紹一下這兩個類
Scene(場景)
顧名思義Scene
就是場景的意思,在執行動畫之前,我們需要創建兩個場景(場景A和場景B), 其動畫執行流程如下:
- 根據起始佈局和結束佈局創建兩個
Scene
對象(場景A和場景B); 然而 起始佈局的場景通常是根據當前佈局自動確定的 - 創建一個
Transition
對象以定義所需的動畫類型 - 調用
TransitionManager.go(Scene, Transition)
,使用過渡動畫運行到指定的場景
生成場景
生成場景有兩種方式; 一種是調用靜態方法通過佈局生成 Scene.getSceneForLayout(sceneRoot, R.layout.scene_a, this)
,一種是直接通過構造方法new Scene(sceneRoot, viewHierarchy)
指定view對象生成
這兩種方式其實差不多,第一種通過佈局生成的方式在使用的時候會自動inflate
加載佈局生成view
對象
用法比較簡單;下面我們來看一下官方的demo
定義兩個佈局場景A和場景B
<!-- res/layout/scene_a.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(a)" /> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(a)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(a)" /> </LinearLayout>
<!-- res/layout/scene_b.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(b)" /> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(b)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(b)" /> </LinearLayout>
使用場景並執行動畫
// 創建a場景 val aScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_a, this) // 創建b場景 val bScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_b, this) var aSceneFlag = true // 添加點擊事件,切換不同的場景 binding.btClick1.setOnClickListener { if (aSceneFlag) { TransitionManager.go(bScene) aSceneFlag = false } else { TransitionManager.go(aScene) aSceneFlag = true } }
執行效果如下:
通過上面的效果可以看出,切換的一瞬間會立馬變成指定場景的所有view(文案全都變瞭),隻是應用瞭開始幀的位置而已,然後慢慢過渡到結束幀的位置;
// Scene的enter()方法源碼 public void enter() { // Apply layout change, if any if (mLayoutId > 0 || mLayout != null) { // remove掉場景根視圖下的所有view(即上一個場景) getSceneRoot().removeAllViews(); // 添加當前場景的所有view if (mLayoutId > 0) { LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); } else { mSceneRoot.addView(mLayout); } } // Notify next scene that it is entering. Subclasses may override to configure scene. if (mEnterAction != null) { mEnterAction.run(); } setCurrentScene(mSceneRoot, this); }
可見切換到指定場景,比如切換到場景b, 會remove掉場景a的所有view,然後添加場景b的所有view
其次;這種方式的兩種場景之間的切換動畫;是通過id確定兩個view之間的對應關系,從而確定view的開始幀和結束幀 來執行過渡動畫;如果沒有id對應關系的view(即沒有開始幀或結束幀), 會執行刪除動畫(默認是漸隱動畫)或添加動畫(默認是漸顯動畫)(看源碼也可以通過transtionName屬性來指定對應關系)
其視圖匹配對應關系的源碼如下:
// startValues 是開始幀所有對象的屬性 // endValues 是結束幀所有對象的屬性 private void matchStartAndEnd(TransitionValuesMaps startValues, TransitionValuesMaps endValues) { ArrayMap<View, TransitionValues> unmatchedStart = new ArrayMap<View, TransitionValues>(startValues.viewValues); ArrayMap<View, TransitionValues> unmatchedEnd = new ArrayMap<View, TransitionValues>(endValues.viewValues); for (int i = 0; i < mMatchOrder.length; i++) { switch (mMatchOrder[i]) { case MATCH_INSTANCE: // 匹配是否相同的對象(可以通過改變view的屬性,使用過渡動畫) matchInstances(unmatchedStart, unmatchedEnd); break; case MATCH_NAME: // 匹配transitionName屬性是否相同(activity之間就是通過transtionName來匹配的) matchNames(unmatchedStart, unmatchedEnd, startValues.nameValues, endValues.nameValues); break; case MATCH_ID: // 匹配view的id是否相同 matchIds(unmatchedStart, unmatchedEnd, startValues.idValues, endValues.idValues); break; case MATCH_ITEM_ID: // 特殊處理listview的item matchItemIds(unmatchedStart, unmatchedEnd, startValues.itemIdValues, endValues.itemIdValues); break; } } // 添加沒有匹配到的對象 addUnmatched(unmatchedStart, unmatchedEnd); }
可見試圖的匹配關系有很多種;可以根據 視圖對象本身、視圖的id、視圖的transitionName屬性等匹配對應關系
定義場景比較簡單,其實相對比較復雜的是Transition
過度動畫
缺點:個人覺得通過創建不同Scene
對象實現動畫效果比較麻煩,需要創建多套佈局,後期難以維護;所以一般這種使用TransitionManager.go(bScene)
方法指定Scene
對象的方式基本不常用,一般都是使用TransitionManager.beginDelayedTransition()
方法來實現過渡動畫
下面我們來介紹Transition
,並配合使用TransitionManager.beginDelayedTransition()
方法實現動畫效果
Transition(過渡)
顧名思義 Transition
是過渡的意思,裡面定義瞭怎麼 記錄開始幀的屬性、記錄結束幀的屬性、創建動畫或執行動畫的邏輯
我們先看看Transition
源碼裡比較重要的幾個方法
// android.transition.Transition的源碼 public abstract class Transition implements Cloneable { ... // 通過實現這個方法記錄view的開始幀的屬性 public abstract void captureStartValues(TransitionValues transitionValues); // 通過實現這個方法記錄view的結束幀的屬性 public abstract void captureEndValues(TransitionValues transitionValues); // 通過記錄的開始幀和結束幀的屬性,創建動畫 // 默認返回null,即沒有動畫;需要你自己創建動畫對象 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { return null; } // 執行動畫 // mAnimators 裡包含的就是上面createAnimator()方法創建的動畫對象 protected void runAnimators() { if (DBG) { Log.d(LOG_TAG, "runAnimators() on " + this); } start(); ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); // Now start every Animator that was previously created for this transition for (Animator anim : mAnimators) { if (DBG) { Log.d(LOG_TAG, " anim: " + anim); } if (runningAnimators.containsKey(anim)) { start(); runAnimator(anim, runningAnimators); } } mAnimators.clear(); end(); } ... }
如果我們要自定義Transition
過渡動畫的話,一般隻需要重寫前三個方法即可
當前系統也提供瞭一套完成的Transition
過渡動畫的子類
上面這些都是系統提供的Transition
子類 的 實現效果 和 其在captureStartValues
、captureEndValues
中記錄的屬性,然後在createAnimator
方法中創建的屬性動畫 不斷改變的屬性
當然除瞭上面的一些類以外,系統還提供瞭TransitionSet
類,可以指定一組動畫;它也是的Transition
子類
其TransitionManager
中的默認動畫就是 AutoTransition
, 它是TransitionSet
的子類
public class AutoTransition extends TransitionSet { public AutoTransition() { init(); } public AutoTransition(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setOrdering(ORDERING_SEQUENTIAL); addTransition(new Fade(Fade.OUT)). addTransition(new ChangeBounds()). addTransition(new Fade(Fade.IN)); } }
可見AutoTransition
包含瞭 淡出、位移、改變大小、淡入 等一組效果
下面我們來自定義一些Transition
,達到一些效果
// 記錄和改變translationX、translationY屬性 class XYTranslation : Transition() { override fun captureStartValues(transitionValues: TransitionValues?) { transitionValues ?: return transitionValues.values["translationX"] = transitionValues.view.translationX transitionValues.values["translationY"] = transitionValues.view.translationY } override fun captureEndValues(transitionValues: TransitionValues?) { transitionValues ?: return transitionValues.values["translationX"] = transitionValues.view.translationX transitionValues.values["translationY"] = transitionValues.view.translationY } override fun createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { if (startValues == null || endValues == null) return null val startX = startValues.values["translationX"] as Float val startY = startValues.values["translationY"] as Float val endX = endValues.values["translationX"] as Float val endY = endValues.values["translationY"] as Float var translationXAnim: Animator? = null if (startX != endX) { translationXAnim = ObjectAnimator.ofFloat(endValues.view, "translationX", startX, endX) } var translationYAnim: Animator? = null if (startY != endY) { translationYAnim = ObjectAnimator.ofFloat(endValues.view, "translationY", startY, endY) } return mergeAnimators(translationXAnim, translationYAnim) } fun mergeAnimators(animator1: Animator?, animator2: Animator?): Animator? { return if (animator1 == null) { animator2 } else if (animator2 == null) { animator1 } else { val animatorSet = AnimatorSet() animatorSet.playTogether(animator1, animator2) animatorSet } } }
// 記錄和改變backgroundColor屬性 class BackgroundColorTransition : Transition() { override fun captureStartValues(transitionValues: TransitionValues?) { transitionValues ?: return val drawable = transitionValues.view.background as? ColorDrawable ?: return transitionValues.values["backgroundColor"] = drawable.color } override fun captureEndValues(transitionValues: TransitionValues?) { transitionValues ?: return val drawable = transitionValues.view.background as? ColorDrawable ?: return transitionValues.values["backgroundColor"] = drawable.color } override fun createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { if (startValues == null || endValues == null) return null val startColor = (startValues.values["backgroundColor"] as? Int) ?: return null val endColor = (endValues.values["backgroundColor"] as? Int) ?: return null if (startColor != endColor) { return ObjectAnimator.ofArgb(endValues.view, "backgroundColor", startColor, endColor) } return super.createAnimator(sceneRoot, startValues, endValues) } }
非常簡單,上面就自定義瞭兩個XYTranslation
和BackgroundColorTransition
類,實現位移和改變背景顏色的效果
下面我們配合使用TransitionManager.beginDelayedTransition()
方法,應用XYTranslation
和BackgroundColorTransition
兩個動畫過渡類,實現效果
<!-- res/layout/activity_main.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btClick" android:layout_width="100dp" android:layout_height="wrap_content" android:text="執行動畫"/> <LinearLayout android:id="@+id/beginDelayRoot" android:layout_width="match_parent" android:layout_height="180dp" android:background="#ffff00" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3" /> </LinearLayout> </LinearLayout>
class TransitionDemoActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) val backgroundColor1 = Color.parseColor("#ffff00") val backgroundColor2 = Color.parseColor("#00ff00") var index = 0 binding.btClick.setOnClickListener { val view1 = binding.beginDelayRoot.getChildAt(0) val view2 = binding.beginDelayRoot.getChildAt(1) // 設置開始位置的x偏移量為100px(定義開始幀的屬性) view1.translationX = 100f view2.translationX = 100f // 調用beginDelayedTransition 會立馬調用 Transition的captureStartValues方法記錄開始幀 // 同時會添加一個OnPreDrawListener, 在屏幕刷新的下一幀觸發onPreDraw() 方法,然後調用captureEndValues方法記錄結束幀,然後開始執行動畫 TransitionManager.beginDelayedTransition(binding.beginDelayRoot, TransitionSet().apply { // 實現上下移動(因為沒有改變view的left屬性所以, 所以它沒有左右移動效果) addTransition(ChangeBounds()) // 通過translationX屬性實現左右移動 addTransition(XYTranslation()) // 通過backgroundColor屬性改變背景顏色 addTransition(BackgroundColorTransition()) }) // 下面開始改變視圖的屬性(定義結束幀的屬性) // 將結束位置x偏移量為0px view1.translationX = 0f view2.translationX = 0f binding.beginDelayRoot.removeView(view1) binding.beginDelayRoot.removeView(view2) binding.beginDelayRoot.addView(view1) binding.beginDelayRoot.addView(view2) binding.beginDelayRoot.setBackgroundColor(if (index % 2 == 0) backgroundColor2 else backgroundColor1) index++ } } }
其效果圖如下:
你可能會有些疑問,為什麼上面將translationX設置成100之後,立馬又改成瞭0;這樣有什麼意義嗎??
可見Transition
的使用和自定義也比較簡單,同時也能達到一些比較炫酷的效果
請註意,改變view的屬性並不會立馬重新繪制視圖,而是在屏幕的下一幀(60fps的話,就是16毫秒一幀)去繪制;而在繪制下一幀之前調用瞭TransitionManager.beginDelayedTransition()
方法,裡面會觸發XYTransition
的captureStartValues
方法記錄開始幀(記錄的translationX
為100),同時TransitionManager
會添加OnPreDrawListener
, 在屏幕下一幀到來觸發view去繪制的時候,會先調用OnPreDrawListener
的onPreDraw()
方法,裡面又會觸發XYTransition
的captureEndValues
方法記錄結束幀的屬性(記錄的translationX
為0), 然後應用動畫 改變view的屬性,最後交給view去繪制
上面講瞭這麼多,下面我們簡單分析一下TransitionManager.beginDelayedTransition
方法的源碼
首先是TransitionManager
的beginDelayedTransition
方法
// android.transition.TransitionManager源碼 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) { if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) { if (Transition.DBG) { Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + sceneRoot + ", " + transition); } sPendingTransitions.add(sceneRoot); if (transition == null) { transition = sDefaultTransition; } final Transition transitionClone = transition.clone(); sceneChangeSetup(sceneRoot, transitionClone); Scene.setCurrentScene(sceneRoot, null); sceneChangeRunTransition(sceneRoot, transitionClone); } }
裡面代碼比較少;我們主要看sceneChangeSetup
和sceneChangeRunTransition
方法的實現
// android.transition.TransitionManager源碼 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { // Capture current values ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); if (runningTransitions != null && runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { // 暫停正在運行的動畫 runningTransition.pause(sceneRoot); } } if (transition != null) { // 調用transition.captureValues; // 其實第二個參數 true或false,表示是開始還是結束,對應會調用captureStartValues和captureEndValues 方法 transition.captureValues(sceneRoot, true); } ... }
我們來簡單看看transition.captureValues
的源碼
// android.transition.Transition源碼 void captureValues(ViewGroup sceneRoot, boolean start) { clearValues(start); // 如果你的 Transition 指定瞭目標view,就會執行這個if if ((mTargetIds.size() > 0 || mTargets.size() > 0) && (mTargetNames == null || mTargetNames.isEmpty()) && (mTargetTypes == null || mTargetTypes.isEmpty())) { for (int i = 0; i < mTargetIds.size(); ++i) { int id = mTargetIds.get(i); View view = sceneRoot.findViewById(id); if (view != null) { TransitionValues values = new TransitionValues(view); if (start) { // 記錄開始幀的屬性 captureStartValues(values); } else { // 記錄結束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } } for (int i = 0; i < mTargets.size(); ++i) { View view = mTargets.get(i); TransitionValues values = new TransitionValues(view); if (start) { // 記錄開始幀的屬性 captureStartValues(values); } else { // 記錄結束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } } else { // 沒有指定目標view的情況 captureHierarchy(sceneRoot, start); } ... } private void captureHierarchy(View view, boolean start) { ... if (view.getParent() instanceof ViewGroup) { TransitionValues values = new TransitionValues(view); if (start) { // 記錄開始幀的屬性 captureStartValues(values); } else { // 記錄結束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } if (view instanceof ViewGroup) { // 遞歸遍歷所有的children ViewGroup parent = (ViewGroup) view; for (int i = 0; i < parent.getChildCount(); ++i) { captureHierarchy(parent.getChildAt(i), start); } } }
可見sceneChangeSetup
方法就會觸發Transition
的captureStartValues
方法
接下來我們來看看sceneChangeRunTransition
方法
// android.transition.TransitionManager源碼 private static void sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition) { if (transition != null && sceneRoot != null) { MultiListener listener = new MultiListener(transition, sceneRoot); sceneRoot.addOnAttachStateChangeListener(listener); // 添加OnPreDrawListener sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); } } private static class MultiListener implements ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener { ... @Override public boolean onPreDraw() { removeListeners(); ... // 這裡就會觸發captureEndValues方法,記錄結束幀的屬性 mTransition.captureValues(mSceneRoot, false); if (previousRunningTransitions != null) { for (Transition runningTransition : previousRunningTransitions) { runningTransition.resume(mSceneRoot); } } // 開始執行動畫 // 這裡就會調用 Transition的createAnimator方法 和 runAnimators方法 mTransition.playTransition(mSceneRoot); return true; } };
Transition
的playTransition
沒啥好看的,至此TransitionManager
的beginDelayedTransition
源碼分析到這裡
上面源碼裡你可能也看到瞭Transition
可以設置目標視圖,應用過渡動畫, 主要是通過addTarget
方法實現的,如果沒有設置目標視圖,默認就會遍歷所有的children應用在所有的視圖上
OverlayView和ViewGroupOverlay
OverlayView
和ViewGroupOverlay
是Activity
共享元素動畫實現裡比較重要的一個類,所以就單獨的介紹一下
OverlayView
是針對View
的一個頂層附加層(即遮罩層),它在View的所有內容繪制完成之後 再繪制
ViewGroupOverlay
是針對ViewGroup
的,是OverlayView
的子類,它在ViewGroup
的所有內容(包括所有的children)繪制完成之後 再繪制
// android.view.View源碼 public ViewOverlay getOverlay() { if (mOverlay == null) { mOverlay = new ViewOverlay(mContext, this); } return mOverlay; }
// android.view.ViewGroup源碼 @Override public ViewGroupOverlay getOverlay() { if (mOverlay == null) { mOverlay = new ViewGroupOverlay(mContext, this); } return (ViewGroupOverlay) mOverlay; }
看上面的源碼我們知道,可以直接調用getOverlay
方法直接獲取OverlayView
或ViewGroupOverlay
對象, 然後我們就可以在上面添加一些裝飾等效果
OverlayView
隻支持添加drawable
ViewGroupOverlay
支持添加View
和 drawable
註意:如果View
的parent不為null, 則會自動先把它從parent中remove掉,然後添加到ViewGroupOverlay
中
核心源碼如下:
// OverlayViewGroup的add方法源碼 public void add(@NonNull View child) { if (child == null) { throw new IllegalArgumentException("view must be non-null"); } if (child.getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) child.getParent(); ... // 將child從原來的parent中remove掉 parent.removeView(child); if (parent.getLayoutTransition() != null) { // LayoutTransition will cause the child to delay removal - cancel it parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING); } // fail-safe if view is still attached for any reason if (child.getParent() != null) { child.mParent = null; } } super.addView(child); }
用法也非常簡單,我們來看看一個簡單的demo
class OverlayActivity : AppCompatActivity() { private lateinit var binding: ActivityOverlayBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityOverlayBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) addViewToOverlayView() addDrawableToOverlayView() binding.btClick.setOnClickListener { // 測試一下OverlayView的動畫 TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation()) binding.llOverlayContainer.translationX += 100 } } private fun addViewToOverlayView() { val view = View(this) view.layoutParams = LinearLayout.LayoutParams(100, 100) view.setBackgroundColor(Color.parseColor("#ff00ff")) // 需要手動調用layout,不然view顯示不出來 view.layout(0, 0, 100, 100) binding.llOverlayContainer.overlay.add(view) } private fun addDrawableToOverlayView() { binding.view2.post { val drawable = ContextCompat.getDrawable(this, R.mipmap.ic_temp) // 需要手動調用setBounds,不然drawable顯示不出來 drawable!!.setBounds(binding.view2.width / 2, 0, binding.view2.width, binding.view2.height / 2) binding.view2.overlay.add(drawable) } } }
效果圖如下:
這裡唯一需要註意的是,如果是添加view
,需要手動調用layout
佈局,不然view
顯示不出來;如果添加的是drawable
需要手動調用setBounds
,不然drawable
也顯示不出來
GhostView
GhostView
是Activity
共享元素動畫實現裡比較重要的一個類,所以就單獨的介紹一下
它的作用是在不改變view
的parent
的情況下,將view
繪制在另一個parent
下
我們先看看GhostView
的部分源碼
public class GhostView extends View { private final View mView; private int mReferences; private boolean mBeingMoved; private GhostView(View view) { super(view.getContext()); mView = view; mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); // 這句代碼 讓mView在原來的parent中隱藏(即不繪制視圖) mView.setTransitionVisibility(View.INVISIBLE); parent.invalidate(); } @Override public void setVisibility(@Visibility int visibility) { super.setVisibility(visibility); if (mView.mGhostView == this) { // 如果view在ghostview中繪制(可見),則設置在原來的parent不繪制(不可見) // 如果view在ghostview中不繪制(不可見),則設置在原來的parent繪制(可見) int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE; mView.setTransitionVisibility(inverseVisibility); } } }
看源碼得知 如果把View
添加到GhostView
裡,則默認會調用view
的setTransitionVisibility
方法 將view
設置成在parent
中不可見, 在GhostView
裡可見;調用GhostView
的setVisibility
方法設置 要麼在GhostView
中可見,要麼在parent
中可見
系統內部是使用GhostView.addGhost
靜態方法添加GhostView
的
我們來看看添加GhostView
的addGhost
靜態方法源碼
public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) { if (!(view.getParent() instanceof ViewGroup)) { throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup"); } // 獲取 ViewGroupOverlay ViewGroupOverlay overlay = viewGroup.getOverlay(); ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup; GhostView ghostView = view.mGhostView; int previousRefCount = 0; if (ghostView != null) { View oldParent = (View) ghostView.getParent(); ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent(); if (oldGrandParent != overlayViewGroup) { previousRefCount = ghostView.mReferences; oldGrandParent.removeView(oldParent); ghostView = null; } } if (ghostView == null) { if (matrix == null) { matrix = new Matrix(); calculateMatrix(view, viewGroup, matrix); } // 創建GhostView ghostView = new GhostView(view); ghostView.setMatrix(matrix); FrameLayout parent = new FrameLayout(view.getContext()); parent.setClipChildren(false); copySize(viewGroup, parent); // 設置GhostView的大小 copySize(viewGroup, ghostView); // 將ghostView添加到瞭parent中 parent.addView(ghostView); ArrayList<View> tempViews = new ArrayList<View>(); int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews); // 將parent添加到瞭ViewGroupOverlay中 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost); ghostView.mReferences = previousRefCount; } else if (matrix != null) { ghostView.setMatrix(matrix); } ghostView.mReferences++; return ghostView; }
可見內部的實現最終將GhostView
添加到瞭ViewGroupOverlay
(遮罩層)裡
配合GhostView
,同時也解決瞭ViewGroupOverlay
會將view
從parent
中remove
的問題(即可同時在ViewGroupOverlay
和原來的parent
中繪制)
我們來看看一個簡單的demo
class GhostViewActivity : AppCompatActivity() { private lateinit var binding: ActivityGhostViewBinding private var ghostView: View? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityGhostViewBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) binding.btClick.setOnClickListener { // 配合動畫看看效果 TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation()) binding.llOverlayContainer.translationX += 100 } binding.btClick2.setOnClickListener { val ghostView = ghostView ?: return@setOnClickListener // 測試一下ghostView的setVisibility方法效果 if (ghostView.isVisible) { ghostView.visibility = View.INVISIBLE } else { ghostView.visibility = View.VISIBLE } (binding.view1.parent as ViewGroup).invalidate() } binding.view1.post { // 創建一個GhostView添加到window.decorView的ViewGroupOverlay中 ghostView = addGhost(binding.view1, window.decorView as ViewGroup) } } // 我們無法直接使用GhostView,隻能臨時使用反射看看效果 private fun addGhost(view: View, viewGroup: ViewGroup): View { val ghostViewClass = Class.forName("android.view.GhostView") val addGhostMethod: Method = ghostViewClass.getMethod( "addGhost", View::class.java, ViewGroup::class.java, Matrix::class.java ) return addGhostMethod.invoke(null, view, viewGroup, null) as View } }
效果圖如下:
可見使用GhostView
並通過setVisibility
方法,實現的效果是 既可以在window.decorView
的ViewGroupOverlay
中繪制,也可以在原來的parent
中繪制
那怎麼同時繪制呢?
隻需要在addGhost
之後強制設置view
的setTransitionVisibility
為View.VISIBLE
即可
binding.view1.post { ghostView = addGhost(binding.view1, window.decorView as ViewGroup) // android 10 之前setTransitionVisibility是hide方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { binding.view1.setTransitionVisibility(View.VISIBLE) (binding.view1.parent as ViewGroup).invalidate() } }
效果圖如下:
Activity的共享元素源碼分析
好的,上面的準備工作做完瞭之後,下面我們來真正的分析Activity
的共享元素源碼
我們先以ActivityA
打開ActivityB
為例
先是調用ActivityOptions.makeSceneTransitionAnimation
創建包含共享元素的ActivityOptions
對象
//android.app.ActivityOptions類的源碼 public class ActivityOptions { ... public static ActivityOptions makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements) { ActivityOptions opts = new ActivityOptions(); // activity.mExitTransitionListener是SharedElementCallback對象 makeSceneTransitionAnimation(activity, activity.getWindow(), opts, activity.mExitTransitionListener, sharedElements); return opts; } }
其中activity
的mExitTransitionListener
是SharedElementCallback
對象,默認值是SharedElementCallback.NULL_CALLBACK
,使用的是默認實現;可以調用Activity
的setExitSharedElementCallback
方法設置這個對象, 但是大多數情況下用默認的即可
下面我們來簡單介紹下SharedElementCallback
的一些回調在什麼情況下觸發
public abstract class SharedElementCallback { ... static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() { }; /** * 共享元素 開始幀準備好瞭 觸發 * @param sharedElementNames 共享元素名稱 * @param sharedElements 共享元素view,並且已經將開始幀的屬性應用到view裡瞭 * @param sharedElementSnapshots 調用SharedElementCallback.onCreateSnapshotView方法創建的快照 **/ public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * 共享元素 結束幀準備好瞭 觸發 * @param sharedElementNames 共享元素名稱 * @param sharedElements 共享元素view,並且已經將結束幀的屬性應用到view裡瞭 * @param sharedElementSnapshots 調用SharedElementCallback.onCreateSnapshotView方法創建的快照 * 註意:跟onSharedElementStart方法的sharedElementSnapshots參數是同一個對象 */ public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * 比如在ActivityA存在,而在ActivityB不存在的共享元素 回調 * @param rejectedSharedElements 在ActivityB中不存在的共享元素 **/ public void onRejectSharedElements(List<View> rejectedSharedElements) {} /** * 需要做動畫的共享元素映射關系準備好之後 回調 * @param names 支持的所有共享元素名稱(是ActivityA打開ActivityB時傳過來的所有共享元素名稱) * @param sharedElements 需要做動畫的共享元素名稱及view的對應關系 * 註意:比如ActivityA打開ActivityB,對於ActivityA中的回調 names和sharedElements的大小基本上是一樣的,ActivityB中的回調就可能會不一樣 **/ public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {} /** * 將共享元素 view 生成 bitmap 保存在Parcelable中,最終這個Parcelable會保存在sharedElementBundle中 * 如果是ActivityA打開ActivityB, 則會把sharedElementBundle傳給ActivityB **/ public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) { ... } /** * 根據snapshot反過來創建view * 如果是ActivityA打開ActivityB, ActivityB接收到Parcelable對象後,在適當的時候會調用這個方法創建出view對象 **/ public View onCreateSnapshotView(Context context, Parcelable snapshot) { ... } /** * 當共享元素和sharedElementBundle對象都已經傳第給對方的時候觸發(表明接下來可以準備執行過場動畫瞭) * 比如: ActivityA 打開 ActivityB, ActivityA調用完onCaptureSharedElementSnapshot將信息保存在sharedElementBundle中,然後傳給ActivityB,這個時候ActivityA 和 ActivityB的SharedElementCallback都會觸發onSharedElementsArrived方法 **/ public void onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener) { listener.onSharedElementsReady(); } }
SharedElementCallback
的每個回調方法的大致意思是這樣的
接下來我門繼續往下看源碼 makeSceneTransitionAnimation
//android.app.ActivityOptions類的源碼 public class ActivityOptions { ... static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window, ActivityOptions opts, SharedElementCallback callback, Pair<View, String>[] sharedElements) { // activity的window一定要添加Window.FEATURE_ACTIVITY_TRANSITIONS特征 if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { opts.mAnimationType = ANIM_DEFAULT; return null; } opts.mAnimationType = ANIM_SCENE_TRANSITION; ArrayList<String> names = new ArrayList<String>(); ArrayList<View> views = new ArrayList<View>(); if (sharedElements != null) { for (int i = 0; i < sharedElements.length; i++) { Pair<View, String> sharedElement = sharedElements[i]; String sharedElementName = sharedElement.second; if (sharedElementName == null) { throw new IllegalArgumentException("Shared element name must not be null"); } names.add(sharedElementName); View view = sharedElement.first; if (view == null) { throw new IllegalArgumentException("Shared element must not be null"); } views.add(sharedElement.first); } } //創建ActivityA退出時的過場動畫核心類 ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window, callback, names, names, views, false); //註意 這個opts保存瞭ActivityA的exit對象,到時候會傳給ActivityB的EnterTransitionCoordinator對象 opts.mTransitionReceiver = exit; // 支持的共享元素名稱 opts.mSharedElementNames = names; // 是否是返回 opts.mIsReturning = (activity == null); if (activity == null) { opts.mExitCoordinatorIndex = -1; } else { // 將exit添加到mActivityTransitionState對象中,然後由ActivityTransitionState對象管理和調用exit對象裡的方法 opts.mExitCoordinatorIndex = activity.mActivityTransitionState.addExitTransitionCoordinator(exit); } return exit; } }
接下來我們來看看ExitTransitionCoordinator
這個類的構造函數幹瞭啥
// android.app.ActivityTransitionCoordinator源碼 abstract class ActivityTransitionCoordinator extends ResultReceiver { ... public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, SharedElementCallback listener, boolean isReturning) { super(new Handler()); mWindow = window; // activity裡的SharedElementCallback對象 mListener = listener; // 支持的所有共享元素名稱 // 比如ActivityA打開ActivityB,則是makeSceneTransitionAnimation方法傳過來的共享元素名稱 mAllSharedElementNames = allSharedElementNames; // 是否是返回 mIsReturning = isReturning; } } // android.app.ExitTransitionCoordinator源碼 public class ExitTransitionCoordinator extends ActivityTransitionCoordinator { public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { super(window, names, listener, isReturning); // viewsReady主要有以下作用 // 1. 準備好需要執行動畫的共享元素,並排序 保存在mSharedElementNames和mSharedElements中 // 2. 準備好需要做退出動畫的非共享元素,保存在mTransitioningViews中 // 3. 這裡會觸發 SharedElementCallback的onMapSharedElements回調 viewsReady(mapSharedElements(accepted, mapped)); // 將mTransitioningViews中的不在屏幕內的非共享元素剔除掉 stripOffscreenViews(); mIsBackgroundReady = !isReturning; mExitCallbacks = exitCallbacks; } }
這裡比較重要的方法就是viewsReady
方法,核心作用就是我上面說的
// android.app.ActivityTransitionCoordinator源碼 protected void viewsReady(ArrayMap<String, View> sharedElements) { // 剔除掉不在mAllSharedElementNames中共享元素 sharedElements.retainAll(mAllSharedElementNames); if (mListener != null) { // 執行SharedElementCallback的onMapSharedElements回調 mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); } // 共享元素排序 setSharedElements(sharedElements); if (getViewsTransition() != null && mTransitioningViews != null) { ViewGroup decorView = getDecor(); if (decorView != null) { // 遍歷decorView收集非共享元素 decorView.captureTransitioningViews(mTransitioningViews); } // 移除掉其中的共享元素 mTransitioningViews.removeAll(mSharedElements); } setEpicenter(); }
準備好ActivityOptions
參數後,就可以調用startActivity(Intent intent, @Nullable Bundle options)
方法瞭,然後就會調用到activity
的cancelInputsAndStartExitTransition
方法
// android.app.Activity源碼 private void cancelInputsAndStartExitTransition(Bundle options) { final View decor = mWindow != null ? mWindow.peekDecorView() : null; if (decor != null) { decor.cancelPendingInputEvents(); } if (options != null) { // 開始處理ActivityA的退場動畫 mActivityTransitionState.startExitOutTransition(this, options); } }
// android.app.ActivityTransitionState源碼 public void startExitOutTransition(Activity activity, Bundle options) { mEnterTransitionCoordinator = null; if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) || mExitTransitionCoordinators == null) { return; } ActivityOptions activityOptions = new ActivityOptions(options); if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { int key = activityOptions.getExitCoordinatorKey(); int index = mExitTransitionCoordinators.indexOfKey(key); if (index >= 0) { mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get(); mExitTransitionCoordinators.removeAt(index); if (mCalledExitCoordinator != null) { mExitingFrom = mCalledExitCoordinator.getAcceptedNames(); mExitingTo = mCalledExitCoordinator.getMappedNames(); mExitingToView = mCalledExitCoordinator.copyMappedViews(); // 調用ExitTransitionCoordinator的startExit mCalledExitCoordinator.startExit(); } } } }
這裡startExitOutTransition
裡面就會調用ExitTransitionCoordinator
的startExit
方法
// android.app.ExitTransitionCoordinator源碼 public void startExit() { if (!mIsExitStarted) { backgroundAnimatorComplete(); mIsExitStarted = true; pauseInput(); ViewGroup decorView = getDecor(); if (decorView != null) { decorView.suppressLayout(true); } // 將共享元素用GhostView包裹,然後添加的Activity的decorView的OverlayView中 moveSharedElementsToOverlay(); startTransition(this::beginTransitions); } }
這裡的moveSharedElementsToOverlay
方法比較重要,會使用到最開始介紹的GhostView
和OverlayView
,目的是將共享元素繪制到最頂層
然後開始執行beginTransitions
方法
// android.app.ExitTransitionCoordinator源碼 private void beginTransitions() { // 獲取共享元素的過渡動畫類Transition,可以通過window.setSharedElementExitTransition方法設置 // 一般不需要設置 有默認值 Transition sharedElementTransition = getSharedElementExitTransition(); // 獲取非共享元素的過渡動畫類Transition,也可以通過window.setExitTransition方法設置 Transition viewsTransition = getExitTransition(); // 將sharedElementTransition和viewsTransition合並成一個 TransitionSet Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); ViewGroup decorView = getDecor(); if (transition != null && decorView != null) { setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); if (viewsTransition != null) { setTransitioningViewsVisiblity(View.VISIBLE, false); } // 開始采集開始幀和結束幀,執行過度動畫 TransitionManager.beginDelayedTransition(decorView, transition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (viewsTransition != null) { setTransitioningViewsVisiblity(View.INVISIBLE, false); } decorView.invalidate(); } else { transitionStarted(); } }
這裡在TransitionManager.beginDelayedTransition
的前後都有屌用setGhostVisibility
和scheduleGhostVisibilityChange
方法,是為瞭采集前後幀的屬性,執行過度動畫,采集完成之後,會顯示GhostView
,而隱藏原來parent裡的共享元素view
上面的sharedElementTransition
和viewsTransition
都添加瞭監聽器,在動畫結束之後分別調用sharedElementTransitionComplete
和viewsTransitionComplete
方法
// android.app.ExitTransitionCoordinator源碼 @Override protected void sharedElementTransitionComplete() { // 這裡就會采集共享元素當前的屬性(大小、位置等),會觸發SharedElementCallback.onCaptureSharedElementSnapshot方法 mSharedElementBundle = mExitSharedElementBundle == null ? captureSharedElementState() : captureExitSharedElementsState(); super.sharedElementTransitionComplete(); } // android.app.ActivityTransitionCoordinator源碼 protected void viewsTransitionComplete() { mViewsTransitionComplete = true; startInputWhenTransitionsComplete(); }
然後在startInputWhenTransitionsComplete
方法裡會調用onTransitionsComplete
方法,最終會調用notifyComplete
方法
// android.app.ExitTransitionCoordinator源碼 protected boolean isReadyToNotify() { // 1. 調用完sharedElementTransitionComplete後,mSharedElementBundle不為null // 2. mResultReceiver是在ActivityB創建完EnterTransitionCoordinator之後,發送MSG_SET_REMOTE_RECEIVER消息 將EnterTransitionCoordinator傳給ActivityA之後不為null // 3. 看構造函數,一開始就為true return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; } protected void notifyComplete() { if (isReadyToNotify()) { if (!mSharedElementNotified) { mSharedElementNotified = true; // 延遲發送一個MSG_CANCEL消息,清空各種狀態等 delayCancel(); if (!mActivity.isTopOfTask()) { // mResultReceiver是ActivityB的EnterTransitionCoordinator對象 mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null); } if (mListener == null) { mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); notifyExitComplete(); } else { final ResultReceiver resultReceiver = mResultReceiver; final Bundle sharedElementBundle = mSharedElementBundle; // 觸發SharedElementCallback.onSharedElementsArrived mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, new OnSharedElementsReadyListener() { @Override public void onSharedElementsReady() { // 發送MSG_TAKE_SHARED_ELEMENTS,將共享元素的sharedElementBundle信息傳遞給ActivityB resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElementBundle); notifyExitComplete(); } }); } } else { notifyExitComplete(); } } }
這裡的notifyComplete
會在特定的條件下不斷觸發,一旦isReadyToNotify
為true
,就會執行方法裡的邏輯
這裡可能比較關心的是resultReceiver
到底是什麼對象,是怎麼賦值的???(這裡在接下來講到ActivityB的時候會介紹到)
ActivityA
的流程暫時到這裡就沒發走下去瞭
接下來我們來看看ActivityB
, 當打開瞭ActivityB
的時候會執行Activity
的performStart
方法
// android.app.Activity源碼 final void performStart(String reason) { dispatchActivityPreStarted(); // getActivityOptions() 獲取到的是在上面ActivityA中創建的ActivityOptions對象 // 裡面有支持的所有的共享元素名稱、ActivityA的ExitTransitionCoordinator對象、返回標志等信息 mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason); ... mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); }
然後就進入到ActivityTransitionState
的enterReady
方法
// android.app.ActivityTransitionState源碼 public void enterReady(Activity activity) { if (mEnterActivityOptions == null || mIsEnterTriggered) { return; } mIsEnterTriggered = true; mHasExited = false; // 獲取ActivityA傳過來的所有共享元素名稱 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); // 獲取ActivityA的ExitTransitionCoordinator ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); // 獲取返回標志 final boolean isReturning = mEnterActivityOptions.isReturning(); if (isReturning) { restoreExitedViews(); activity.getWindow().getDecorView().setVisibility(View.VISIBLE); } // 創建EnterTransitionCoordinator,保存resultReceiver、sharedElementNames等對象 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(), mEnterActivityOptions.isCrossTask()); if (mEnterActivityOptions.isCrossTask()) { mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); } if (!mIsEnterPostponed) { // 是否推遲執行動畫,配合postponeEnterTransition方法使用 startEnter(); } }
上面的mIsEnterPostponed
,默認值是false,可以通過postponeEnterTransition
和 startPostponedEnterTransition
控制什麼時候執行動畫,這個不是重點,我們忽略
我們先來看看EnterTransitionCoordinator
的構造函數
// android.app.EnterTransitionCoordinator源碼 EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) { super(activity.getWindow(), sharedElementNames, getListener(activity, isReturning && !isCrossTask), isReturning); mActivity = activity; mIsCrossTask = isCrossTask; // 保存ActivityA的ExitTransitionCoordinator對象到mResultReceiver中 setResultReceiver(resultReceiver); // 這裡會將ActivityB的window背景設置成透明 prepareEnter(); // 構造resultReceiverBundle,保存EnterTransitionCoordinator對象 Bundle resultReceiverBundle = new Bundle(); resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); // 發送MSG_SET_REMOTE_RECEIVER消息,將EnterTransitionCoordinator對象傳遞給ActivityA mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); ... }
這個時候ActivityA
那邊就接收到瞭ActivityB
的EnterTransitionCoordinator
對象
接下來我門看看ActivityA
是怎麼接收MSG_SET_REMOTE_RECEIVER
消息的
// android.app.ExitTransitionCoordinator 源碼 @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_SET_REMOTE_RECEIVER: stopCancel(); // 將`ActivityB`的`EnterTransitionCoordinator`對象保存到mResultReceiver對象中 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); if (mIsCanceled) { mResultReceiver.send(MSG_CANCEL, null); mResultReceiver = null; } else { //又會觸發notifyComplete(),這個時候isReadyToNotify就為true瞭,就會執行notifyComplete裡的代碼 notifyComplete(); } break; ... } }
這個時候ActivityA
的共享元素動畫的核心邏輯就基本已經走完瞭
接下來繼續看ActivityB
的邏輯,接來下會執行startEnter
方法
// android.app.ActivityTransitionState源碼 private void startEnter() { if (mEnterTransitionCoordinator.isReturning()) { // 這個為false if (mExitingToView != null) { mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, mExitingToView); } else { mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); } } else { // 會執行這個邏輯 mEnterTransitionCoordinator.namedViewsReady(null, null); mPendingExitNames = null; } mExitingFrom = null; mExitingTo = null; mExitingToView = null; mEnterActivityOptions = null; }
也就是說接下來會觸發EnterTransitionCoordinator
的namedViewsReady
方法, 然後就會觸發viewsReady
方法
// android.app.EnterTransitionCoordinator源碼 public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { triggerViewsReady(mapNamedElements(accepted, localNames)); } // android.app.EnterTransitionCoordinator源碼 private void triggerViewsReady(final ArrayMap<String, View> sharedElements) { if (mAreViewsReady) { return; } mAreViewsReady = true; final ViewGroup decor = getDecor(); // Ensure the views have been laid out before capturing the views -- we need the epicenter. if (decor == null || (decor.isAttachedToWindow() && (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) { viewsReady(sharedElements); } else { mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> { mViewsReadyListener = null; // 觸發viewsReady viewsReady(sharedElements); }); decor.invalidate(); } }
EnterTransitionCoordinator
的viewsReady
代碼邏輯 跟 ExitTransitionCoordinator
的差不多,準備好共享元素和非共享元素等,
// android.app.EnterTransitionCoordinator源碼 @Override protected void viewsReady(ArrayMap<String, View> sharedElements) { // 準備好共享元素和非共享元素 super.viewsReady(sharedElements); mIsReadyForTransition = true; // 隱藏共享元素 hideViews(mSharedElements); Transition viewsTransition = getViewsTransition(); if (viewsTransition != null && mTransitioningViews != null) { // 將mTransitioningViews當作target設置到viewsTransition中 removeExcludedViews(viewsTransition, mTransitioningViews); // 剔除掉mTransitioningViews中不在屏幕中的view stripOffscreenViews(); // 隱藏非共享元素 hideViews(mTransitioningViews); } if (mIsReturning) { sendSharedElementDestination(); } else { // 將共享元素用GhostView包裹,然後添加到ActivityB的decorView的OverlayView中 moveSharedElementsToOverlay(); } if (mSharedElementsBundle != null) { onTakeSharedElements(); } }
一般情況下,這個時候mSharedElementsBundle
為null,所以不會走onTakeSharedElements
方法
這裡的mSharedElementsBundle
對象是在ActivityA的notifyComplete
中發送的MSG_TAKE_SHARED_ELEMENTS
消息傳過來的
// android.app.EnterTransitionCoordinator源碼 @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_TAKE_SHARED_ELEMENTS: if (!mIsCanceled) { mSharedElementsBundle = resultData; onTakeSharedElements(); } break; ... } }
可見當ActivityB
接收到MSG_TAKE_SHARED_ELEMENTS
消息,賦值完mSharedElementsBundle
之後,也會執行onTakeSharedElements
方法
接下來我們來看看onTakeSharedElements
方法
// android.app.EnterTransitionCoordinator源碼 private void onTakeSharedElements() { if (!mIsReadyForTransition || mSharedElementsBundle == null) { return; } final Bundle sharedElementState = mSharedElementsBundle; mSharedElementsBundle = null; OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() { @Override public void onSharedElementsReady() { final View decorView = getDecor(); if (decorView != null) { OneShotPreDrawListener.add(decorView, false, () -> { startTransition(() -> { startSharedElementTransition(sharedElementState); }); }); decorView.invalidate(); } } }; if (mListener == null) { listener.onSharedElementsReady(); } else { // 觸發SharedElementCallback.onSharedElementsArrived回調 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener); } }
接下來就會執行startTransition
方法,然後執行startSharedElementTransition
方法,開始執行ActivityB
的動畫瞭
// android.app.EnterTransitionCoordinator源碼 private void startSharedElementTransition(Bundle sharedElementState) { ViewGroup decorView = getDecor(); if (decorView == null) { return; } // Remove rejected shared elements ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); // 過濾出ActivityA存在,ActivityB不存在的共享元素 rejectedNames.removeAll(mSharedElementNames); // 根據ActivityA傳過來的共享元素sharedElementState信息,創建快照view對象 // 這裡會觸發SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); if (mListener != null) { // 觸發SharedElementCallback.onRejectSharedElements方法 mListener.onRejectSharedElements(rejectedSnapshots); } removeNullViews(rejectedSnapshots); // 執行漸隱的退出動畫 startRejectedAnimations(rejectedSnapshots); // 開始創建共享元素的快照view // 這裡會再觸發一遍SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); // 顯示共享元素 showViews(mSharedElements, true); // 添加OnPreDrawListener,在下一幀觸發SharedElementCallback.onSharedElementEnd回調 scheduleSetSharedElementEnd(sharedElementSnapshots); // 設置共享元素設置到動畫的開始位置,並返回在ActivityB佈局中的原始的狀態(即結束位置) // 這裡會觸發SharedElementCallback.onSharedElementStart回調 ArrayList<SharedElementOriginalState> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); pauseInput(); // 然後就開始采集開始幀和結束幀,執行過度動畫 Transition transition = beginTransition(decorView, startEnterTransition, startSharedElementTransition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (startEnterTransition) { // 添加監聽器,動畫結束的時候,將window的背景恢復成不透明等 startEnterTransition(transition); } // 將共享元素設置到結束的位置(為瞭TransitionManager能采集到結束幀的值) setOriginalSharedElementState(mSharedElements, originalImageViewState); if (mResultReceiver != null) { // We can't trust that the view will disappear on the same frame that the shared // element appears here. Assure that we get at least 2 frames for double-buffering. decorView.postOnAnimation(new Runnable() { int mAnimations; @Override public void run() { if (mAnimations++ < MIN_ANIMATION_FRAMES) { View decorView = getDecor(); if (decorView != null) { decorView.postOnAnimation(this); } } else if (mResultReceiver != null) { mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); mResultReceiver = null; // all done sending messages. } } }); } }
接下來看一下beginTransition
方法
// android.app.EnterTransitionCoordinator源碼 private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition, boolean startSharedElementTransition) { Transition sharedElementTransition = null; if (startSharedElementTransition) { if (!mSharedElementNames.isEmpty()) { // 獲取共享元素的過渡動畫類Transition,可以通過window.setSharedElementEnterTransition方法設置 // 一般不需要設置 有默認值 sharedElementTransition = configureTransition(getSharedElementTransition(), false); } ... } Transition viewsTransition = null; if (startEnterTransition) { mIsViewsTransitionStarted = true; if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { // 獲取非共享元素的過渡動畫類Transition,可以通過window.setEnterTransition方法設置 // 一般不需要設置 有默認值 viewsTransition = configureTransition(getViewsTransition(), true); } ... // 合並成TransitionSet 對象 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); if (transition != null) { transition.addListener(new ContinueTransitionListener()); if (startEnterTransition) { setTransitioningViewsVisiblity(View.INVISIBLE, false); } // 開始采集開始幀和結束幀,執行過度動畫 TransitionManager.beginDelayedTransition(decorView, transition); if (startEnterTransition) { setTransitioningViewsVisiblity(View.VISIBLE, false); } decorView.invalidate(); } else { transitionStarted(); } return transition; }
到瞭這裡,就會真正的開始執行 ActivityB
的共享元素和非共享元素的進場動畫
當動畫執行結束之後就會觸發 onTransitionsComplete
方法
// android.app.EnterTransitionCoordinator源碼 @Override protected void onTransitionsComplete() { // 將共享元素和GhostView從decorView的OverlayView中remove掉 moveSharedElementsFromOverlay(); final ViewGroup decorView = getDecor(); if (decorView != null) { decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); Window window = getWindow(); if (window != null && mReplacedBackground == decorView.getBackground()) { window.setBackgroundDrawable(null); } } if (mOnTransitionComplete != null) { mOnTransitionComplete.run(); mOnTransitionComplete = null; } }
用非常簡單點的話總結共享元素流程是:
- ActivityA先執行退場動畫
- ActivityA將共享元素的結束位置等屬性傳遞給ActivityB
- ActivityB加載自己的佈局,在onStart生命周期左右去找到共享元素 先定位到ActivityA的結束位置
- ActivityB開始執行過度動畫,過渡到自己佈局中的位置
這就是 從ActivityA打開ActivityB的共享元素動畫過程的核心源碼分析過程
ActivityB返回ActivityA
既然是返回,首先肯定是要調用ActivityB
的finishAfterTransition
方法
// android.app.Activity 源碼 public void finishAfterTransition() { if (!mActivityTransitionState.startExitBackTransition(this)) { finish(); } }
這裡就會調用ActivityTransitionState
的startExitBackTransition
方法
// android.app.ActivityTransitionState源碼 public boolean startExitBackTransition(final Activity activity) { // 獲取打開ActivityB時 傳過來的共享元素名稱 ArrayList<String> pendingExitNames = getPendingExitNames(); if (pendingExitNames == null || mCalledExitCoordinator != null) { return false; } else { if (!mHasExited) { mHasExited = true; Transition enterViewsTransition = null; ViewGroup decor = null; boolean delayExitBack = false; ... // 創建ExitTransitionCoordinator對象 mReturnExitCoordinator = new ExitTransitionCoordinator(activity, activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames, null, null, true); if (enterViewsTransition != null && decor != null) { enterViewsTransition.resume(decor); } if (delayExitBack && decor != null) { final ViewGroup finalDecor = decor; // 在下一幀調用startExit方法 OneShotPreDrawListener.add(decor, () -> { if (mReturnExitCoordinator != null) { mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); } }); } else { mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); } } return true; } }
這個方法首先會獲取到需要執行退場動畫的共享元素(由ActivityA
打開ActivityB
時傳過來的),然後會創建ExitTransitionCoordinator
對象,最後調用startExit
執行ActivityB
的退場邏輯
我們繼續看看ExitTransitionCoordinator
的構造方法,雖然在上面在分析ActivityA
打開ActivityB
時已經看過瞭這個構造方法,但ActivityB
返回ActivityA
時有點不一樣,accepted
和mapped
參數為null
,isReturning
參數為true
// android.app.ExitTransitionCoordinator源碼 public ExitTransitionCoordinator(Activity activity, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { super(window, names, listener, isReturning); // viewsReady方法跟上面介紹的一樣,主要是mapSharedElements不一樣瞭 viewsReady(mapSharedElements(accepted, mapped)); // 剔除掉mTransitioningViews中不在屏幕內的非共享元素 stripOffscreenViews(); mIsBackgroundReady = !isReturning; mActivity = activity; } // android.app.ActivityTransitionCoordinator源碼 protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews) { ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); if (accepted != null) { for (int i = 0; i < accepted.size(); i++) { sharedElements.put(accepted.get(i), localViews.get(i)); } } else { ViewGroup decorView = getDecor(); if (decorView != null) { // 遍歷整個ActivityB的所有view,找到所有設置瞭transitionName屬性的view decorView.findNamedViews(sharedElements); } } return sharedElements; }
這裡由於accepted
和mapped
參數為null
,所以會遍歷整個decorView
上的所有view
,找到所有設置瞭transitionName
屬性的view
,保存到sharedElements
然後viewsReady
就會根據自己所支持的共享元素名稱,從sharedElements
中刪除所有不支持的共享元素,然後對其排序,並保存到mSharedElements
(保存的view
對象)和mSharedElementNames
(保存的是共享元素名稱)中; 同時也會準備好非共享元素view
對象,保存在mTransitioningViews
中
註意viewReady
會觸發SharedElementCallback.onMapSharedElements
回調
結下來就會執行ExitTransitionCoordinator
的startExit
方法
// android.app.ExitTransitionCoordinator源碼 public void startExit(int resultCode, Intent data) { if (!mIsExitStarted) { ... // 這裡又將ActivityB的共享元素用GhostView包裹一下,添加的decorView的OverlayView中 moveSharedElementsToOverlay(); // 將ActivityB的window背景設置成透明 if (decorView != null && decorView.getBackground() == null) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } final boolean targetsM = decorView == null || decorView.getContext() .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M; ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames : mAllSharedElementNames; //這裡創建options對象,保存ExitTransitionCoordinator、sharedElementNames等對象 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, sharedElementNames, resultCode, data); // 這裡會將ActivityB改成透明的activity,同時會將options對象傳給ActivityA mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { @Override public void onTranslucentConversionComplete(boolean drawComplete) { if (!mIsCanceled) { fadeOutBackground(); } } }, options); startTransition(new Runnable() { @Override public void run() { startExitTransition(); } }); } }
這個方法的主要作用是
- 使用
GhostView
將共享元素view
添加到最頂層decorView
的OverlayView
中 - 然後創建一個
ActivityOptions
對象,把ActivityB
的ExitTransitionCoordinator
對象和支持的共享元素集合對象傳遞給ActivityA
- 將ActivityB改成透明背景
然後就會執行startExitTransition
方法
// android.app.ExitTransitionCoordinator源碼 private void startExitTransition() { // 獲取ActivityB的非共享元素退場的過渡動畫Transition對象 // 最終會調用getReturnTransition方法獲取Transition對象 Transition transition = getExitTransition(); ViewGroup decorView = getDecor(); if (transition != null && decorView != null && mTransitioningViews != null) { setTransitioningViewsVisiblity(View.VISIBLE, false); // 開始執行非共享元素的退場動畫 TransitionManager.beginDelayedTransition(decorView, transition); setTransitioningViewsVisiblity(View.INVISIBLE, false); decorView.invalidate(); } else { transitionStarted(); } }
看到這裡我們就知道瞭,這裡會單獨先執行非共享元素的退場動畫
ActivityB
的退場流程暫時就走到這裡瞭,結下來就需要ActivityA
的配和,所以接下來我們來看看ActivityA
的進場邏輯
ActivityA
進場時,會調用performStart
方法
// android.app.Activity 源碼 final void performStart(String reason) { dispatchActivityPreStarted(); // 這裡的getActivityOptions()獲取到的就是上面說的`ActivityB`傳過來的對象 mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason); ... // ActivityA準備執行進場邏輯 mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); } // android.app.ActivityTransitionState 源碼 public void enterReady(Activity activity) { // mEnterActivityOptions對象就是`ActivityB`傳過來的對象 if (mEnterActivityOptions == null || mIsEnterTriggered) { return; } mIsEnterTriggered = true; mHasExited = false; // 共享元素名稱 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); // ActivityB的ExitTransitionCoordinator對象 ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); // 返回標志 true final boolean isReturning = mEnterActivityOptions.isReturning(); if (isReturning) { restoreExitedViews(); activity.getWindow().getDecorView().setVisibility(View.VISIBLE); } // 創建ActivityA的EnterTransitionCoordinator對象 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(), mEnterActivityOptions.isCrossTask()); if (mEnterActivityOptions.isCrossTask()) { mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); } if (!mIsEnterPostponed) { startEnter(); } }
ActivityA
進場時,會在performStart
裡獲取並保存ActivityB
傳過來的對象,然後創建EnterTransitionCoordinator
進場動畫實現的核心類,然後調用startEnter方法
// android.app.ActivityTransitionState 源碼 private void startEnter() { if (mEnterTransitionCoordinator.isReturning()) { // 這裡的mExitingFrom、mExitingTo、mExitingToView是ActivityA打開ActivityB的時候保存下載的對象 // 所以一般情況下都不為null if (mExitingToView != null) { mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, mExitingToView); } else { mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); } } else { mEnterTransitionCoordinator.namedViewsReady(null, null); mPendingExitNames = null; } mExitingFrom = null; mExitingTo = null; mExitingToView = null; mEnterActivityOptions = null; }
接下來就會執行EnterTransitionCoordinator
的viewInstancesReady
方法
// android.app.EnterTransitionCoordinator 源碼 public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, ArrayList<View> localViews) { boolean remap = false; for (int i = 0; i < localViews.size(); i++) { View view = localViews.get(i); // view的TransitionName屬性有沒有發生變化,或者view對象沒有AttachedToWindow if (!TextUtils.equals(view.getTransitionName(), localNames.get(i)) || !view.isAttachedToWindow()) { remap = true; break; } } if (remap) {// 如果發生瞭變化,則會調用mapNamedElements遍歷decorView找到所有設置瞭TransitionName的view triggerViewsReady(mapNamedElements(accepted, localNames)); } else { // 一般會執行這個else triggerViewsReady(mapSharedElements(accepted, localViews)); } }
接下來就會執行 triggerViewsReady
,裡面就會調用viewsReady
方法,viewsReady
在上面介紹過,唯一有點不一樣的是 這裡的mIsReturning
是true
, 所以會執行sendSharedElementDestination
方法
// android.app.EnterTransitionCoordinator源碼 @Override protected void viewsReady(ArrayMap<String, View> sharedElements) { // 準備好共享元素和非共享元素 super.viewsReady(sharedElements); mIsReadyForTransition = true; // 隱藏共享元素 hideViews(mSharedElements); Transition viewsTransition = getViewsTransition(); if (viewsTransition != null && mTransitioningViews != null) { // 將mTransitioningViews當作target設置到viewsTransition中 removeExcludedViews(viewsTransition, mTransitioningViews); // 剔除掉mTransitioningViews中不在屏幕中的view stripOffscreenViews(); // 隱藏非共享元素 hideViews(mTransitioningViews); } if (mIsReturning) { sendSharedElementDestination(); } else { moveSharedElementsToOverlay(); } if (mSharedElementsBundle != null) { onTakeSharedElements(); } }
// android.app.EnterTransitionCoordinator源碼 private void sendSharedElementDestination() { boolean allReady; final View decorView = getDecor(); if (allowOverlappingTransitions() && getEnterViewsTransition() != null) { allReady = false; } else if (decorView == null) { allReady = true; } else { allReady = !decorView.isLayoutRequested(); if (allReady) { for (int i = 0; i < mSharedElements.size(); i++) { if (mSharedElements.get(i).isLayoutRequested()) { allReady = false; break; } } } } if (allReady) { // 捕獲共享元素當前的狀態, 會觸發SharedElementCallback.onCaptureSharedElementSnapshot方法 Bundle state = captureSharedElementState(); // 將共享元素view 添加的最頂層(decorView的OverlayView中) moveSharedElementsToOverlay(); // 給ActivityB發送MSG_SHARED_ELEMENT_DESTINATION,將共享元素的狀態傳給ActivityB mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else if (decorView != null) { OneShotPreDrawListener.add(decorView, () -> { if (mResultReceiver != null) { Bundle state = captureSharedElementState(); moveSharedElementsToOverlay(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } }); } if (allowOverlappingTransitions()) { // 執行非共享元素的進場動畫 startEnterTransitionOnly(); } }
sendSharedElementDestination
方法主要有以下三個作用
- 獲取
ActivityA
當前共享元素的狀態 傳給ActivityB
,當作過度動畫結束位置的狀態(即結束幀) - 將共享元素添加到最頂層(decorView的OverlayView中)
- 給
ActivityB
發送MSG_SHARED_ELEMENT_DESTINATION
消息傳遞state
- 優先開始執行
ActivityA
的非共享元素的進場動畫
到這裡ActivityA
的邏輯暫時告一段落,接下來我們來看看ActivityB
接收到MSG_SHARED_ELEMENT_DESTINATION
時幹瞭些什麼
// android.app.ExitTransitionCoordinator @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { ... case MSG_SHARED_ELEMENT_DESTINATION: // 保存ActivityA傳過來的共享元素狀態 mExitSharedElementBundle = resultData; // 準備執行共享元素退出動畫 sharedElementExitBack(); break; ... } }
接下來我們來看看sharedElementExitBack
方法
// android.app.ExitTransitionCoordinator private void sharedElementExitBack() { final ViewGroup decorView = getDecor(); if (decorView != null) { decorView.suppressLayout(true); } if (decorView != null && mExitSharedElementBundle != null && !mExitSharedElementBundle.isEmpty() && !mSharedElements.isEmpty() && getSharedElementTransition() != null) { startTransition(new Runnable() { public void run() { // 會執行這個方法 startSharedElementExit(decorView); } }); } else { sharedElementTransitionComplete(); } }
接下來就會執行startSharedElementExit
方法
// android.app.ExitTransitionCoordinator private void startSharedElementExit(final ViewGroup decorView) { // 獲取共享元素的過度動畫的Transition對象,裡面最終會調用`getSharedElementReturnTransition`方法獲取該對象 Transition transition = getSharedElementExitTransition(); transition.addListener(new TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); if (isViewsTransitionComplete()) { delayCancel(); } } }); // 根據ActivityA傳過來的狀態,創建快照view對象 // 這裡會觸發SharedElementCallback.onCreateSnapshotView方法 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames); OneShotPreDrawListener.add(decorView, () -> { // 在下一幀觸發,將共享元素的屬性設置到開始狀態(ActivityA中共享元素的狀態) // 這裡會觸發SharedElementCallback.onSharedElementStart回調 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); }); setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); if (mListener != null) { // 先觸發SharedElementCallback.onSharedElementEnd回調 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots); } // 采集開始幀和結束幀,並執行動畫 TransitionManager.beginDelayedTransition(decorView, transition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); decorView.invalidate(); }
看到上面的方法你可能會發現,先觸發瞭onSharedElementEnd
方法,然後再觸發onSharedElementStart
,這可能是因為ActivityB
返回到ActivityA
時, google
工程師定義為是從結束狀態返回到開始狀態吧,即ActivityB
的狀態為結束狀態,ActivityA
的狀態為開始狀態
至於setGhostVisibility
和scheduleGhostVisibilityChange
主要的作用是為TransitionManager
采集開始幀和結束幀執行動畫用的
到這裡ActivityB
就開始執行共享元素的退出動畫瞭
當ActivityB
共享元素動畫執行結束之後,就會調用sharedElementTransitionComplete
方法,然後就會調用notifyComplete
方法
@Override protected void sharedElementTransitionComplete() { // 這裡又會獲取ActivityB共享元素的狀態(之後會傳給ActivityA) // 可能會觸發ActivityB的SharedElementCallback.onCaptureSharedElementSnapshot回調 mSharedElementBundle = mExitSharedElementBundle == null ? captureSharedElementState() : captureExitSharedElementsState(); super.sharedElementTransitionComplete(); }
這裡為什麼要再一次獲取ActivityB
的共享元素的狀態,因為需要傳給ActivityA
, 然後ActivityA
再根據條件判斷 共享元素的狀態是否又發生瞭變化,然後交給ActivityA
自己去執行共享元素動畫
至於最後會執行notifyComplete
,源碼就沒什麼好看的瞭,上面也都介紹過,這裡面主要是給ActivityA
發送瞭MSG_TAKE_SHARED_ELEMENTS
消息,將ActivityB
的共享元素的狀態對象(mSharedElementBundle
)傳遞給ActivityA
到這裡ActivityB
退場動畫基本上就結束瞭,至於最後的狀態清空等處理 我們就不看瞭
接下來我們繼續看ActivityA
接收到MSG_TAKE_SHARED_ELEMENTS
消息後做瞭什麼
@Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_TAKE_SHARED_ELEMENTS: if (!mIsCanceled) { // 保存共享元素狀態對象 mSharedElementsBundle = resultData; // 準備執行共享元素動畫 onTakeSharedElements(); } break; ... } }
結下來就會執行onTakeSharedElements
方法,這些方法的源碼上面都介紹過,我就不在貼出來瞭,這裡面會觸發SharedElementCallback.onSharedElementsArrived
回調,然後執行startSharedElementTransition
// android.app.EnterTransitionCoordinator源碼 private void startSharedElementTransition(Bundle sharedElementState) { ViewGroup decorView = getDecor(); if (decorView == null) { return; } // Remove rejected shared elements ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); // 過濾出ActivityB存在,ActivityA不存在的共享元素 rejectedNames.removeAll(mSharedElementNames); // 根據ActivityB傳過來的共享元素sharedElementState信息,創建快照view對象 // 這裡會觸發SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); if (mListener != null) { // 觸發SharedElementCallback.onRejectSharedElements方法 mListener.onRejectSharedElements(rejectedSnapshots); } removeNullViews(rejectedSnapshots); // 執行漸隱的退出動畫 startRejectedAnimations(rejectedSnapshots); // 開始創建共享元素的快照view // 這裡會再觸發一遍SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); // 顯示共享元素 showViews(mSharedElements, true); // 添加OnPreDrawListener,在下一幀觸發SharedElementCallback.onSharedElementEnd回調 scheduleSetSharedElementEnd(sharedElementSnapshots); // 設置共享元素設置到動畫的開始位置,並返回在ActivityA佈局中的原始的狀態(即結束位置) // SharedElementCallback.onSharedElementStart回調 ArrayList<SharedElementOriginalState> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); pauseInput(); // 然後就開始采集開始幀和結束幀,執行過度動畫 Transition transition = beginTransition(decorView, startEnterTransition, startSharedElementTransition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (startEnterTransition) {// 這裡為false,不會執行, 因為非共享元素動畫執行單獨執行瞭 startEnterTransition(transition); } // 將共享元素設置到結束的位置(為瞭TransitionManager能采集到結束幀的值) setOriginalSharedElementState(mSharedElements, originalImageViewState); if (mResultReceiver != null) { // We can't trust that the view will disappear on the same frame that the shared // element appears here. Assure that we get at least 2 frames for double-buffering. decorView.postOnAnimation(new Runnable() { int mAnimations; @Override public void run() { if (mAnimations++ < MIN_ANIMATION_FRAMES) { View decorView = getDecor(); if (decorView != null) { decorView.postOnAnimation(this); } } else if (mResultReceiver != null) { mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); mResultReceiver = null; // all done sending messages. } } }); } }
這裡要特別說明的是
- 這裡沒有執行
ActivityA
的非共享元素的進場動畫,因為在之前已經優先調用瞭非共享元素的進場動畫 - 雖然這裡調用瞭
ActivityA
的共享元素動畫,但是基本上並不會創建動畫對象去執行,因為ActivityB
傳過來的狀態 跟ActivityA
當前的狀態是一模一樣的,除非你在某種情況下並在執行動畫之前 強制改變ActivityA
的當前狀態;所以你所看到的共享元素的退場動畫其實是ActivityB
的共享元素退場動畫,而不是ActivityA
的
最後ActivityA
的共享元素動畫結束之後 會就調用onTransitionsComplete
(不需要執行動畫,就會立馬觸發),將ActivityA
的共享元素view從從decorView的ViewGroupOverlay中remove掉
到這裡由ActivityB
返回ActivityA
的退場動畫到這裡基本上就結束瞭,至於最後的cancel
等狀態清理就不介紹瞭
到這裡我也用非常簡單點的大白話總結一下ActivityB
返回ActivityA
的退場動畫:
- 將
ActivityB
的window背景設置成透明, 並執行非共享元素的退場動畫 - 返回到ActivityA時,將會執行到performStart方法,並執行非共享元素的進場動畫
ActivityB
接收到ActivityA
傳過來的共享元素狀態,開始執行共享元素的退場動畫ActivityA
接收到ActivityB
的共享元素狀態,繼續執行共享元素動畫(但由於兩個狀態沒有變化,所以並不會執行動畫,會立馬直接動畫結束的回調)
SharedElementCallback回調總結
最後我們在總結以下SharedElementCallback
回調的順序,因為你有可能會自定義這個類 做一些特定的邏輯處理
當是ActivityA打開ActivityB時
ActivityA: ==Exit, onMapSharedElements ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityB: ==Enter, onMapSharedElements ActivityA: ==Exit, onSharedElementsArrived ActivityB: ==Enter, onSharedElementsArrived ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onRejectSharedElements ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onSharedElementStart ActivityB: ==Enter, onSharedElementEnd
當是ActivityB返回到ActivityA時
ActivityB: ==Enter, onMapSharedElements ActivityA: ==Exit, onMapSharedElements ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onSharedElementEnd ActivityB: ==Enter, onSharedElementStart ActivityB: ==Enter, onSharedElementsArrived ActivityA: ==Exit, onSharedElementsArrived ActivityA: ==Exit, onRejectSharedElements ActivityA: ==Exit, onCreateSnapshotView ActivityA: ==Exit, onSharedElementStart ActivityA: ==Exit, onSharedElementEnd
好瞭,到這裡 我所要介紹的內容已經結束瞭,上面的源碼是針對Android30和Android31分析的(我在不同的時間段用不同的筆記本寫的,所以上面的源碼有的是Android30的源碼,有的是Android31的源碼)
最後再附上一張Activity共享元素動畫的全程時序圖
點擊下載打開
以上就是Android Activity共享元素動畫示例解析的詳細內容,更多關於Android Activity共享元素動畫的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android非異常情況下的Activity生命周期分析
- 關於Android觸摸事件分發的原理詳析
- Android中的Launch Mode詳情
- Android ViewStub使用方法學習
- Android Fragment的具體使用方式詳解