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

手机APP/开发
711
0
0
2023-02-24
标签   Android
目录
  • 正文
  • 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的源码)