Android Activity共享元素動畫示例解析

正文

所謂Activity共享元素動畫,就是從ActivityA跳轉到ActivityB 通過控制某些元素(View)從ActivityA開始幀的位置跳轉到ActivityB 結束幀的位置,應用過度動畫

Activity的共享元素動畫,其動畫核心是使用的Transition記錄共享元素的開始幀、結束幀,然後使用TransitionManager過度動畫管理類調用beginDelayedTransition方法 應用過度動畫

註意:Android5.0才開始支持共享元素動畫

所以咱們先介紹一下TransitionManager的一些基礎知識

TransitionManager介紹

TransitionManagerAndroid5.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子類 的 實現效果 和 其在captureStartValuescaptureEndValues中記錄的屬性,然後在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()方法,裡面會觸發XYTransitioncaptureStartValues方法記錄開始幀(記錄的translationX為100),同時TransitionManager會添加OnPreDrawListener, 在屏幕下一幀到來觸發view去繪制的時候,會先調用OnPreDrawListeneronPreDraw() 方法,裡面又會觸發XYTransitioncaptureEndValues方法記錄結束幀的屬性(記錄的translationX為0), 然後應用動畫 改變view的屬性,最後交給view去繪制

上面講瞭這麼多,下面我們簡單分析一下TransitionManager.beginDelayedTransition方法的源碼

首先是TransitionManagerbeginDelayedTransition方法

// 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);
    }
}

裡面代碼比較少;我們主要看sceneChangeSetupsceneChangeRunTransition方法的實現

// 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方法就會觸發TransitioncaptureStartValues 方法

接下來我們來看看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;
    }
};

TransitionplayTransition沒啥好看的,至此TransitionManagerbeginDelayedTransition源碼分析到這裡

上面源碼裡你可能也看到瞭Transition可以設置目標視圖,應用過渡動畫, 主要是通過addTarget方法實現的,如果沒有設置目標視圖,默認就會遍歷所有的children應用在所有的視圖上

OverlayView和ViewGroupOverlay

OverlayViewViewGroupOverlayActivity共享元素動畫實現裡比較重要的一個類,所以就單獨的介紹一下

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方法直接獲取OverlayViewViewGroupOverlay對象, 然後我們就可以在上面添加一些裝飾等效果

OverlayView隻支持添加drawable

ViewGroupOverlay支持添加Viewdrawable

註意:如果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共享元素動畫實現裡比較重要的一個類,所以就單獨的介紹一下

它的作用是在不改變viewparent的情況下,將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裡,則默認會調用viewsetTransitionVisibility方法 將view設置成在parent中不可見, 在GhostView裡可見;調用GhostViewsetVisibility方法設置 要麼在GhostView中可見,要麼在parent中可見

系統內部是使用GhostView.addGhost靜態方法添加GhostView

我們來看看添加GhostViewaddGhost靜態方法源碼

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會將viewparentremove的問題(即可同時在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.decorViewViewGroupOverlay中繪制,也可以在原來的parent中繪制

那怎麼同時繪制呢?

隻需要在addGhost之後強制設置viewsetTransitionVisibilityView.VISIBLE即可

binding.view1.post {
    ghostView = addGhost(binding.view1, window.decorView as ViewGroup)
    // android 10 之前setTransitionVisibility是hide方法
    if (Build.VERSION.SDK_INT &gt;= 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&lt;View, String&gt;... sharedElements) {
	    ActivityOptions opts = new ActivityOptions();
	    // activity.mExitTransitionListener是SharedElementCallback對象
	    makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
	            activity.mExitTransitionListener, sharedElements);
	    return opts;
	}
}

其中activitymExitTransitionListenerSharedElementCallback對象,默認值是SharedElementCallback.NULL_CALLBACK,使用的是默認實現;可以調用ActivitysetExitSharedElementCallback方法設置這個對象, 但是大多數情況下用默認的即可

下面我們來簡單介紹下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)方法瞭,然後就會調用到activitycancelInputsAndStartExitTransition方法

// 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裡面就會調用ExitTransitionCoordinatorstartExit方法

// 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方法比較重要,會使用到最開始介紹的GhostViewOverlayView ,目的是將共享元素繪制到最頂層

然後開始執行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的前後都有屌用setGhostVisibilityscheduleGhostVisibilityChange方法,是為瞭采集前後幀的屬性,執行過度動畫,采集完成之後,會顯示GhostView,而隱藏原來parent裡的共享元素view

上面的sharedElementTransitionviewsTransition都添加瞭監聽器,在動畫結束之後分別調用sharedElementTransitionCompleteviewsTransitionComplete方法

// 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會在特定的條件下不斷觸發,一旦isReadyToNotifytrue,就會執行方法裡的邏輯

這裡可能比較關心的是resultReceiver到底是什麼對象,是怎麼賦值的???(這裡在接下來講到ActivityB的時候會介紹到)

ActivityA的流程暫時到這裡就沒發走下去瞭

接下來我們來看看ActivityB, 當打開瞭ActivityB的時候會執行ActivityperformStart方法

// 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();
}

然後就進入到ActivityTransitionStateenterReady方法

// 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,可以通過postponeEnterTransitionstartPostponedEnterTransition控制什麼時候執行動畫,這個不是重點,我們忽略

我們先來看看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那邊就接收到瞭ActivityBEnterTransitionCoordinator對象

接下來我門看看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;
}

也就是說接下來會觸發EnterTransitionCoordinatornamedViewsReady方法, 然後就會觸發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();
    }
}

EnterTransitionCoordinatorviewsReady代碼邏輯 跟 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

既然是返回,首先肯定是要調用ActivityBfinishAfterTransition方法

// android.app.Activity 源碼
public void finishAfterTransition() {
    if (!mActivityTransitionState.startExitBackTransition(this)) {
        finish();
    }
}

這裡就會調用ActivityTransitionStatestartExitBackTransition方法

// 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時有點不一樣,acceptedmapped參數為nullisReturning參數為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;
}

這裡由於acceptedmapped參數為null,所以會遍歷整個decorView上的所有view,找到所有設置瞭transitionName屬性的view,保存到sharedElements

然後viewsReady就會根據自己所支持的共享元素名稱,從sharedElements中刪除所有不支持的共享元素,然後對其排序,並保存到mSharedElements(保存的view對象)和mSharedElementNames(保存的是共享元素名稱)中; 同時也會準備好非共享元素view對象,保存在mTransitioningViews

註意viewReady會觸發SharedElementCallback.onMapSharedElements回調

結下來就會執行ExitTransitionCoordinatorstartExit方法

// 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添加到最頂層decorViewOverlayView
  • 然後創建一個ActivityOptions 對象,把ActivityBExitTransitionCoordinator對象和支持的共享元素集合對象傳遞給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;
}

接下來就會執行EnterTransitionCoordinatorviewInstancesReady方法

// 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在上面介紹過,唯一有點不一樣的是 這裡的mIsReturningtrue, 所以會執行sendSharedElementDestination方法

// android.app.EnterTransitionCoordinator源碼
@Override
protected void viewsReady(ArrayMap&lt;String, View&gt; sharedElements) {
    // 準備好共享元素和非共享元素
    super.viewsReady(sharedElements);
    mIsReadyForTransition = true;
    // 隱藏共享元素
    hideViews(mSharedElements);
    Transition viewsTransition = getViewsTransition();
    if (viewsTransition != null &amp;&amp; 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的狀態為開始狀態

至於setGhostVisibilityscheduleGhostVisibilityChange主要的作用是為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其它相關文章!

推薦閱讀: