Android Activity共享元素动画示例解析

目录
  • 正文
  • TransitionManager介绍
    • Scene(场景)
    • 生成场景
    • Transition(过渡)
  • OverlayView和ViewGroupOverlay
  • GhostView
  • Activity的共享元素源码分析
    • 我们先以ActivityA打开ActivityB为例
    • ActivityB返回ActivityA
    • SharedElementCallback回调总结

正文

所谓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共享元素动画的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Activity启动流程刨析

    目录 前言 一.Binder的基本理解 二.Activity启动的双向IPC过程 三.AMS服务注册 前言 上篇文章写到 Service 的启动过程: 相对来说Activity的启动过程比Service的启动过程更为复杂,其一Activity的生命周期方法比Service多,其二Activity具有启动模式和返回栈: 写本文的目的在于更清晰的梳理Activity的启动过程,加强自己的内功修炼,力在以最简单的方式让大家理解,跟大家一起学习 一.Binder的基本理解 Activity的启动有多次I

  • Android Activity状态与操作探究

    目录 知晓当前是在哪一个Activiy 随时随地退出程序 启动Activity的最佳写法 知晓当前是在哪一个Activiy 创建一个BaseActivity类,继承AppCompatActivity类.重写onCreate方法 open class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

  • Android开发Activity的生命周期详解

    目录 前言 典型情况下的生命周期分析 前言 Android生命周期分为两部分: (1)典型情况下的生命周期. (2)异常情况下的生命周期. 典型情况下的生命周期分析 图1 Activity的生命周期图解 图2 Activity生命周期的金字塔图 (1)典型情况下的生命周期指在有用户参与的情况下,Activity所经过的生命周期的改变,正常情况下,Activity的常用生命周期有以下几种情况: onCreate():Activity启动后第一个被调用的函数,常用来进行Activity的初始化,如创

  • Android Activity View加载与绘制流程深入刨析源码

    1.App的启动流程,从startActivity到Activity被创建. 这个流程主要是ActivityThread和ActivityManagerService之间通过binder进行通信来完成. ActivityThread可以拿到AMS 的BinderProxy.AMS可以拿到ActivityThread的BinderProxy ApplicationThread.这样双方就可以互相通讯了. 当ApplicationThread 接收到AMS的Binder调用后,会通过handler机

  • Android10 App启动Activity源码分析

    目录 正文 ActivityThread的main方法 Application Context对象 LaunchActivityItem ClientLifecycleManager ClientTransaction TransactionExecutor executeLifecycleState方法 正文 上一篇: Android 10 App启动分析之进程创建篇(一) 上一篇文章,我们探讨了App启动过程中进程创建及初始化的流程,这篇文章我们接着上篇的内容,继续探讨App的Applica

  • Android Activity共享元素动画示例解析

    目录 正文 TransitionManager介绍 Scene(场景) 生成场景 Transition(过渡) OverlayView和ViewGroupOverlay GhostView Activity的共享元素源码分析 我们先以ActivityA打开ActivityB为例 ActivityB返回ActivityA SharedElementCallback回调总结 正文 所谓Activity共享元素动画,就是从ActivityA跳转到ActivityB 通过控制某些元素(View)从Act

  • Android共享元素动画效果显示问题解决

    目录 bug描述 官方文档 解决流程 总结 bug描述 设计同学想搞一个点击图片item,item内的图片移动到新页面的图片位置的效果,一想就是共享元素就能搞定啊. companion object { fun gotoDetail( context: Activity, dynamicId: String?, jumpComment: Boolean = false, shareElement: Boolean = false, imageView: ImageView? = null, na

  • Android Activity跳转动画效果

    Activity的跳转动画在5.0的时候做了一个重大的突破,下面来看一下吧 1.5.0之前的overridePendingTransition 相信大家对OverridePendingTransition还是很熟悉的,使用方法也是很简单就是 //fade_in是第二个界面的进入动画,fade_out是第一个界面退出动画,不想动画传入0  overridePendingTransition(R.anim.fade_in,R.anim.fade_out); fade_in : <?xml versi

  • Android实现循环平移动画示例

    实现用一张背景图做循环从左往右平移动画. 1.实现两个animation xml文件,一个起始位置在-100%p ,一个在0%p.设置repeat属性为循环,重复. 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolato

  • Android开发之5.0activity跳转时共享元素的使用方法

    在两个activity中的xml文件中编写下面的ImageView,主要的就是transitionName属性必须使用相同的属性. <ImageView android:id="@+id/item_image" android:layout_width="100dp" android:layout_height="140dp" android:layout_margin="10dp" android:transition

  • 详解Android(共享元素)转场动画开发实践

    最近零碎时间一直在研究OpenGL,所以没怎么进行分享,以后可能大部分时间会学习系统底层\NDK\VR\AR等领域,话不多少,今天来分享个小的动画效果. 效果如下 基本知识 其实Android的转场动画由来已久,比如平常开发安卓的时候界面切换 都是右进右出,这样的效果,就是早期的转场动画,在5.0之后安卓官方支持了共享元素的效果,那么问题来了,5.0以后该怎么适配? 准备步骤 定义两个activity,界面跳转是从A到B. ActivityA定义一个控件View,在跳转时传入到Pair里面,详细

  • Android面向单Activity开发示例解析

    目录 正文 总结 正文 记得前一两年很多人都跟风面向单Activity开发,顾名思义,就是整个项目只有一个Activity.一个Activity里面装着N多个Fragment,再给Fragment加上转场动画,效果和多Activity跳转无异.其实想想还比较酷,以前还需要关注多个Acitivity之间的生命周期,现在只需关注一个,但还是需要对Fragment的生命周期进行关注. 其实早在六七年前GitHub上就有单Activity的开源库Fragmentation,后来谷歌也出了一个库Navig

  • Android 使用XML做动画UI的深入解析

    效果: http://www.56.com/u82/v_OTM4MDk5MTk.html第一步: 创建anim文件夹放置动画xml文件在res文件夹下,创建一个anim的子文件夹. 第二步: 加载动画接着在Activity创建一个Animation类,然后使用AnimationUtils类加载动画xml 复制代码 代码如下: Animation animFadein; @Overrideprotected void onCreate(Bundle savedInstanceState) { su

  • Android Activity进出动画三种方法

     Android Activity进出动画三种方法 实现activity的进出场动画总共有3种方式,下面会一一列出,首先给出示例的动画xml文件. 动画的xml文件 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromYDelta="

  • 基于JavaScript的数据结构队列动画实现示例解析

    ###一 摘要 今天给大家介绍一个基于数据结构中的队列的一个动画,在实现这个动画之前呢,还是给大家讲讲,在JavaScript中我们如何实现一个队列. ###二 队列 队列是一种列表,不同的是队列只能在末尾插入元素,在队首删除元素.队列用于存储按顺序排列的数据.先进先出.这点和栈不一样,在栈中,最后入栈的元素反被优先处理.可以将队列想象成银行排队办理业务的人,排队在第一个的人先办理业务,其它人只能排着,直到轮到他们为止. 队列是一种先进先出(FIFO)的数据结构.队列被用在很多地方.比如提交操作

随机推荐