所谓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

  1. 界说两个布局场景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>
    
  2. 运用场景并履行动画

    // 创立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
        }
    }
    
  3. 履行效果如下:

transition_anim1.gif

经过上面的效果能够看出,切换的一会儿会立马变成指定场景的一切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.png

上面这些都是体系供给的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++
        }
    }
}

其效果图如下:

transition_anim2.gif

你或许会有些疑问,为什么上面将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)
        }
    }
}

效果图如下:

阿灿 2022-09-09 22.18.31.2022-09-12 22_21_50.gif

这儿仅有需求留意的是,假如是增加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
    }
}

效果图如下:

阿灿 2022-09-09 22.18.29.2022-09-12 22_20_33.gif

可见运用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 >= Build.VERSION_CODES.Q) {
        binding.view1.setTransitionVisibility(View.VISIBLE)
        (binding.view1.parent as ViewGroup).invalidate()
    }
}

效果图如下:

ghostview.jpg

Activity的同享元素源码剖析

好的,上面的预备工作做完了之后,下面咱们来真实的剖析Activity的同享元素源码

咱们先以ActivityA翻开ActivityB为例

先是调用ActivityOptions.makeSceneTransitionAnimation创立包括同享元素的ActivityOptions方针

//android.app.ActivityOptions类的源码
public class ActivityOptions {
	...
	public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
	        Pair<View, String>... sharedElements) {
	    ActivityOptions opts = new ActivityOptions();
	    // activity.mExitTransitionListener是SharedElementCallback方针
	    makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
	            activity.mExitTransitionListener, sharedElements);
	    return opts;
	}
}

其间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;
    }
}

用十分简略点的话总结同享元素流程是:

    1. ActivityA先履行离场动画
    1. ActivityA将同享元素的完毕方位等特点传递给ActivityB
    1. ActivityB加载自己的布局,在onStart生命周期左右去找到同享元素 先定位到ActivityA的完毕方位
    1. 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<String, View> sharedElements) {
    // 预备好同享元素和非同享元素
    super.viewsReady(sharedElements);
    mIsReadyForTransition = true;
    // 躲藏同享元素
    hideViews(mSharedElements);
    Transition viewsTransition = getViewsTransition();
    if (viewsTransition != null && mTransitioningViews != null) {
    	// 将mTransitioningViews当作target设置到viewsTransition中
        removeExcludedViews(viewsTransition, mTransitioningViews);
        // 剔除去mTransitioningViews中不在屏幕中的view
        stripOffscreenViews();
        // 躲藏非同享元素
        hideViews(mTransitioningViews);
    }
    if (mIsReturning) {
        sendSharedElementDestination();
    } else {
        moveSharedElementsToOverlay();
    }
    if (mSharedElementsBundle != null) {
        onTakeSharedElements();
    }
}
// android.app.EnterTransitionCoordinator源码
private void sendSharedElementDestination() {
    boolean allReady;
    final View decorView = getDecor();
    if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
        allReady = false;
    } else if (decorView == null) {
        allReady = true;
    } else {
        allReady = !decorView.isLayoutRequested();
        if (allReady) {
            for (int i = 0; i < mSharedElements.size(); i++) {
                if (mSharedElements.get(i).isLayoutRequested()) {
                    allReady = false;
                    break;
                }
            }
        }
    }
    if (allReady) {
    	// 捕获同享元素当时的状况, 会触发SharedElementCallback.onCaptureSharedElementSnapshot办法
        Bundle state = captureSharedElementState();
        // 将同享元素view 增加的最顶层(decorView的OverlayView中)
        moveSharedElementsToOverlay();
        // 给ActivityB发送MSG_SHARED_ELEMENT_DESTINATION,将同享元素的状况传给ActivityB
        mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
    } else if (decorView != null) {
        OneShotPreDrawListener.add(decorView, () -> {
            if (mResultReceiver != null) {
                Bundle state = captureSharedElementState();
                moveSharedElementsToOverlay();
                mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
            }
        });
    }
    if (allowOverlappingTransitions()) {
    	// 履行非同享元素的出场动画
        startEnterTransitionOnly();
    }
}

