所谓Activity
同享元素动画,便是从ActivityA
跳转到ActivityB
经过操控某些元素(View
)从ActivityA
开端帧的方位跳转到ActivityB
完毕帧的方位,运用过度动画
Activity
的同享元素动画,其动画中心是运用的Transition
记载同享元素的开端帧、完毕帧,然后运用TransitionManager
过度动画办理类调用beginDelayedTransition
办法 运用过度动画
留意:Android5.0才开端支撑同享元素动画
所以咱们先介绍一下TransitionManager
的一些基础知识
TransitionManager
介绍
TransitionManager
是 Android5.0
开端供给的一个过渡动画办理类,功能十分强大;其可运用在两个Activity
之间、Fragment
之间、View
之间运用过渡动画
TransitionManager
有两个比较重要的类Scene(场景)
和Transition(过渡)
, 咱们先来介绍一下这两个类
Scene(场景)
望文生义Scene
便是场景的意思,在履行动画之前,咱们需求创立两个场景(场景A和场景B), 其动画履行流程如下:
- 依据开端布局和完毕布局创立两个
Scene
方针(场景A和场景B); 可是 开端布局的场景通常是依据当时布局主动确认的 - 创立一个
Transition
方针以界说所需的动画类型 - 调用
TransitionManager.go(Scene, Transition)
,运用过渡动画运转到指定的场景
生成场景
生成场景有两种办法; 一种是调用静态办法经过布局生成 Scene.getSceneForLayout(sceneRoot, R.layout.scene_a, this)
,一种是直接经过结构办法new Scene(sceneRoot, viewHierarchy)
指定view方针生成
这两种办法其实差不多,第一种经过布局生成的办法在运用的时分会主动inflate
加载布局生成view
方针
用法比较简略;下面咱们来看一下官方的demo
-
界说两个布局场景A和场景B
<!-- res/layout/scene_a.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(a)" /> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(a)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(a)" /> </LinearLayout>
<!-- res/layout/scene_b.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(b)" /> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(b)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(b)" /> </LinearLayout>
-
运用场景并履行动画
// 创立a场景 val aScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_a, this) // 创立b场景 val bScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_b, this) var aSceneFlag = true // 增加点击事情,切换不同的场景 binding.btClick1.setOnClickListener { if (aSceneFlag) { TransitionManager.go(bScene) aSceneFlag = false } else { TransitionManager.go(aScene) aSceneFlag = true } }
-
履行效果如下:
经过上面的效果能够看出,切换的一会儿会立马变成指定场景的一切view(文案全都变了),仅仅运用了开端帧的方位罢了,然后渐渐过渡到完毕帧的方位;
// Scene的enter()办法源码
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// remove掉场景根视图下的一切view(即上一个场景)
getSceneRoot().removeAllViews();
// 增加当时场景的一切view
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
可见切换到指定场景,比方切换到场景b, 会remove掉场景a的一切view,然后增加场景b的一切view
其次;这种办法的两种场景之间的切换动画;是经过id确认两个view之间的对应联系,然后确认view的开端帧和完毕帧 来履行过渡动画;假如没有id对应联系的view(即没有开端帧或完毕帧), 会履行删去动画(默许是渐隐动画)或增加动画(默许是渐显动画)(看源码也能够经过transtionName特点来指定对应联系)
其视图匹配对应联系的源码如下:
// startValues 是开端帧一切方针的特点
// endValues 是完毕帧一切方针的特点
private void matchStartAndEnd(TransitionValuesMaps startValues,
TransitionValuesMaps endValues) {
ArrayMap<View, TransitionValues> unmatchedStart =
new ArrayMap<View, TransitionValues>(startValues.viewValues);
ArrayMap<View, TransitionValues> unmatchedEnd =
new ArrayMap<View, TransitionValues>(endValues.viewValues);
for (int i = 0; i < mMatchOrder.length; i++) {
switch (mMatchOrder[i]) {
case MATCH_INSTANCE:
// 匹配是否相同的方针(能够经过改动view的特点,运用过渡动画)
matchInstances(unmatchedStart, unmatchedEnd);
break;
case MATCH_NAME:
// 匹配transitionName特点是否相同(activity之间便是经过transtionName来匹配的)
matchNames(unmatchedStart, unmatchedEnd,
startValues.nameValues, endValues.nameValues);
break;
case MATCH_ID:
// 匹配view的id是否相同
matchIds(unmatchedStart, unmatchedEnd,
startValues.idValues, endValues.idValues);
break;
case MATCH_ITEM_ID:
// 特殊处理listview的item
matchItemIds(unmatchedStart, unmatchedEnd,
startValues.itemIdValues, endValues.itemIdValues);
break;
}
}
// 增加没有匹配到的方针
addUnmatched(unmatchedStart, unmatchedEnd);
}
可见企图的匹配联系有很多种;能够依据 视图方针自身、视图的id、视图的transitionName特点等匹配对应联系
界说场景比较简略,其实相对比较复杂的是Transition
过度动画
缺点:个人觉得经过创立不同Scene
方针完结动画效果比较费事,需求创立多套布局,后期难以维护;所以一般这种运用TransitionManager.go(bScene)
办法指定Scene
方针的办法根本不常用,一般都是运用TransitionManager.beginDelayedTransition()
办法来完结过渡动画
下面咱们来介绍Transition
,并合作运用TransitionManager.beginDelayedTransition()
办法完结动画效果
Transition(过渡)
望文生义 Transition
是过渡的意思,里边界说了怎样 记载开端帧的特点、记载完毕帧的特点、创立动画或履行动画的逻辑
咱们先看看Transition
源码里比较重要的几个办法
// android.transition.Transition的源码
public abstract class Transition implements Cloneable {
...
// 经过完结这个办法记载view的开端帧的特点
public abstract void captureStartValues(TransitionValues transitionValues);
// 经过完结这个办法记载view的完毕帧的特点
public abstract void captureEndValues(TransitionValues transitionValues);
// 经过记载的开端帧和完毕帧的特点,创立动画
// 默许回来null,即没有动画;需求你自己创立动画方针
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
return null;
}
// 履行动画
// mAnimators 里包括的便是上面createAnimator()办法创立的动画方针
protected void runAnimators() {
if (DBG) {
Log.d(LOG_TAG, "runAnimators() on " + this);
}
start();
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
// Now start every Animator that was previously created for this transition
for (Animator anim : mAnimators) {
if (DBG) {
Log.d(LOG_TAG, " anim: " + anim);
}
if (runningAnimators.containsKey(anim)) {
start();
runAnimator(anim, runningAnimators);
}
}
mAnimators.clear();
end();
}
...
}
假如咱们要自界说Transition
过渡动画的话,一般只需求重写前三个办法即可
当时体系也供给了一套完结的Transition
过渡动画的子类
上面这些都是体系供给的Transition
子类 的 完结效果 和 其在captureStartValues
、captureEndValues
中记载的特点,然后在createAnimator
办法中创立的特点动画 不断改动的特点
当然除了上面的一些类以外,体系还供给了TransitionSet
类,能够指定一组动画;它也是的Transition
子类
其TransitionManager
中的默许动画便是 AutoTransition
, 它是TransitionSet
的子类
public class AutoTransition extends TransitionSet {
public AutoTransition() {
init();
}
public AutoTransition(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
}
可见AutoTransition
包括了 淡出、位移、改动巨细、淡入 等一组效果
下面咱们来自界说一些Transition
,到达一些效果
// 记载和改动translationX、translationY特点
class XYTranslation : Transition() {
override fun captureStartValues(transitionValues: TransitionValues?) {
transitionValues ?: return
transitionValues.values["translationX"] = transitionValues.view.translationX
transitionValues.values["translationY"] = transitionValues.view.translationY
}
override fun captureEndValues(transitionValues: TransitionValues?) {
transitionValues ?: return
transitionValues.values["translationX"] = transitionValues.view.translationX
transitionValues.values["translationY"] = transitionValues.view.translationY
}
override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) return null
val startX = startValues.values["translationX"] as Float
val startY = startValues.values["translationY"] as Float
val endX = endValues.values["translationX"] as Float
val endY = endValues.values["translationY"] as Float
var translationXAnim: Animator? = null
if (startX != endX) {
translationXAnim = ObjectAnimator.ofFloat(endValues.view, "translationX", startX, endX)
}
var translationYAnim: Animator? = null
if (startY != endY) {
translationYAnim = ObjectAnimator.ofFloat(endValues.view, "translationY", startY, endY)
}
return mergeAnimators(translationXAnim, translationYAnim)
}
fun mergeAnimators(animator1: Animator?, animator2: Animator?): Animator? {
return if (animator1 == null) {
animator2
} else if (animator2 == null) {
animator1
} else {
val animatorSet = AnimatorSet()
animatorSet.playTogether(animator1, animator2)
animatorSet
}
}
}
// 记载和改动backgroundColor特点
class BackgroundColorTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues?) {
transitionValues ?: return
val drawable = transitionValues.view.background as? ColorDrawable ?: return
transitionValues.values["backgroundColor"] = drawable.color
}
override fun captureEndValues(transitionValues: TransitionValues?) {
transitionValues ?: return
val drawable = transitionValues.view.background as? ColorDrawable ?: return
transitionValues.values["backgroundColor"] = drawable.color
}
override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) return null
val startColor = (startValues.values["backgroundColor"] as? Int) ?: return null
val endColor = (endValues.values["backgroundColor"] as? Int) ?: return null
if (startColor != endColor) {
return ObjectAnimator.ofArgb(endValues.view, "backgroundColor", startColor, endColor)
}
return super.createAnimator(sceneRoot, startValues, endValues)
}
}
十分简略,上面就自界说了两个XYTranslation
和BackgroundColorTransition
类,完结位移和改动布景颜色的效果
下面咱们合作运用TransitionManager.beginDelayedTransition()
办法,运用XYTranslation
和BackgroundColorTransition
两个动画过渡类,完结效果
<!-- res/layout/activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btClick"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="履行动画"/>
<LinearLayout
android:id="@+id/beginDelayRoot"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="#ffff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:text="Text Line 1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:text="Text Line 2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:text="Text Line 3" />
</LinearLayout>
</LinearLayout>
class TransitionDemoActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
val backgroundColor1 = Color.parseColor("#ffff00")
val backgroundColor2 = Color.parseColor("#00ff00")
var index = 0
binding.btClick.setOnClickListener {
val view1 = binding.beginDelayRoot.getChildAt(0)
val view2 = binding.beginDelayRoot.getChildAt(1)
// 设置开端方位的x偏移量为100px(界说开端帧的特点)
view1.translationX = 100f
view2.translationX = 100f
// 调用beginDelayedTransition 会立马调用 Transition的captureStartValues办法记载开端帧
// 一起会增加一个OnPreDrawListener, 在屏幕刷新的下一帧触发onPreDraw() 办法,然后调用captureEndValues办法记载完毕帧,然后开端履行动画
TransitionManager.beginDelayedTransition(binding.beginDelayRoot, TransitionSet().apply {
// 完结上下移动(由于没有改动view的left特点所以, 所以它没有左右移动效果)
addTransition(ChangeBounds())
// 经过translationX特点完结左右移动
addTransition(XYTranslation())
// 经过backgroundColor特点改动布景颜色
addTransition(BackgroundColorTransition())
})
// 下面开端改动视图的特点(界说完毕帧的特点)
// 将完毕方位x偏移量为0px
view1.translationX = 0f
view2.translationX = 0f
binding.beginDelayRoot.removeView(view1)
binding.beginDelayRoot.removeView(view2)
binding.beginDelayRoot.addView(view1)
binding.beginDelayRoot.addView(view2)
binding.beginDelayRoot.setBackgroundColor(if (index % 2 == 0) backgroundColor2 else backgroundColor1)
index++
}
}
}
其效果图如下:
你或许会有些疑问,为什么上面将translationX设置成100之后,立马又改成了0;这样有什么意义吗??
可见Transition
的运用和自界说也比较简略,一起也能到达一些比较炫酷的效果
请留意,改动view的特点并不会立马重新制作视图,而是在屏幕的下一帧(60fps的话,便是16毫秒一帧)去制作;而在制作下一帧之前调用了TransitionManager.beginDelayedTransition()
办法,里边会触发XYTransition
的captureStartValues
办法记载开端帧(记载的translationX
为100),一起TransitionManager
会增加OnPreDrawListener
, 在屏幕下一帧到来触发view去制作的时分,会先调用OnPreDrawListener
的onPreDraw()
办法,里边又会触发XYTransition
的captureEndValues
办法记载完毕帧的特点(记载的translationX
为0), 然后运用动画 改动view的特点,终究交给view去制作
上面讲了这么多,下面咱们简略剖析一下TransitionManager.beginDelayedTransition
办法的源码
首要是TransitionManager
的beginDelayedTransition
办法
// android.transition.TransitionManager源码
public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
if (Transition.DBG) {
Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
sceneRoot + ", " + transition);
}
sPendingTransitions.add(sceneRoot);
if (transition == null) {
transition = sDefaultTransition;
}
final Transition transitionClone = transition.clone();
sceneChangeSetup(sceneRoot, transitionClone);
Scene.setCurrentScene(sceneRoot, null);
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
里边代码比较少;咱们首要看sceneChangeSetup
和sceneChangeRunTransition
办法的完结
// android.transition.TransitionManager源码
private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
// Capture current values
ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
// 暂停正在运转的动画
runningTransition.pause(sceneRoot);
}
}
if (transition != null) {
// 调用transition.captureValues;
// 其实第二个参数 true或false,标明是开端还是完毕,对应会调用captureStartValues和captureEndValues 办法
transition.captureValues(sceneRoot, true);
}
...
}
咱们来简略看看transition.captureValues
的源码
// android.transition.Transition源码
void captureValues(ViewGroup sceneRoot, boolean start) {
clearValues(start);
// 假如你的 Transition 指定了方针view,就会履行这个if
if ((mTargetIds.size() > 0 || mTargets.size() > 0)
&& (mTargetNames == null || mTargetNames.isEmpty())
&& (mTargetTypes == null || mTargetTypes.isEmpty())) {
for (int i = 0; i < mTargetIds.size(); ++i) {
int id = mTargetIds.get(i);
View view = sceneRoot.findViewById(id);
if (view != null) {
TransitionValues values = new TransitionValues(view);
if (start) {
// 记载开端帧的特点
captureStartValues(values);
} else {
// 记载完毕帧的特点
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 缓存开端帧的特点到mStartValues中
addViewValues(mStartValues, view, values);
} else {
// 缓存完毕帧的特点到mEndValues中
addViewValues(mEndValues, view, values);
}
}
}
for (int i = 0; i < mTargets.size(); ++i) {
View view = mTargets.get(i);
TransitionValues values = new TransitionValues(view);
if (start) {
// 记载开端帧的特点
captureStartValues(values);
} else {
// 记载完毕帧的特点
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 缓存开端帧的特点到mStartValues中
addViewValues(mStartValues, view, values);
} else {
// 缓存完毕帧的特点到mEndValues中
addViewValues(mEndValues, view, values);
}
}
} else {
// 没有指定方针view的情况
captureHierarchy(sceneRoot, start);
}
...
}
private void captureHierarchy(View view, boolean start) {
...
if (view.getParent() instanceof ViewGroup) {
TransitionValues values = new TransitionValues(view);
if (start) {
// 记载开端帧的特点
captureStartValues(values);
} else {
// 记载完毕帧的特点
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 缓存开端帧的特点到mStartValues中
addViewValues(mStartValues, view, values);
} else {
// 缓存完毕帧的特点到mEndValues中
addViewValues(mEndValues, view, values);
}
}
if (view instanceof ViewGroup) {
// 递归遍历一切的children
ViewGroup parent = (ViewGroup) view;
for (int i = 0; i < parent.getChildCount(); ++i) {
captureHierarchy(parent.getChildAt(i), start);
}
}
}
可见sceneChangeSetup
办法就会触发Transition
的captureStartValues
办法
接下来咱们来看看sceneChangeRunTransition
办法
// android.transition.TransitionManager源码
private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
final Transition transition) {
if (transition != null && sceneRoot != null) {
MultiListener listener = new MultiListener(transition, sceneRoot);
sceneRoot.addOnAttachStateChangeListener(listener);
// 增加OnPreDrawListener
sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
}
}
private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
View.OnAttachStateChangeListener {
...
@Override
public boolean onPreDraw() {
removeListeners();
...
// 这儿就会触发captureEndValues办法,记载完毕帧的特点
mTransition.captureValues(mSceneRoot, false);
if (previousRunningTransitions != null) {
for (Transition runningTransition : previousRunningTransitions) {
runningTransition.resume(mSceneRoot);
}
}
// 开端履行动画
// 这儿就会调用 Transition的createAnimator办法 和 runAnimators办法
mTransition.playTransition(mSceneRoot);
return true;
}
};
Transition
的playTransition
没啥美观的,至此TransitionManager
的beginDelayedTransition
源码剖析到这儿
上面源码里你或许也看到了Transition
能够设置方针视图,运用过渡动画, 首要是经过addTarget
办法完结的,假如没有设置方针视图,默许就会遍历一切的children运用在一切的视图上
OverlayView和ViewGroupOverlay
OverlayView
和ViewGroupOverlay
是Activity
同享元素动画完结里比较重要的一个类,所以就独自的介绍一下
OverlayView
是针对View
的一个顶层附加层(即遮罩层),它在View的一切内容制作完结之后 再制作
ViewGroupOverlay
是针对ViewGroup
的,是OverlayView
的子类,它在ViewGroup
的一切内容(包括一切的children)制作完结之后 再制作
// android.view.View源码
public ViewOverlay getOverlay() {
if (mOverlay == null) {
mOverlay = new ViewOverlay(mContext, this);
}
return mOverlay;
}
// android.view.ViewGroup源码
@Override
public ViewGroupOverlay getOverlay() {
if (mOverlay == null) {
mOverlay = new ViewGroupOverlay(mContext, this);
}
return (ViewGroupOverlay) mOverlay;
}
看上面的源码咱们知道,能够直接调用getOverlay
办法直接获取OverlayView
或ViewGroupOverlay
方针, 然后咱们就能够在上面增加一些装饰等效果
OverlayView
只支撑增加drawable
ViewGroupOverlay
支撑增加View
和 drawable
留意:假如View
的parent不为null, 则会主动先把它从parent中remove掉,然后增加到ViewGroupOverlay
中
中心源码如下:
// OverlayViewGroup的add办法源码
public void add(@NonNull View child) {
if (child == null) {
throw new IllegalArgumentException("view must be non-null");
}
if (child.getParent() instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) child.getParent();
...
// 将child从本来的parent中remove掉
parent.removeView(child);
if (parent.getLayoutTransition() != null) {
// LayoutTransition will cause the child to delay removal - cancel it
parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
}
// fail-safe if view is still attached for any reason
if (child.getParent() != null) {
child.mParent = null;
}
}
super.addView(child);
}
用法也十分简略,咱们来看看一个简略的demo
class OverlayActivity : AppCompatActivity() {
private lateinit var binding: ActivityOverlayBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityOverlayBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
addViewToOverlayView()
addDrawableToOverlayView()
binding.btClick.setOnClickListener {
// 测试一下OverlayView的动画
TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation())
binding.llOverlayContainer.translationX += 100
}
}
private fun addViewToOverlayView() {
val view = View(this)
view.layoutParams = LinearLayout.LayoutParams(100, 100)
view.setBackgroundColor(Color.parseColor("#ff00ff"))
// 需求手动调用layout,否则view显现不出来
view.layout(0, 0, 100, 100)
binding.llOverlayContainer.overlay.add(view)
}
private fun addDrawableToOverlayView() {
binding.view2.post {
val drawable = ContextCompat.getDrawable(this, R.mipmap.ic_temp)
// 需求手动调用setBounds,否则drawable显现不出来
drawable!!.setBounds(binding.view2.width / 2, 0, binding.view2.width, binding.view2.height / 2)
binding.view2.overlay.add(drawable)
}
}
}
效果图如下:
这儿仅有需求留意的是,假如是增加view
,需求手动调用layout
布局,否则view
显现不出来;假如增加的是drawable
需求手动调用setBounds
,否则drawable
也显现不出来
GhostView
GhostView
是Activity
同享元素动画完结里比较重要的一个类,所以就独自的介绍一下
它的效果是在不改动view
的parent
的情况下,将view
制作在另一个parent
下
咱们先看看GhostView
的部分源码
public class GhostView extends View {
private final View mView;
private int mReferences;
private boolean mBeingMoved;
private GhostView(View view) {
super(view.getContext());
mView = view;
mView.mGhostView = this;
final ViewGroup parent = (ViewGroup) mView.getParent();
// 这句代码 让mView在本来的parent中躲藏(即不制作视图)
mView.setTransitionVisibility(View.INVISIBLE);
parent.invalidate();
}
@Override
public void setVisibility(@Visibility int visibility) {
super.setVisibility(visibility);
if (mView.mGhostView == this) {
// 假如view在ghostview中制作(可见),则设置在本来的parent不制作(不行见)
// 假如view在ghostview中不制作(不行见),则设置在本来的parent制作(可见)
int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
mView.setTransitionVisibility(inverseVisibility);
}
}
}
看源码得知 假如把View
增加到GhostView
里,则默许会调用view
的setTransitionVisibility
办法 将view
设置成在parent
中不行见, 在GhostView
里可见;调用GhostView
的setVisibility
办法设置 要么在GhostView
中可见,要么在parent
中可见
体系内部是运用GhostView.addGhost
静态办法增加GhostView
的
咱们来看看增加GhostView
的addGhost
静态办法源码
public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
if (!(view.getParent() instanceof ViewGroup)) {
throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
}
// 获取 ViewGroupOverlay
ViewGroupOverlay overlay = viewGroup.getOverlay();
ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
GhostView ghostView = view.mGhostView;
int previousRefCount = 0;
if (ghostView != null) {
View oldParent = (View) ghostView.getParent();
ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
if (oldGrandParent != overlayViewGroup) {
previousRefCount = ghostView.mReferences;
oldGrandParent.removeView(oldParent);
ghostView = null;
}
}
if (ghostView == null) {
if (matrix == null) {
matrix = new Matrix();
calculateMatrix(view, viewGroup, matrix);
}
// 创立GhostView
ghostView = new GhostView(view);
ghostView.setMatrix(matrix);
FrameLayout parent = new FrameLayout(view.getContext());
parent.setClipChildren(false);
copySize(viewGroup, parent);
// 设置GhostView的巨细
copySize(viewGroup, ghostView);
// 将ghostView增加到了parent中
parent.addView(ghostView);
ArrayList<View> tempViews = new ArrayList<View>();
int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
// 将parent增加到了ViewGroupOverlay中
insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
ghostView.mReferences = previousRefCount;
} else if (matrix != null) {
ghostView.setMatrix(matrix);
}
ghostView.mReferences++;
return ghostView;
}
可见内部的完结终究将GhostView
增加到了ViewGroupOverlay
(遮罩层)里
合作GhostView
,一起也处理了ViewGroupOverlay
会将view
从parent
中remove
的问题(即可一起在ViewGroupOverlay
和本来的parent
中制作)
咱们来看看一个简略的demo
class GhostViewActivity : AppCompatActivity() {
private lateinit var binding: ActivityGhostViewBinding
private var ghostView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityGhostViewBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
binding.btClick.setOnClickListener {
// 合作动画看看效果
TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation())
binding.llOverlayContainer.translationX += 100
}
binding.btClick2.setOnClickListener {
val ghostView = ghostView ?: return@setOnClickListener
// 测试一下ghostView的setVisibility办法效果
if (ghostView.isVisible) {
ghostView.visibility = View.INVISIBLE
} else {
ghostView.visibility = View.VISIBLE
}
(binding.view1.parent as ViewGroup).invalidate()
}
binding.view1.post {
// 创立一个GhostView增加到window.decorView的ViewGroupOverlay中
ghostView = addGhost(binding.view1, window.decorView as ViewGroup)
}
}
// 咱们无法直接运用GhostView,只能临时运用反射看看效果
private fun addGhost(view: View, viewGroup: ViewGroup): View {
val ghostViewClass = Class.forName("android.view.GhostView")
val addGhostMethod: Method = ghostViewClass.getMethod(
"addGhost", View::class.java,
ViewGroup::class.java, Matrix::class.java
)
return addGhostMethod.invoke(null, view, viewGroup, null) as View
}
}
效果图如下:
可见运用GhostView
并经过setVisibility
办法,完结的效果是 既能够在window.decorView
的ViewGroupOverlay
中制作,也能够在本来的parent
中制作
那怎样一起制作呢?
只需求在addGhost
之后强制设置view
的setTransitionVisibility
为View.VISIBLE
即可
binding.view1.post {
ghostView = addGhost(binding.view1, window.decorView as ViewGroup)
// android 10 之前setTransitionVisibility是hide办法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
binding.view1.setTransitionVisibility(View.VISIBLE)
(binding.view1.parent as ViewGroup).invalidate()
}
}
效果图如下:
Activity
的同享元素源码剖析
好的,上面的预备工作做完了之后,下面咱们来真实的剖析Activity
的同享元素源码
咱们先以ActivityA
翻开ActivityB
为例
先是调用ActivityOptions.makeSceneTransitionAnimation
创立包括同享元素的ActivityOptions
方针
//android.app.ActivityOptions类的源码
public class ActivityOptions {
...
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
Pair<View, String>... sharedElements) {
ActivityOptions opts = new ActivityOptions();
// activity.mExitTransitionListener是SharedElementCallback方针
makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
activity.mExitTransitionListener, sharedElements);
return opts;
}
}
其间activity
的mExitTransitionListener
是SharedElementCallback
方针,默许值是SharedElementCallback.NULL_CALLBACK
,运用的是默许完结;能够调用Activity
的setExitSharedElementCallback
办法设置这个方针, 可是大多数情况下用默许的即可
下面咱们来简略介绍下SharedElementCallback
的一些回调在什么情况下触发
public abstract class SharedElementCallback {
...
static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() {
};
/**
* 同享元素 开端帧预备好了 触发
* @param sharedElementNames 同享元素称号
* @param sharedElements 同享元素view,并且现已将开端帧的特点运用到view里了
* @param sharedElementSnapshots 调用SharedElementCallback.onCreateSnapshotView办法创立的快照
**/
public void onSharedElementStart(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* 同享元素 完毕帧预备好了 触发
* @param sharedElementNames 同享元素称号
* @param sharedElements 同享元素view,并且现已将完毕帧的特点运用到view里了
* @param sharedElementSnapshots 调用SharedElementCallback.onCreateSnapshotView办法创立的快照
* 留意:跟onSharedElementStart办法的sharedElementSnapshots参数是同一个方针
*/
public void onSharedElementEnd(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {}
/**
* 比方在ActivityA存在,而在ActivityB不存在的同享元素 回调
* @param rejectedSharedElements 在ActivityB中不存在的同享元素
**/
public void onRejectSharedElements(List<View> rejectedSharedElements) {}
/**
* 需求做动画的同享元素映射联系预备好之后 回调
* @param names 支撑的一切同享元素称号(是ActivityA翻开ActivityB时传过来的一切同享元素称号)
* @param sharedElements 需求做动画的同享元素称号及view的对应联系
* 留意:比方ActivityA翻开ActivityB,对于ActivityA中的回调 names和sharedElements的巨细根本上是一样的,ActivityB中的回调就或许会不一样
**/
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}
/**
* 将同享元素 view 生成 bitmap 保存在Parcelable中,终究这个Parcelable会保存在sharedElementBundle中
* 假如是ActivityA翻开ActivityB, 则会把sharedElementBundle传给ActivityB
**/
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
RectF screenBounds) {
...
}
/**
* 依据snapshot反过来创立view
* 假如是ActivityA翻开ActivityB, ActivityB接纳到Parcelable方针后,在恰当的时分会调用这个办法创立出view方针
**/
public View onCreateSnapshotView(Context context, Parcelable snapshot) {
...
}
/**
* 当同享元素和sharedElementBundle方针都现已传第给对方的时分触发(标明接下来能够预备履行过场动画了)
* 比方: ActivityA 翻开 ActivityB, ActivityA调用完onCaptureSharedElementSnapshot将信息保存在sharedElementBundle中,然后传给ActivityB,这个时分ActivityA 和 ActivityB的SharedElementCallback都会触发onSharedElementsArrived办法
**/
public void onSharedElementsArrived(List<String> sharedElementNames,
List<View> sharedElements, OnSharedElementsReadyListener listener) {
listener.onSharedElementsReady();
}
}
SharedElementCallback
的每个回调办法的大致意思是这样的
接下来我门持续往下看源码 makeSceneTransitionAnimation
//android.app.ActivityOptions类的源码
public class ActivityOptions {
...
static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
ActivityOptions opts, SharedElementCallback callback,
Pair<View, String>[] sharedElements) {
// activity的window一定要增加Window.FEATURE_ACTIVITY_TRANSITIONS特征
if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
opts.mAnimationType = ANIM_DEFAULT;
return null;
}
opts.mAnimationType = ANIM_SCENE_TRANSITION;
ArrayList<String> names = new ArrayList<String>();
ArrayList<View> views = new ArrayList<View>();
if (sharedElements != null) {
for (int i = 0; i < sharedElements.length; i++) {
Pair<View, String> sharedElement = sharedElements[i];
String sharedElementName = sharedElement.second;
if (sharedElementName == null) {
throw new IllegalArgumentException("Shared element name must not be null");
}
names.add(sharedElementName);
View view = sharedElement.first;
if (view == null) {
throw new IllegalArgumentException("Shared element must not be null");
}
views.add(sharedElement.first);
}
}
//创立ActivityA退出时的过场动画中心类
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
callback, names, names, views, false);
//留意 这个opts保存了ActivityA的exit方针,到时分会传给ActivityB的EnterTransitionCoordinator方针
opts.mTransitionReceiver = exit;
// 支撑的同享元素称号
opts.mSharedElementNames = names;
// 是否是回来
opts.mIsReturning = (activity == null);
if (activity == null) {
opts.mExitCoordinatorIndex = -1;
} else {
// 将exit增加到mActivityTransitionState方针中,然后由ActivityTransitionState方针办理和调用exit方针里的办法
opts.mExitCoordinatorIndex =
activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
}
return exit;
}
}
接下来咱们来看看ExitTransitionCoordinator
这个类的结构函数干了啥
// android.app.ActivityTransitionCoordinator源码
abstract class ActivityTransitionCoordinator extends ResultReceiver {
...
public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
SharedElementCallback listener, boolean isReturning) {
super(new Handler());
mWindow = window;
// activity里的SharedElementCallback方针
mListener = listener;
// 支撑的一切同享元素称号
// 比方ActivityA翻开ActivityB,则是makeSceneTransitionAnimation办法传过来的同享元素称号
mAllSharedElementNames = allSharedElementNames;
// 是否是回来
mIsReturning = isReturning;
}
}
// android.app.ExitTransitionCoordinator源码
public class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks,
Window window, SharedElementCallback listener, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
super(window, names, listener, isReturning);
// viewsReady首要有以下效果
// 1. 预备好需求履行动画的同享元素,并排序 保存在mSharedElementNames和mSharedElements中
// 2. 预备好需求做退出动画的非同享元素,保存在mTransitioningViews中
// 3. 这儿会触发 SharedElementCallback的onMapSharedElements回调
viewsReady(mapSharedElements(accepted, mapped));
// 将mTransitioningViews中的不在屏幕内的非同享元素剔除去
stripOffscreenViews();
mIsBackgroundReady = !isReturning;
mExitCallbacks = exitCallbacks;
}
}
这儿比较重要的办法便是viewsReady
办法,中心效果便是我上面说的
// android.app.ActivityTransitionCoordinator源码
protected void viewsReady(ArrayMap<String, View> sharedElements) {
// 剔除去不在mAllSharedElementNames中同享元素
sharedElements.retainAll(mAllSharedElementNames);
if (mListener != null) {
// 履行SharedElementCallback的onMapSharedElements回调
mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
}
// 同享元素排序
setSharedElements(sharedElements);
if (getViewsTransition() != null && mTransitioningViews != null) {
ViewGroup decorView = getDecor();
if (decorView != null) {
// 遍历decorView搜集非同享元素
decorView.captureTransitioningViews(mTransitioningViews);
}
// 移除去其间的同享元素
mTransitioningViews.removeAll(mSharedElements);
}
setEpicenter();
}
预备好ActivityOptions
参数后,就能够调用startActivity(Intent intent, @Nullable Bundle options)
办法了,然后就会调用到activity
的cancelInputsAndStartExitTransition
办法
// android.app.Activity源码
private void cancelInputsAndStartExitTransition(Bundle options) {
final View decor = mWindow != null ? mWindow.peekDecorView() : null;
if (decor != null) {
decor.cancelPendingInputEvents();
}
if (options != null) {
// 开端处理ActivityA的离场动画
mActivityTransitionState.startExitOutTransition(this, options);
}
}
// android.app.ActivityTransitionState源码
public void startExitOutTransition(Activity activity, Bundle options) {
mEnterTransitionCoordinator = null;
if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
mExitTransitionCoordinators == null) {
return;
}
ActivityOptions activityOptions = new ActivityOptions(options);
if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
int key = activityOptions.getExitCoordinatorKey();
int index = mExitTransitionCoordinators.indexOfKey(key);
if (index >= 0) {
mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
mExitTransitionCoordinators.removeAt(index);
if (mCalledExitCoordinator != null) {
mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
mExitingTo = mCalledExitCoordinator.getMappedNames();
mExitingToView = mCalledExitCoordinator.copyMappedViews();
// 调用ExitTransitionCoordinator的startExit
mCalledExitCoordinator.startExit();
}
}
}
}
这儿startExitOutTransition
里边就会调用ExitTransitionCoordinator
的startExit
办法
// android.app.ExitTransitionCoordinator源码
public void startExit() {
if (!mIsExitStarted) {
backgroundAnimatorComplete();
mIsExitStarted = true;
pauseInput();
ViewGroup decorView = getDecor();
if (decorView != null) {
decorView.suppressLayout(true);
}
// 将同享元素用GhostView包裹,然后增加的Activity的decorView的OverlayView中
moveSharedElementsToOverlay();
startTransition(this::beginTransitions);
}
}
这儿的moveSharedElementsToOverlay
办法比较重要,会运用到最开端介绍的GhostView
和OverlayView
,意图是将同享元素制作到最顶层
然后开端履行beginTransitions
办法
// android.app.ExitTransitionCoordinator源码
private void beginTransitions() {
// 获取同享元素的过渡动画类Transition,能够经过window.setSharedElementExitTransition办法设置
// 一般不需求设置 有默许值
Transition sharedElementTransition = getSharedElementExitTransition();
// 获取非同享元素的过渡动画类Transition,也能够经过window.setExitTransition办法设置
Transition viewsTransition = getExitTransition();
// 将sharedElementTransition和viewsTransition合并成一个 TransitionSet
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
ViewGroup decorView = getDecor();
if (transition != null && decorView != null) {
setGhostVisibility(View.INVISIBLE);
scheduleGhostVisibilityChange(View.INVISIBLE);
if (viewsTransition != null) {
setTransitioningViewsVisiblity(View.VISIBLE, false);
}
// 开端收集开端帧和完毕帧,履行过度动画
TransitionManager.beginDelayedTransition(decorView, transition);
scheduleGhostVisibilityChange(View.VISIBLE);
setGhostVisibility(View.VISIBLE);
if (viewsTransition != null) {
setTransitioningViewsVisiblity(View.INVISIBLE, false);
}
decorView.invalidate();
} else {
transitionStarted();
}
}
这儿在TransitionManager.beginDelayedTransition
的前后都有屌用setGhostVisibility
和scheduleGhostVisibilityChange
办法,是为了收集前后帧的特点,履行过度动画,收集完结之后,会显现GhostView
,而躲藏本来parent里的同享元素view
上面的sharedElementTransition
和viewsTransition
都增加了监听器,在动画完毕之后别离调用sharedElementTransitionComplete
和viewsTransitionComplete
办法
// android.app.ExitTransitionCoordinator源码
@Override
protected void sharedElementTransitionComplete() {
// 这儿就会收集同享元素当时的特点(巨细、方位等),会触发SharedElementCallback.onCaptureSharedElementSnapshot办法
mSharedElementBundle = mExitSharedElementBundle == null
? captureSharedElementState() : captureExitSharedElementsState();
super.sharedElementTransitionComplete();
}
// android.app.ActivityTransitionCoordinator源码
protected void viewsTransitionComplete() {
mViewsTransitionComplete = true;
startInputWhenTransitionsComplete();
}
然后在startInputWhenTransitionsComplete
办法里会调用onTransitionsComplete
办法,终究会调用notifyComplete
办法
// android.app.ExitTransitionCoordinator源码
protected boolean isReadyToNotify() {
// 1. 调用完sharedElementTransitionComplete后,mSharedElementBundle不为null
// 2. mResultReceiver是在ActivityB创立完EnterTransitionCoordinator之后,发送MSG_SET_REMOTE_RECEIVER音讯 将EnterTransitionCoordinator传给ActivityA之后不为null
// 3. 看结构函数,一开端就为true
return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
}
protected void notifyComplete() {
if (isReadyToNotify()) {
if (!mSharedElementNotified) {
mSharedElementNotified = true;
// 延迟发送一个MSG_CANCEL音讯,清空各种状况等
delayCancel();
if (!mActivity.isTopOfTask()) {
// mResultReceiver是ActivityB的EnterTransitionCoordinator方针
mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null);
}
if (mListener == null) {
mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
notifyExitComplete();
} else {
final ResultReceiver resultReceiver = mResultReceiver;
final Bundle sharedElementBundle = mSharedElementBundle;
// 触发SharedElementCallback.onSharedElementsArrived
mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
new OnSharedElementsReadyListener() {
@Override
public void onSharedElementsReady() {
// 发送MSG_TAKE_SHARED_ELEMENTS,将同享元素的sharedElementBundle信息传递给ActivityB
resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
sharedElementBundle);
notifyExitComplete();
}
});
}
} else {
notifyExitComplete();
}
}
}
这儿的notifyComplete
会在特定的条件下不断触发,一旦isReadyToNotify
为true
,就会履行办法里的逻辑
这儿或许比较关心的是resultReceiver
究竟是什么方针,是怎样赋值的???(这儿在接下来讲到ActivityB的时分会介绍到)
ActivityA
的流程暂时到这儿就没发走下去了
接下来咱们来看看ActivityB
, 当翻开了ActivityB
的时分会履行Activity
的performStart
办法
// android.app.Activity源码
final void performStart(String reason) {
dispatchActivityPreStarted();
// getActivityOptions() 获取到的是在上面ActivityA中创立的ActivityOptions方针
// 里边有支撑的一切的同享元素称号、ActivityA的ExitTransitionCoordinator方针、回来标志等信息
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
mInstrumentation.callActivityOnStart(this);
EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason);
...
mActivityTransitionState.enterReady(this);
dispatchActivityPostStarted();
}
然后就进入到ActivityTransitionState
的enterReady
办法
// android.app.ActivityTransitionState源码
public void enterReady(Activity activity) {
if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
// 获取ActivityA传过来的一切同享元素称号
ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
// 获取ActivityA的ExitTransitionCoordinator
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
// 获取回来标志
final boolean isReturning = mEnterActivityOptions.isReturning();
if (isReturning) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
// 创立EnterTransitionCoordinator,保存resultReceiver、sharedElementNames等方针
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
mEnterActivityOptions.isCrossTask());
if (mEnterActivityOptions.isCrossTask()) {
mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
}
if (!mIsEnterPostponed) { // 是否推迟履行动画,合作postponeEnterTransition办法运用
startEnter();
}
}
上面的mIsEnterPostponed
,默许值是false,能够经过postponeEnterTransition
和 startPostponedEnterTransition
操控什么时分履行动画,这个不是重点,咱们忽略
咱们先来看看EnterTransitionCoordinator
的结构函数
// android.app.EnterTransitionCoordinator源码
EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
super(activity.getWindow(), sharedElementNames,
getListener(activity, isReturning && !isCrossTask), isReturning);
mActivity = activity;
mIsCrossTask = isCrossTask;
// 保存ActivityA的ExitTransitionCoordinator方针到mResultReceiver中
setResultReceiver(resultReceiver);
// 这儿会将ActivityB的window布景设置成通明
prepareEnter();
// 结构resultReceiverBundle,保存EnterTransitionCoordinator方针
Bundle resultReceiverBundle = new Bundle();
resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
// 发送MSG_SET_REMOTE_RECEIVER音讯,将EnterTransitionCoordinator方针传递给ActivityA
mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
...
}
这个时分ActivityA
那边就接纳到了ActivityB
的EnterTransitionCoordinator
方针
接下来我门看看ActivityA
是怎样接纳MSG_SET_REMOTE_RECEIVER
音讯的
// android.app.ExitTransitionCoordinator 源码
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_SET_REMOTE_RECEIVER:
stopCancel();
// 将`ActivityB`的`EnterTransitionCoordinator`方针保存到mResultReceiver方针中
mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
if (mIsCanceled) {
mResultReceiver.send(MSG_CANCEL, null);
mResultReceiver = null;
} else {
//又会触发notifyComplete(),这个时分isReadyToNotify就为true了,就会履行notifyComplete里的代码
notifyComplete();
}
break;
...
}
}
这个时分ActivityA
的同享元素动画的中心逻辑就根本现已走完了
接下来持续看ActivityB
的逻辑,接来下会履行startEnter
办法
// android.app.ActivityTransitionState源码
private void startEnter() {
if (mEnterTransitionCoordinator.isReturning()) { // 这个为false
if (mExitingToView != null) {
mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
mExitingToView);
} else {
mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
}
} else {
// 会履行这个逻辑
mEnterTransitionCoordinator.namedViewsReady(null, null);
mPendingExitNames = null;
}
mExitingFrom = null;
mExitingTo = null;
mExitingToView = null;
mEnterActivityOptions = null;
}
也便是说接下来会触发EnterTransitionCoordinator
的namedViewsReady
办法, 然后就会触发viewsReady
办法
// android.app.EnterTransitionCoordinator源码
public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
triggerViewsReady(mapNamedElements(accepted, localNames));
}
// android.app.EnterTransitionCoordinator源码
private void triggerViewsReady(final ArrayMap<String, View> sharedElements) {
if (mAreViewsReady) {
return;
}
mAreViewsReady = true;
final ViewGroup decor = getDecor();
// Ensure the views have been laid out before capturing the views -- we need the epicenter.
if (decor == null || (decor.isAttachedToWindow() &&
(sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
viewsReady(sharedElements);
} else {
mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> {
mViewsReadyListener = null;
// 触发viewsReady
viewsReady(sharedElements);
});
decor.invalidate();
}
}
EnterTransitionCoordinator
的viewsReady
代码逻辑 跟 ExitTransitionCoordinator
的差不多,预备好同享元素和非同享元素等,
// android.app.EnterTransitionCoordinator源码
@Override
protected void viewsReady(ArrayMap<String, View> sharedElements) {
// 预备好同享元素和非同享元素
super.viewsReady(sharedElements);
mIsReadyForTransition = true;
// 躲藏同享元素
hideViews(mSharedElements);
Transition viewsTransition = getViewsTransition();
if (viewsTransition != null && mTransitioningViews != null) {
// 将mTransitioningViews当作target设置到viewsTransition中
removeExcludedViews(viewsTransition, mTransitioningViews);
// 剔除去mTransitioningViews中不在屏幕中的view
stripOffscreenViews();
// 躲藏非同享元素
hideViews(mTransitioningViews);
}
if (mIsReturning) {
sendSharedElementDestination();
} else {
// 将同享元素用GhostView包裹,然后增加到ActivityB的decorView的OverlayView中
moveSharedElementsToOverlay();
}
if (mSharedElementsBundle != null) {
onTakeSharedElements();
}
}
一般情况下,这个时分mSharedElementsBundle
为null,所以不会走onTakeSharedElements
办法
这儿的mSharedElementsBundle
方针是在ActivityA的notifyComplete
中发送的MSG_TAKE_SHARED_ELEMENTS
音讯传过来的
// android.app.EnterTransitionCoordinator源码
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_TAKE_SHARED_ELEMENTS:
if (!mIsCanceled) {
mSharedElementsBundle = resultData;
onTakeSharedElements();
}
break;
...
}
}
可见当ActivityB
接纳到MSG_TAKE_SHARED_ELEMENTS
音讯,赋值完mSharedElementsBundle
之后,也会履行onTakeSharedElements
办法
接下来咱们来看看onTakeSharedElements
办法
// android.app.EnterTransitionCoordinator源码
private void onTakeSharedElements() {
if (!mIsReadyForTransition || mSharedElementsBundle == null) {
return;
}
final Bundle sharedElementState = mSharedElementsBundle;
mSharedElementsBundle = null;
OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
@Override
public void onSharedElementsReady() {
final View decorView = getDecor();
if (decorView != null) {
OneShotPreDrawListener.add(decorView, false, () -> {
startTransition(() -> {
startSharedElementTransition(sharedElementState);
});
});
decorView.invalidate();
}
}
};
if (mListener == null) {
listener.onSharedElementsReady();
} else {
// 触发SharedElementCallback.onSharedElementsArrived回调
mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
}
}
接下来就会履行startTransition
办法,然后履行startSharedElementTransition
办法,开端履行ActivityB
的动画了
// android.app.EnterTransitionCoordinator源码
private void startSharedElementTransition(Bundle sharedElementState) {
ViewGroup decorView = getDecor();
if (decorView == null) {
return;
}
// Remove rejected shared elements
ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
// 过滤出ActivityA存在,ActivityB不存在的同享元素
rejectedNames.removeAll(mSharedElementNames);
// 依据ActivityA传过来的同享元素sharedElementState信息,创立快照view方针
// 这儿会触发SharedElementCallback.onCreateSnapshotView办法
ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
if (mListener != null) {
// 触发SharedElementCallback.onRejectSharedElements办法
mListener.onRejectSharedElements(rejectedSnapshots);
}
removeNullViews(rejectedSnapshots);
// 履行渐隐的退出动画
startRejectedAnimations(rejectedSnapshots);
// 开端创立同享元素的快照view
// 这儿会再触发一遍SharedElementCallback.onCreateSnapshotView办法
ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
mSharedElementNames);
// 显现同享元素
showViews(mSharedElements, true);
// 增加OnPreDrawListener,鄙人一帧触发SharedElementCallback.onSharedElementEnd回调
scheduleSetSharedElementEnd(sharedElementSnapshots);
// 设置同享元素设置到动画的开端方位,并回来在ActivityB布局中的原始的状况(即完毕方位)
// 这儿会触发SharedElementCallback.onSharedElementStart回调
ArrayList<SharedElementOriginalState> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
requestLayoutForSharedElements();
boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
boolean startSharedElementTransition = true;
setGhostVisibility(View.INVISIBLE);
scheduleGhostVisibilityChange(View.INVISIBLE);
pauseInput();
// 然后就开端收集开端帧和完毕帧,履行过度动画
Transition transition = beginTransition(decorView, startEnterTransition,
startSharedElementTransition);
scheduleGhostVisibilityChange(View.VISIBLE);
setGhostVisibility(View.VISIBLE);
if (startEnterTransition) {
// 增加监听器,动画完毕的时分,将window的布景恢复成不通明等
startEnterTransition(transition);
}
// 将同享元素设置到完毕的方位(为了TransitionManager能收集到完毕帧的值)
setOriginalSharedElementState(mSharedElements, originalImageViewState);
if (mResultReceiver != null) {
// We can't trust that the view will disappear on the same frame that the shared
// element appears here. Assure that we get at least 2 frames for double-buffering.
decorView.postOnAnimation(new Runnable() {
int mAnimations;
@Override
public void run() {
if (mAnimations++ < MIN_ANIMATION_FRAMES) {
View decorView = getDecor();
if (decorView != null) {
decorView.postOnAnimation(this);
}
} else if (mResultReceiver != null) {
mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
mResultReceiver = null; // all done sending messages.
}
}
});
}
}
接下来看一下beginTransition
办法
// android.app.EnterTransitionCoordinator源码
private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
boolean startSharedElementTransition) {
Transition sharedElementTransition = null;
if (startSharedElementTransition) {
if (!mSharedElementNames.isEmpty()) {
// 获取同享元素的过渡动画类Transition,能够经过window.setSharedElementEnterTransition办法设置
// 一般不需求设置 有默许值
sharedElementTransition = configureTransition(getSharedElementTransition(), false);
}
...
}
Transition viewsTransition = null;
if (startEnterTransition) {
mIsViewsTransitionStarted = true;
if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
// 获取非同享元素的过渡动画类Transition,能够经过window.setEnterTransition办法设置
// 一般不需求设置 有默许值
viewsTransition = configureTransition(getViewsTransition(), true);
}
...
// 合并成TransitionSet 方针
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
if (transition != null) {
transition.addListener(new ContinueTransitionListener());
if (startEnterTransition) {
setTransitioningViewsVisiblity(View.INVISIBLE, false);
}
// 开端收集开端帧和完毕帧,履行过度动画
TransitionManager.beginDelayedTransition(decorView, transition);
if (startEnterTransition) {
setTransitioningViewsVisiblity(View.VISIBLE, false);
}
decorView.invalidate();
} else {
transitionStarted();
}
return transition;
}
到了这儿,就会真实的开端履行 ActivityB
的同享元素和非同享元素的出场动画
当动画履行完毕之后就会触发 onTransitionsComplete
办法
// android.app.EnterTransitionCoordinator源码
@Override
protected void onTransitionsComplete() {
// 将同享元素和GhostView从decorView的OverlayView中remove掉
moveSharedElementsFromOverlay();
final ViewGroup decorView = getDecor();
if (decorView != null) {
decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Window window = getWindow();
if (window != null && mReplacedBackground == decorView.getBackground()) {
window.setBackgroundDrawable(null);
}
}
if (mOnTransitionComplete != null) {
mOnTransitionComplete.run();
mOnTransitionComplete = null;
}
}
用十分简略点的话总结同享元素流程是:
-
- ActivityA先履行离场动画
-
- ActivityA将同享元素的完毕方位等特点传递给ActivityB
-
- ActivityB加载自己的布局,在onStart生命周期左右去找到同享元素 先定位到ActivityA的完毕方位
-
- ActivityB开端履行过度动画,过渡到自己布局中的方位
这便是 从ActivityA翻开ActivityB的同享元素动画进程的中心源码剖析进程
ActivityB
回来ActivityA
既然是回来,首要肯定是要调用ActivityB
的finishAfterTransition
办法
// android.app.Activity 源码
public void finishAfterTransition() {
if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
这儿就会调用ActivityTransitionState
的startExitBackTransition
办法
// android.app.ActivityTransitionState源码
public boolean startExitBackTransition(final Activity activity) {
// 获取翻开ActivityB时 传过来的同享元素称号
ArrayList<String> pendingExitNames = getPendingExitNames();
if (pendingExitNames == null || mCalledExitCoordinator != null) {
return false;
} else {
if (!mHasExited) {
mHasExited = true;
Transition enterViewsTransition = null;
ViewGroup decor = null;
boolean delayExitBack = false;
...
// 创立ExitTransitionCoordinator方针
mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
null, null, true);
if (enterViewsTransition != null && decor != null) {
enterViewsTransition.resume(decor);
}
if (delayExitBack && decor != null) {
final ViewGroup finalDecor = decor;
// 鄙人一帧调用startExit办法
OneShotPreDrawListener.add(decor, () -> {
if (mReturnExitCoordinator != null) {
mReturnExitCoordinator.startExit(activity.mResultCode,
activity.mResultData);
}
});
} else {
mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
}
}
return true;
}
}
这个办法首要会获取到需求履行离场动画的同享元素(由ActivityA
翻开ActivityB
时传过来的),然后会创立ExitTransitionCoordinator
方针,终究调用startExit
履行ActivityB
的离场逻辑
咱们持续看看ExitTransitionCoordinator
的结构办法,尽管在上面在剖析ActivityA
翻开ActivityB
时现已看过了这个结构办法,但ActivityB
回来ActivityA
时有点不一样,accepted
和mapped
参数为null
,isReturning
参数为true
// android.app.ExitTransitionCoordinator源码
public ExitTransitionCoordinator(Activity activity, Window window,
SharedElementCallback listener, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
super(window, names, listener, isReturning);
// viewsReady办法跟上面介绍的一样,首要是mapSharedElements不一样了
viewsReady(mapSharedElements(accepted, mapped));
// 剔除去mTransitioningViews中不在屏幕内的非同享元素
stripOffscreenViews();
mIsBackgroundReady = !isReturning;
mActivity = activity;
}
// android.app.ActivityTransitionCoordinator源码
protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
ArrayList<View> localViews) {
ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
if (accepted != null) {
for (int i = 0; i < accepted.size(); i++) {
sharedElements.put(accepted.get(i), localViews.get(i));
}
} else {
ViewGroup decorView = getDecor();
if (decorView != null) {
// 遍历整个ActivityB的一切view,找到一切设置了transitionName特点的view
decorView.findNamedViews(sharedElements);
}
}
return sharedElements;
}
这儿由于accepted
和mapped
参数为null
,所以会遍历整个decorView
上的一切view
,找到一切设置了transitionName
特点的view
,保存到sharedElements
然后viewsReady
就会依据自己所支撑的同享元素称号,从sharedElements
中删去一切不支撑的同享元素,然后对其排序,并保存到mSharedElements
(保存的view
方针)和mSharedElementNames
(保存的是同享元素称号)中; 一起也会预备好非同享元素view
方针,保存在mTransitioningViews
中
留意viewReady
会触发SharedElementCallback.onMapSharedElements
回调
结下来就会履行ExitTransitionCoordinator
的startExit
办法
// android.app.ExitTransitionCoordinator源码
public void startExit(int resultCode, Intent data) {
if (!mIsExitStarted) {
...
// 这儿又将ActivityB的同享元素用GhostView包裹一下,增加的decorView的OverlayView中
moveSharedElementsToOverlay();
// 将ActivityB的window布景设置成通明
if (decorView != null && decorView.getBackground() == null) {
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
final boolean targetsM = decorView == null || decorView.getContext()
.getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
mAllSharedElementNames;
//这儿创立options方针,保存ExitTransitionCoordinator、sharedElementNames等方针
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
sharedElementNames, resultCode, data);
// 这儿会将ActivityB改成通明的activity,一起会将options方针传给ActivityA
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
if (!mIsCanceled) {
fadeOutBackground();
}
}
}, options);
startTransition(new Runnable() {
@Override
public void run() {
startExitTransition();
}
});
}
}
这个办法的首要效果是
- 运用
GhostView
将同享元素view
增加到最顶层decorView
的OverlayView
中 - 然后创立一个
ActivityOptions
方针,把ActivityB
的ExitTransitionCoordinator
方针和支撑的同享元素调集方针传递给ActivityA
- 将ActivityB改成通明布景
然后就会履行startExitTransition
办法
// android.app.ExitTransitionCoordinator源码
private void startExitTransition() {
// 获取ActivityB的非同享元素离场的过渡动画Transition方针
// 终究会调用getReturnTransition办法获取Transition方针
Transition transition = getExitTransition();
ViewGroup decorView = getDecor();
if (transition != null && decorView != null && mTransitioningViews != null) {
setTransitioningViewsVisiblity(View.VISIBLE, false);
// 开端履行非同享元素的离场动画
TransitionManager.beginDelayedTransition(decorView, transition);
setTransitioningViewsVisiblity(View.INVISIBLE, false);
decorView.invalidate();
} else {
transitionStarted();
}
}
看到这儿咱们就知道了,这儿会独自先履行非同享元素的离场动画
ActivityB
的离场流程暂时就走到这儿了,结下来就需求ActivityA
的配和,所以接下来咱们来看看ActivityA
的出场逻辑
ActivityA
出场时,会调用performStart
办法
// android.app.Activity 源码
final void performStart(String reason) {
dispatchActivityPreStarted();
// 这儿的getActivityOptions()获取到的便是上面说的`ActivityB`传过来的方针
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
mInstrumentation.callActivityOnStart(this);
EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason);
...
// ActivityA预备履行出场逻辑
mActivityTransitionState.enterReady(this);
dispatchActivityPostStarted();
}
// android.app.ActivityTransitionState 源码
public void enterReady(Activity activity) {
// mEnterActivityOptions方针便是`ActivityB`传过来的方针
if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
// 同享元素称号
ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
// ActivityB的ExitTransitionCoordinator方针
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
// 回来标志 true
final boolean isReturning = mEnterActivityOptions.isReturning();
if (isReturning) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
// 创立ActivityA的EnterTransitionCoordinator方针
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
mEnterActivityOptions.isCrossTask());
if (mEnterActivityOptions.isCrossTask()) {
mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
}
if (!mIsEnterPostponed) {
startEnter();
}
}
ActivityA
出场时,会在performStart
里获取并保存ActivityB
传过来的方针,然后创立EnterTransitionCoordinator
出场动画完结的中心类,然后调用startEnter办法
// android.app.ActivityTransitionState 源码
private void startEnter() {
if (mEnterTransitionCoordinator.isReturning()) {
// 这儿的mExitingFrom、mExitingTo、mExitingToView是ActivityA翻开ActivityB的时分保存下载的方针
// 所以一般情况下都不为null
if (mExitingToView != null) {
mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
mExitingToView);
} else {
mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
}
} else {
mEnterTransitionCoordinator.namedViewsReady(null, null);
mPendingExitNames = null;
}
mExitingFrom = null;
mExitingTo = null;
mExitingToView = null;
mEnterActivityOptions = null;
}
接下来就会履行EnterTransitionCoordinator
的viewInstancesReady
办法
// android.app.EnterTransitionCoordinator 源码
public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
ArrayList<View> localViews) {
boolean remap = false;
for (int i = 0; i < localViews.size(); i++) {
View view = localViews.get(i);
// view的TransitionName特点有没有发生改动,或者view方针没有AttachedToWindow
if (!TextUtils.equals(view.getTransitionName(), localNames.get(i))
|| !view.isAttachedToWindow()) {
remap = true;
break;
}
}
if (remap) {// 假如发生了改动,则会调用mapNamedElements遍历decorView找到一切设置了TransitionName的view
triggerViewsReady(mapNamedElements(accepted, localNames));
} else { // 一般会履行这个else
triggerViewsReady(mapSharedElements(accepted, localViews));
}
}
接下来就会履行 triggerViewsReady
,里边就会调用viewsReady
办法,viewsReady
在上面介绍过,仅有有点不一样的是 这儿的mIsReturning
是true
, 所以会履行sendSharedElementDestination
办法
// android.app.EnterTransitionCoordinator源码
@Override
protected void viewsReady(ArrayMap<String, View> sharedElements) {
// 预备好同享元素和非同享元素
super.viewsReady(sharedElements);
mIsReadyForTransition = true;
// 躲藏同享元素
hideViews(mSharedElements);
Transition viewsTransition = getViewsTransition();
if (viewsTransition != null && mTransitioningViews != null) {
// 将mTransitioningViews当作target设置到viewsTransition中
removeExcludedViews(viewsTransition, mTransitioningViews);
// 剔除去mTransitioningViews中不在屏幕中的view
stripOffscreenViews();
// 躲藏非同享元素
hideViews(mTransitioningViews);
}
if (mIsReturning) {
sendSharedElementDestination();
} else {
moveSharedElementsToOverlay();
}
if (mSharedElementsBundle != null) {
onTakeSharedElements();
}
}
// android.app.EnterTransitionCoordinator源码
private void sendSharedElementDestination() {
boolean allReady;
final View decorView = getDecor();
if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
allReady = false;
} else if (decorView == null) {
allReady = true;
} else {
allReady = !decorView.isLayoutRequested();
if (allReady) {
for (int i = 0; i < mSharedElements.size(); i++) {
if (mSharedElements.get(i).isLayoutRequested()) {
allReady = false;
break;
}
}
}
}
if (allReady) {
// 捕获同享元素当时的状况, 会触发SharedElementCallback.onCaptureSharedElementSnapshot办法
Bundle state = captureSharedElementState();
// 将同享元素view 增加的最顶层(decorView的OverlayView中)
moveSharedElementsToOverlay();
// 给ActivityB发送MSG_SHARED_ELEMENT_DESTINATION,将同享元素的状况传给ActivityB
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
} else if (decorView != null) {
OneShotPreDrawListener.add(decorView, () -> {
if (mResultReceiver != null) {
Bundle state = captureSharedElementState();
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
}
});
}
if (allowOverlappingTransitions()) {
// 履行非同享元素的出场动画
startEnterTransitionOnly();
}
}
sendSharedElementDestination
办法首要有以下三个效果
- 获取
ActivityA
当时同享元素的状况 传给ActivityB
,当作过度动画完毕方位的状况(即完毕帧) - 将同享元素增加到最顶层(decorView的OverlayView中)
- 给
ActivityB
发送MSG_SHARED_ELEMENT_DESTINATION
音讯传递state
- 优先开端履行
ActivityA
的非同享元素的出场动画
到这儿ActivityA
的逻辑暂时告一段落,接下来咱们来看看ActivityB
接纳到MSG_SHARED_ELEMENT_DESTINATION
时干了些什么
// android.app.ExitTransitionCoordinator
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
...
case MSG_SHARED_ELEMENT_DESTINATION:
// 保存ActivityA传过来的同享元素状况
mExitSharedElementBundle = resultData;
// 预备履行同享元素退出动画
sharedElementExitBack();
break;
...
}
}
接下来咱们来看看sharedElementExitBack
办法
// android.app.ExitTransitionCoordinator
private void sharedElementExitBack() {
final ViewGroup decorView = getDecor();
if (decorView != null) {
decorView.suppressLayout(true);
}
if (decorView != null && mExitSharedElementBundle != null &&
!mExitSharedElementBundle.isEmpty() &&
!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
startTransition(new Runnable() {
public void run() {
// 会履行这个办法
startSharedElementExit(decorView);
}
});
} else {
sharedElementTransitionComplete();
}
}
接下来就会履行startSharedElementExit
办法
// android.app.ExitTransitionCoordinator
private void startSharedElementExit(final ViewGroup decorView) {
// 获取同享元素的过度动画的Transition方针,里边终究会调用`getSharedElementReturnTransition`办法获取该方针
Transition transition = getSharedElementExitTransition();
transition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
if (isViewsTransitionComplete()) {
delayCancel();
}
}
});
// 依据ActivityA传过来的状况,创立快照view方针
// 这儿会触发SharedElementCallback.onCreateSnapshotView办法
final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
mSharedElementNames);
OneShotPreDrawListener.add(decorView, () -> {
// 鄙人一帧触发,将同享元素的特点设置到开端状况(ActivityA中同享元素的状况)
// 这儿会触发SharedElementCallback.onSharedElementStart回调
setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
});
setGhostVisibility(View.INVISIBLE);
scheduleGhostVisibilityChange(View.INVISIBLE);
if (mListener != null) {
// 先触发SharedElementCallback.onSharedElementEnd回调
mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
sharedElementSnapshots);
}
// 收集开端帧和完毕帧,并履行动画
TransitionManager.beginDelayedTransition(decorView, transition);
scheduleGhostVisibilityChange(View.VISIBLE);
setGhostVisibility(View.VISIBLE);
decorView.invalidate();
}
看到上面的办法你或许会发现,先触发了onSharedElementEnd
办法,然后再触发onSharedElementStart
,这或许是由于ActivityB
回来到ActivityA
时, google
工程师界说为是从完毕状况回来到开端状况吧,即ActivityB
的状况为完毕状况,ActivityA
的状况为开端状况
至于setGhostVisibility
和scheduleGhostVisibilityChange
首要的效果是为TransitionManager
收集开端帧和完毕帧履行动画用的
到这儿ActivityB
就开端履行同享元素的退出动画了
当ActivityB
同享元素动画履行完毕之后,就会调用sharedElementTransitionComplete
办法,然后就会调用notifyComplete
办法
@Override
protected void sharedElementTransitionComplete() {
// 这儿又会获取ActivityB同享元素的状况(之后会传给ActivityA)
// 或许会触发ActivityB的SharedElementCallback.onCaptureSharedElementSnapshot回调
mSharedElementBundle = mExitSharedElementBundle == null
? captureSharedElementState() : captureExitSharedElementsState();
super.sharedElementTransitionComplete();
}
这儿为什么要再一次获取ActivityB
的同享元素的状况,由于需求传给ActivityA
, 然后ActivityA
再依据条件判断 同享元素的状况是否又发生了改动,然后交给ActivityA
自己去履行同享元素动画
至于终究会履行notifyComplete
,源码就没什么美观的了,上面也都介绍过,这儿边首要是给ActivityA
发送了MSG_TAKE_SHARED_ELEMENTS
音讯,将ActivityB
的同享元素的状况方针(mSharedElementBundle
)传递给ActivityA
到这儿ActivityB
离场动画根本上就完毕了,至于终究的状况清空等处理 咱们就不看了
接下来咱们持续看ActivityA
接纳到MSG_TAKE_SHARED_ELEMENTS
音讯后做了什么
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_TAKE_SHARED_ELEMENTS:
if (!mIsCanceled) {
// 保存同享元素状况方针
mSharedElementsBundle = resultData;
// 预备履行同享元素动画
onTakeSharedElements();
}
break;
...
}
}
结下来就会履行onTakeSharedElements
办法,这些办法的源码上面都介绍过,我就不在贴出来了,这儿边会触发SharedElementCallback.onSharedElementsArrived
回调,然后履行startSharedElementTransition
// android.app.EnterTransitionCoordinator源码
private void startSharedElementTransition(Bundle sharedElementState) {
ViewGroup decorView = getDecor();
if (decorView == null) {
return;
}
// Remove rejected shared elements
ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
// 过滤出ActivityB存在,ActivityA不存在的同享元素
rejectedNames.removeAll(mSharedElementNames);
// 依据ActivityB传过来的同享元素sharedElementState信息,创立快照view方针
// 这儿会触发SharedElementCallback.onCreateSnapshotView办法
ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
if (mListener != null) {
// 触发SharedElementCallback.onRejectSharedElements办法
mListener.onRejectSharedElements(rejectedSnapshots);
}
removeNullViews(rejectedSnapshots);
// 履行渐隐的退出动画
startRejectedAnimations(rejectedSnapshots);
// 开端创立同享元素的快照view
// 这儿会再触发一遍SharedElementCallback.onCreateSnapshotView办法
ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
mSharedElementNames);
// 显现同享元素
showViews(mSharedElements, true);
// 增加OnPreDrawListener,鄙人一帧触发SharedElementCallback.onSharedElementEnd回调
scheduleSetSharedElementEnd(sharedElementSnapshots);
// 设置同享元素设置到动画的开端方位,并回来在ActivityA布局中的原始的状况(即完毕方位)
// SharedElementCallback.onSharedElementStart回调
ArrayList<SharedElementOriginalState> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
requestLayoutForSharedElements();
boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
boolean startSharedElementTransition = true;
setGhostVisibility(View.INVISIBLE);
scheduleGhostVisibilityChange(View.INVISIBLE);
pauseInput();
// 然后就开端收集开端帧和完毕帧,履行过度动画
Transition transition = beginTransition(decorView, startEnterTransition,
startSharedElementTransition);
scheduleGhostVisibilityChange(View.VISIBLE);
setGhostVisibility(View.VISIBLE);
if (startEnterTransition) {// 这儿为false,不会履行, 由于非同享元素动画履行独自履行了
startEnterTransition(transition);
}
// 将同享元素设置到完毕的方位(为了TransitionManager能收集到完毕帧的值)
setOriginalSharedElementState(mSharedElements, originalImageViewState);
if (mResultReceiver != null) {
// We can't trust that the view will disappear on the same frame that the shared
// element appears here. Assure that we get at least 2 frames for double-buffering.
decorView.postOnAnimation(new Runnable() {
int mAnimations;
@Override
public void run() {
if (mAnimations++ < MIN_ANIMATION_FRAMES) {
View decorView = getDecor();
if (decorView != null) {
decorView.postOnAnimation(this);
}
} else if (mResultReceiver != null) {
mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
mResultReceiver = null; // all done sending messages.
}
}
});
}
}
这儿要特别阐明的是
- 这儿没有履行
ActivityA
的非同享元素的出场动画,由于在之前现已优先调用了非同享元素的出场动画 - 尽管这儿调用了
ActivityA
的同享元素动画,可是根本上并不会创立动画方针去履行,由于ActivityB
传过来的状况 跟ActivityA
当时的状况是一模一样的,除非你在某种情况下并在履行动画之前 强制改动ActivityA
的当时状况;所以你所看到的同享元素的离场动画其实是ActivityB
的同享元素离场动画,而不是ActivityA
的
终究ActivityA
的同享元素动画完毕之后 会就调用onTransitionsComplete
(不需求履行动画,就会立马触发),将ActivityA
的同享元素view从从decorView的ViewGroupOverlay中remove掉
到这儿由ActivityB
回来ActivityA
的离场动画到这儿根本上就完毕了,至于终究的cancel
等状况整理就不介绍了
到这儿我也用十分简略点的大白话总结一下ActivityB
回来ActivityA
的离场动画:
-
- 将
ActivityB
的window布景设置成通明, 并履行非同享元素的离场动画
- 将
-
- 回来到ActivityA时,将会履行到performStart办法,并履行非同享元素的出场动画
-
-
ActivityB
接纳到ActivityA
传过来的同享元素状况,开端履行同享元素的离场动画
-
-
-
ActivityA
接纳到ActivityB
的同享元素状况,持续履行同享元素动画(但由于两个状况没有改动,所以并不会履行动画,会立马直接动画完毕的回调)
-
SharedElementCallback回调总结
终究咱们在总结以下SharedElementCallback
回调的次序,由于你有或许会自界说这个类 做一些特定的逻辑处理
当是ActivityA翻开ActivityB时
ActivityA: ==Exit, onMapSharedElements
ActivityA: ==Exit, onCaptureSharedElementSnapshot
ActivityA: ==Exit, onCaptureSharedElementSnapshot
ActivityB: ==Enter, onMapSharedElements
ActivityA: ==Exit, onSharedElementsArrived
ActivityB: ==Enter, onSharedElementsArrived
ActivityB: ==Enter, onCreateSnapshotView
ActivityB: ==Enter, onRejectSharedElements
ActivityB: ==Enter, onCreateSnapshotView
ActivityB: ==Enter, onSharedElementStart
ActivityB: ==Enter, onSharedElementEnd
当是ActivityB回来到ActivityA时
ActivityB: ==Enter, onMapSharedElements
ActivityA: ==Exit, onMapSharedElements
ActivityA: ==Exit, onCaptureSharedElementSnapshot
ActivityB: ==Enter, onCreateSnapshotView
ActivityB: ==Enter, onSharedElementEnd
ActivityB: ==Enter, onSharedElementStart
ActivityB: ==Enter, onSharedElementsArrived
ActivityA: ==Exit, onSharedElementsArrived
ActivityA: ==Exit, onRejectSharedElements
ActivityA: ==Exit, onCreateSnapshotView
ActivityA: ==Exit, onSharedElementStart
ActivityA: ==Exit, onSharedElementEnd
好了,到这儿 我所要介绍的内容现已完毕了,上面的源码是针对Android30和Android31剖析的(我在不同的时间段用不同的笔记本写的,所以上面的源码有的是Android30的源码,有的是Android31的源码)
终究再附上一张Activity同享元素动画的全程时序图