最近在项目中遇到一个关于共享元素动画失效的问题,在Activity跳转时使用ActivityOptionsCompat.makeSceneTransitionAnimation
做转场动画,
对于Activity共享元素动画的使用不懂的童鞋自行谷歌,网上资料太多,这里就不做描述了。
一般情况下使用ActivityOptionsCompat.makeSceneTransitionAnimation
做转场动画时,当Activity返回时也会伴随返回的动画。但是就是这样的一个简单的API,
居然在Android 10上测试出现了异常:
如果使用了
ActivityOptionsCompat.makeSceneTransitionAnimation
跳转的Activity没有再次跳转到别的Activity的话,返回时系统是带上响应的共享元素动画的,
但是如果跳转的Activity再次跳转了其他的Activity,然后再返回的话共享元素的返回动画就失效了(包括按了Home键退回桌面,然后再次进入也会失效)。
笔者的测试手机是华为nova 5i
,系统是Android 10
。
1、 大神方案
当笔者看到这个bug的时候,首先想到的可能是我打开的方式不对,于是谷歌百度了一波,结果确实无功而返。
于是笔者第一想到的就是是不是自己的使用方式不对,于是看看大神是怎么使用的,然后参考了一下Android 大神郭霖开源的一个开源项目giffun
,
发现giffun
也存在同样的异常…
为此我专门给giffun
提了一个issue:
https://github.com/guolindev/giffun/issues/67
绝望了。。。。。。。
2、 竞品对比
既然查找不到相关的资料,那么就看看竞品是否也有这样的问题的,如果竞品也有这样的问题话就可以拿着竞品去忽悠产品了,心里美滋滋。
于是笔者对比了一下竞品的产品是否也存在这么的一个bug,发现竞品没有一样的bug,它们的共享元素动画一切正常。
后面通过研究了它们的APK才发现它们的共享元素动画是使用Fragment做的,所以尽量使用Fragment的一个好处又体现出来了。
但是笔者今天要说的将Activity的转场动画改为Fragment这样就完事了,今天将带大家一步一步分析共享元素动画是如何失效的,如何修复这样的一个bug。
3、 源码先行
既然网上没有这样的资料,那就自己动手,丰衣足食了。首先我们初步看看Activity
的源码,发现如果是返回带执行共享元素动画的话执行的方法是finishAfterTransition
。
在finishAfterTransition
里面调用了ActivityTransitionState
的startExitBackTransition
方法。
然而看源码并没有看出什么破绽,毕竟大多数手机是正常的,只有在Android 10上才出现了异常。既然这条路走不通,那我们就换一个途径吧。
4、 Debug大法
我们使用Android Studio的断点调试功能,在ActivityTransitionState
的startExitBackTransition
方法里面打断点调试,
对比发现最终返回时没有执行共享元素动画的原因是startExitBackTransition
方法内的pendingExitNames
变量为空就直接返回了false,也就是不执行共享元素动画。
5、 万变不离其宗,再次回到源码
那么为什么pendingExitNames
变量会变成了空呢?我们再次回到源码分析,pendingExitNames
变量是通过
ActivityTransitionState
的startExitBackTransition
的getPendingExitNames
方法获取的,方法如下:
if (mPendingExitNames == null && mEnterTransitionCoordinator != null) {
mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
}
return mPendingExitNames;
}
很简单的代码,如果mEnterTransitionCoordinator
是空的话,那么getPendingExitNames
方法必定会返回空。
那么我们继续追踪一下mEnterTransitionCoordinator
变量是如何赋值的,跟踪发现是在ActivityTransitionState
的enterReady(Activity activity)
方法
中对mEnterTransitionCoordinator
做了赋值操作,然后在ActivityTransitionState
的onStop
方法中被置空了,而ActivityTransitionState
的onStop
方法
又被Activity
的onStop
方法调用了,至此大概就能解析的解析得通为什么经过跳转后Activity的返回共享元素失效了,原来是被Activity的onStop
生命周期给影响了。
ActivityTransitionState的enterReady代码:
public void enterReady(Activity activity) {
if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
ArrayList sharedElementNames = mEnterActivityOptions.getSharedElementNames();
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
if (mEnterActivityOptions.isReturning()) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
// 在这里对mEnterTransitionCoordinator赋值了
// 在这里对mEnterTransitionCoordinator赋值了
// 在这里对mEnterTransitionCoordinator赋值了
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();
}
}
ActivityTransitionState的onStop代码:
public void onStop() {
restoreExitedViews();
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.stop();
mEnterTransitionCoordinator = null;
}
if (mReturnExitCoordinator != null) {
mReturnExitCoordinator.stop();
mReturnExitCoordinator = null;
}
}
在这里给同学们留一个问题,既然是被Activity的onStop方法影响了,那么为什么有些系统可以,但是在Android 10系统上就不行了呢?这个问题留给大家去解答,因为最近项目非常忙,
笔者也没时间去深究这个问题了。
既然找到了问题那么就好解决了,我们在Activity的onResume方法中调用一下ActivityTransitionState
的enterReady
方法,再次给mEnterTransitionCoordinator
赋值不久完事了吗?
但是ActivityTransitionState
类是系统的私有类,开发者是不能直接调用的,这时候我们就想到了Java的反射大法,是的笔者就是通过反射调用的。
在编写反射调用ActivityTransitionState
的enterReady
方法时候AS提示targetSdkVersion
是28以上的会得到反射异常。这是因为Android 9以上对反射做了限制,这时候我们只需要将targetSdkVersion
设置成28以下的即可。
主要代码:
/**
* Android10 Activity的onStop方法可能会导致共享元素动画失效,通过反射注入恢复共享元素动画
* @param activity
*/
public static void updateResume(Activity activity){
try {
Field activityTransitionStateField = Activity.class.getDeclaredField("mActivityTransitionState");
activityTransitionStateField.setAccessible(true);
Object mActivityTransitionState = activityTransitionStateField.get(activity);
Class clazz = Class.forName("android.app.ActivityTransitionState");
Method enterReady = clazz.getDeclaredMethod("enterReady",Activity.class);
enterReady.setAccessible(true);
enterReady.invoke(mActivityTransitionState,activity);
} catch (Exception e) {
e.printStackTrace();
}
}
然后在Activity的onResume方法中调用一下反射方法即可。实测返回是共享元素动画正常,但是会导致什么未知bug目前还不可知,欢迎大家深究讨论。。。