最近在我的应用KeepassA中碰到了一个诡异的过渡动画问题

正常状态应该如下:

normal

当我从一级设置界面,进入二级设置界面后,并从二级设置界面返回时,一级界面当回主页的过渡动画神器消失了

error

原因分析

阅读源码发现,返回时调用的finishAfterTransition()最终会调用ActivityTransitionStatestartExitBackTransition方法,但是当我从二级界面返回到一级界面,并从一级界面返回主页时,pendingExitNames变为了空,导致直接走了finish,而没有走过渡动画的逻辑。

public void finishAfterTransition() {
if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
public boolean startExitBackTransition(final Activity activity) {
ArrayList<String> pendingExitNames = getPendingExitNames();
if (pendingExitNames == null || mCalledExitCoordinator != null) {
return false;
} else {
...
}
....
}

为什么会出现pendingExitNames为空的情况呢,继续阅读源代码,通过Activity的生命周期可以知道,每当activity开始活动时(从二级界面返回一级界面会回调onStart),导致重新调用了onNewActivityOptions方法。

/** @hide */
public void onNewActivityOptions(ActivityOptions options) {
mActivityTransitionState.setEnterActivityOptions(this, options); // 重新设置了option
if (!mStopped) {
mActivityTransitionState.enterReady(this);
}
}

mActivityTransitionState.setEnterActivityOptions(this, options);中会重新设置共享元素,如果当前activity已经停止(启动了二级页面,并从二级界面返回)则会调用 mActivityTransitionState.enterReady重新构建过渡动画。而在这一步debug发现ActivityOptions,设置的过渡元素为null了。

public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
...
if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
&& options != null && mEnterActivityOptions == null
&& mEnterTransitionCoordinator == null
&& options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
mEnterActivityOptions = options; // 重新更新了option,但是这时option的共享元素列表被清空了
...
}
}
public void enterReady(Activity activity) {
...

// 重新创建过渡场景,但是该场景的共享元素列表 sharedElementNames 没有数据
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
mEnterActivityOptions.isCrossTask());

...

if (!mIsEnterPostponed) {
startEnter();
}
}

这个时候,只是共享元素的列表大小为0,并没有为null,还达不到那个条件,继续阅读代码,看到EnterTransitionCoordinator找到了一个处理共享元素状态的方法onReceiveResult,在这里面看到,只有接收到的消息类型为MSG_ALLOW_RETURN_TRANSITION才会给mPendingExitNames赋值。但是这个消息接收又是从那个地方回调的呢?

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_TAKE_SHARED_ELEMENTS:
if (!mIsCanceled) {
mSharedElementsBundle = resultData;
onTakeSharedElements();
}
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mIsCanceled) {
mIsExitTransitionComplete = true;
if (mSharedElementTransitionStarted) {
onRemoteExitTransitionComplete();
}
}
break;
case MSG_CANCEL:
cancel();
break;
case MSG_ALLOW_RETURN_TRANSITION:
if (!mIsCanceled) {
mPendingExitNames = mAllSharedElementNames;
}
break;
}
}

根据其父类ActivityTransitionCoordinator,发现其本质上是个handler的处理函数。

public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
SharedElementCallback listener, boolean isReturning) {
super(new Handler());
mWindow = window;
mListener = listener;
mAllSharedElementNames = allSharedElementNames;
mIsReturning = isReturning;
}

因此只要找到发送消息的地方,就能知道启动的各种条件。继续阅读源码,该方法在onresume中被调用,将会取消所有待处理的过渡动画!!!!

/**
* This is called onResume. If an Activity is resuming and the transitions
* haven't started yet, force the views to appear. This is likely to be
* caused by the top Activity finishing before the transitions started.
* In that case, we can finish any transition that was started, but we
* should cancel any pending transition and just bring those Views visible.
*/
public void forceViewsToAppear() {
if (!mIsReturning) {
return;
}
if (!mIsReadyForTransition) {
mIsReadyForTransition = true;
final ViewGroup decor = getDecor();
if (decor != null && mViewsReadyListener != null) {
mViewsReadyListener.removeListener();
mViewsReadyListener = null;
}
showViews(mTransitioningViews, true);
setTransitioningViewsVisiblity(View.VISIBLE, true);
mSharedElements.clear();
mAllSharedElementNames.clear();
mTransitioningViews.clear();
mIsReadyForTransition = true;
viewsTransitionComplete();
sharedElementTransitionComplete();
} else {
if (!mSharedElementTransitionStarted) {
moveSharedElementsFromOverlay();
mSharedElementTransitionStarted = true;
showViews(mSharedElements, true);
mSharedElements.clear();
sharedElementTransitionComplete();
}
if (!mIsViewsTransitionStarted) {
mIsViewsTransitionStarted = true;
showViews(mTransitioningViews, true);
setTransitioningViewsVisiblity(View.VISIBLE, true);
mTransitioningViews.clear();
viewsTransitionComplete();
}
cancelPendingTransitions();
}
mAreViewsReady = true;
if (mResultReceiver != null) {
mResultReceiver.send(MSG_CANCEL, null);
mResultReceiver = null;
}
}