sendSharedElementDestination办法首要有以下三个效果

  • 获取ActivityA当时同享元素的状况 传给ActivityB,当作过度动画完毕方位的状况(即完毕帧)
  • 将同享元素增加到最顶层(decorView的OverlayView中)
  • ActivityB发送MSG_SHARED_ELEMENT_DESTINATION音讯传递state
  • 优先开端履行ActivityA的非同享元素的出场动画

到这儿ActivityA的逻辑暂时告一段落,接下来咱们来看看ActivityB接纳到MSG_SHARED_ELEMENT_DESTINATION时干了些什么

// android.app.ExitTransitionCoordinator
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        ...
        case MSG_SHARED_ELEMENT_DESTINATION:
            // 保存ActivityA传过来的同享元素状况
            mExitSharedElementBundle = resultData;
            // 预备履行同享元素退出动画
            sharedElementExitBack();
            break;
        ...
    }
}

接下来咱们来看看sharedElementExitBack办法

// android.app.ExitTransitionCoordinator
private void sharedElementExitBack() {
    final ViewGroup decorView = getDecor();
    if (decorView != null) {
        decorView.suppressLayout(true);
    }
    if (decorView != null && mExitSharedElementBundle != null &&
            !mExitSharedElementBundle.isEmpty() &&
            !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
        startTransition(new Runnable() {
            public void run() {
            	// 会履行这个办法
                startSharedElementExit(decorView);
            }
        });
    } else {
        sharedElementTransitionComplete();
    }
}

接下来就会履行startSharedElementExit办法

// android.app.ExitTransitionCoordinator
private void startSharedElementExit(final ViewGroup decorView) {
    // 获取同享元素的过度动画的Transition方针,里边终究会调用`getSharedElementReturnTransition`办法获取该方针
    Transition transition = getSharedElementExitTransition();
    transition.addListener(new TransitionListenerAdapter() {
        @Override
        public void onTransitionEnd(Transition transition) {
            transition.removeListener(this);
            if (isViewsTransitionComplete()) {
                delayCancel();
            }
        }
    });
    // 依据ActivityA传过来的状况,创立快照view方针
    // 这儿会触发SharedElementCallback.onCreateSnapshotView办法
    final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
            mSharedElementNames);
    OneShotPreDrawListener.add(decorView, () -> {
    	// 鄙人一帧触发,将同享元素的特点设置到开端状况(ActivityA中同享元素的状况)
    	// 这儿会触发SharedElementCallback.onSharedElementStart回调
        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
    });
    setGhostVisibility(View.INVISIBLE);
    scheduleGhostVisibilityChange(View.INVISIBLE);
    if (mListener != null) {
    	// 先触发SharedElementCallback.onSharedElementEnd回调
        mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
                sharedElementSnapshots);
    }
    // 收集开端帧和完毕帧,并履行动画
    TransitionManager.beginDelayedTransition(decorView, transition);
    scheduleGhostVisibilityChange(View.VISIBLE);
    setGhostVisibility(View.VISIBLE);
    decorView.invalidate();
}

看到上面的办法你或许会发现,先触发了onSharedElementEnd办法,然后再触发onSharedElementStart,这或许是由于ActivityB回来到ActivityA时, google工程师界说为是从完毕状况回来到开端状况吧,即ActivityB的状况为完毕状况,ActivityA的状况为开端状况

至于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的离场动画:

    1. ActivityB的window布景设置成通明, 并履行非同享元素的离场动画
    1. 回来到ActivityA时,将会履行到performStart办法,并履行非同享元素的出场动画
    1. ActivityB接纳到ActivityA传过来的同享元素状况,开端履行同享元素的离场动画
    1. 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

share_callback1.png

当是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

share_callback2.png

好了,到这儿 我所要介绍的内容现已完毕了,上面的源码是针对Android30和Android31剖析的(我在不同的时间段用不同的笔记本写的,所以上面的源码有的是Android30的源码,有的是Android31的源码)

终究再附上一张Activity同享元素动画的全程时序图

sxt_share_element.png