Android 共享元素动画分析及背景空白的解决方案

背景

前段时间写了一篇Android 仿微信朋友圈图片拖拽返回,有朋友指出为什么在拖拽的时候,发现上一个页面点击的图片是空白的,可以看下效果图。

SVID_20190515_114901_123.gif

出现问题的本能反应,先对比下微信朋友圈的效果,发现没问题。[手动黑人问号脸]
后来无意中发现,当手机休眠唤醒之后,这个问题就没有了。那就说明在onResume中的部分代码对view做了处理。

onResume分析

既然发现onResume是没问题的,所以进入该方法。(注意,全篇都是基于API-28分析)

//Activity.class
protected void onResume() {
    getApplication().dispatchActivityResumed(this);
    mActivityTransitionState.onResume(this, isTopOfTask());
    if (mAutoFillResetNeeded) {
        if (!mAutoFillIgnoreFirstResumePause) {
            View focus = getCurrentFocus();
            if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
                getAutofillManager().notifyViewEntered(focus);
            }
        }
    }
    mCalled = true;
}

因为是在共享元素发现的问题,那就很明显,主要就看mActivityTransitionState.onResume(this, isTopOfTask());这行代码。继续追踪

//ActivityTransitionState.class
public void onResume(Activity activity, boolean isTopOfTask) {
    if (isTopOfTask || mEnterTransitionCoordinator == null) {
        restoreExitedViews();
        restoreReenteringViews();
    } else {
        activity.mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mEnterTransitionCoordinator == null ||
                        mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
                    restoreExitedViews();
                    restoreReenteringViews();
                }
            }
        }, 1000);
    }
}

其实就两个关键方法,restoreExitedViews();和restoreReenteringViews(); 那就分别看下这两个方法做了什么。

restoreExitedViews,字面翻译意思就是,恢复已经退出的view。

//ActivityTransitionState.class
private void restoreExitedViews() {
    if (mCalledExitCoordinator != null) {
        mCalledExitCoordinator.resetViews();
        mCalledExitCoordinator = null;
    }
}

很简单,只有一行有效代码,继续跟进,进入ExitTransitionCoordinator.class。

//ExitTransitionCoordinator.class
public void resetViews() {
    ViewGroup decorView = getDecor();
    if (decorView != null) {
        //如果decorView不为空,则强制将动画结束
        TransitionManager.endTransitions(decorView);
    }
    if (mTransitioningViews != null) {
        //如果有过渡动画的view的话,就开始显示view
        showViews(mTransitioningViews, true);
        setTransitioningViewsVisiblity(View.VISIBLE, true);
    }
    showViews(mSharedElements, true);
    mIsHidden = true;
    if (!mIsReturning && decorView != null) {
        decorView.suppressLayout(false);
    }
    //从遮罩层上移除共享元素
    moveSharedElementsFromOverlay();
    //清除状态
    clearState();
}

关键方法1.showViews,2.setTransitioningViewsVisiblity,3.moveSharedElementsFromOverlay。其实看字面意思就是重新显示view。不妨在跟踪看下。

进入父类ActivityTransitionCoordinator.class

//ActivityTransitionCoordinator.class
protected void showViews(ArrayList views, boolean setTransitionAlpha) {
    int count = views.size();
    for (int i = 0; i < count; i++) {
        showView(views.get(i), setTransitionAlpha);
    }
}

private void showView(View view, boolean setTransitionAlpha) {
    Float alpha = mOriginalAlphas.remove(view);
    if (alpha != null) {
        view.setAlpha(alpha);
    }
    if (setTransitionAlpha) {
        view.setTransitionAlpha(1f);
    }
}

本以为这个方法主要做的是setVisibility,结果发现主要做了alpha的处理。再看setTransitioningViewsVisiblity

//ActivityTransitionCoordinator.class
protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) {
    final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
    for (int i = 0; i < numElements; i++) {
        final View view = mTransitioningViews.get(i);
        if (invalidate) {
            view.setVisibility(visiblity);
        } else {
            view.setTransitionVisibility(visiblity);
        }
    }
}

终于,找到setVisibility方法了。最后看moveSharedElementsFromOverlay方法

//ActivityTransitionCoordinator.class
protected void moveSharedElementsFromOverlay() {
    int numListeners = mGhostViewListeners.size();
    for (int i = 0; i < numListeners; i++) {
        GhostViewListeners listener = mGhostViewListeners.get(i);
        listener.removeListener();
    }
    mGhostViewListeners.clear();
    ****省略部分代码****
    ViewGroup decor = getDecor();
    if (decor != null) {
        ViewGroupOverlay overlay = decor.getOverlay();
        int count = mSharedElements.size();
        for (int i = 0; i < count; i++) {
            //移除共享元素的view
            View sharedElement = mSharedElements.get(i);
            GhostView.removeGhost(sharedElement);
        }
    }
}

所以,showViews 将view的alpha设为1,即不透明,
setTransitioningViewsVisiblity 将view重新setVisiblity,
moveSharedElementsFromOverlay 从遮罩层中移除共享元素

其实到这里就已经知道,为什么onResume之后就没问题了。因为view setVisiblity 和 setAlpha了。但是还是看下restoreReenteringViews方法。

//ActivityTransitionState.class
private void restoreReenteringViews() {
    if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
            !mEnterTransitionCoordinator.isCrossTask()) {
        //关键代码
        mEnterTransitionCoordinator.forceViewsToAppear();
        mExitingFrom = null;
        mExitingTo = null;
        mExitingToView = null;
    }
}

继续进入关键代码forceViewsToAppear

//EnterTransitionCoordinator.class
public void forceViewsToAppear() {
    ****省略部分代码****
    if (!mIsReadyForTransition) {
        mIsReadyForTransition = true;
        ****省略部分代码****
        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();
    }
    ****省略部分代码****
}

其实会发现,和上面分析的restoreExitedViews方法差不多,基本都已经分析过了。所以,这个问题到目前为止,其实已经算结束了。只要在共享元素动画结束之后,再手动的调一次onResume方法即可。但是,onResume方法太重了,还没见过解决方法用的这么重的方法的。那就继续分析吧。

共享元素分析

略长,对源码分析不感兴趣的,可以直接滑到最后。
首先,共享元素的动画监听,可以通过setExitSharedElementCallback 和 setEnterSharedElementCallback监听。其中共有7个方法,如下

//动画开始
void onSharedElementStart(List sharedElementNames, List sharedElements, List sharedElementSnapshots)
//动画结束
void onSharedElementEnd(List sharedElementNames, List sharedElements, List sharedElementSnapshots)
//被移除的共享元素,即不需要进行共享元素动画的view
void onRejectSharedElements(List rejectedSharedElements)
//共享元素view和name的键值对
void onMapSharedElements(List names, Map sharedElements)
//将view的信息以parcelable的对象类型保存,用于activity之间传递
Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)
 //将parcelable对象重新转成view
View onCreateSnapshotView(Context context, Parcelable snapshot) 
 //共享元素已经拿到
void onSharedElementsArrived(List sharedElementNames, List sharedElements, OnSharedElementsReadyListener listener)

我们不妨先打印下log,看下调用顺序。
注意:A进入B的过程,A使用setExitSharedElementCallback退出监听,B使用setEnterSharedElementCallback进入监听。
结果如下

A进入B
A:
onMapSharedElements
onCaptureSharedElementSnapshot
onSharedElementsArrived
B:
onMapSharedElements
onSharedElementsArrived
onRejectSharedElements
onCreateSnapshotView
onSharedElementStart
onSharedElementEnd

B返回到A
B:
onMapSharedElements
A:
onMapSharedElements
onCaptureSharedElementSnapshot
B:
onCreateSnapshotView
onSharedElementEnd//没看错,先end后start
onSharedElementStart
onSharedElementsArrived
A:
onSharedElementsArrived
onRejectSharedElements
onCreateSnapshotView
onSharedElementStart
onSharedElementEnd

从页面开始分析

//MainActivity.class
ActivityOptionsCompat compat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, v, "share_photo");
startActivity(intent, compat.toBundle());

先从makeSceneTransitionAnimation开始,一步步跳转,进入ActivityOptions.class

//ActivityOptions.class
static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
        ActivityOptions opts, SharedElementCallback callback,
        Pair[] sharedElements) {
    ****省略部分代码****
    opts.mAnimationType = ANIM_SCENE_TRANSITION;
    ArrayList names = new ArrayList();
    ArrayList views = new ArrayList();
    if (sharedElements != null) {
        //将view和name以键值对的形式保存
        for (int i = 0; i < sharedElements.length; i++) {
            Pair 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);
        }
    }
    
    //创建ExitTransitionCoordinator
    ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
            callback, names, names, views, false);
    ****省略部分代码****
    return exit;
}

主要就是将view和name以键值对的形式保存,并且创建了ExitTransitionCoordinator对象,进入此对象

//ExitTransitionCoordinator.class
public ExitTransitionCoordinator(Activity activity, Window window,
        SharedElementCallback listener, ArrayList names,
        ArrayList accepted, ArrayList mapped, boolean isReturning) {
    super(window, names, listener, isReturning);
    viewsReady(mapSharedElements(accepted, mapped));
    stripOffscreenViews();
    mIsBackgroundReady = !isReturning;
    mActivity = activity;
}

在进入父类的viewsReady

//ActivityTransitionCoordinator.class
protected void viewsReady(ArrayMap sharedElements) {
    sharedElements.retainAll(mAllSharedElementNames);
    if (mListener != null) {
        mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
    }
    setSharedElements(sharedElements);
    ****省略部分代码****
}

成功找到第一个回调onMapSharedElements。所以ActivityOptionsCompat.makeSceneTransitionAnimation,主要就是将view和对应的name保存到ExitTransitionCoordinator对象中,期间会执行onMapSharedElements回调。

然后分析startActivity-->startActivityForResult-->cancelInputsAndStartExitTransition-->startExitOutTransition-->startExit

//ExitTransitionCoordinator.class
public void startExit() {
    if (!mIsExitStarted) {
        ****省略部分代码****
        moveSharedElementsToOverlay();
        startTransition(new Runnable() {
            @Override
            public void run() {
                if (mActivity != null) {
                    beginTransitions();
                } else {
                    startExitTransition();
                }
            }
        });
    }
}

主要就是两个方法,1.moveSharedElementsToOverlay,2.startTransition。方法1理解字面意思即可。主要还是方法2。
因为mActivity不为null,所以直接分析beginTransitions

//ExitTransitionCoordinator.class
private void beginTransitions() {
    //获取共享元素的过渡效果
    Transition sharedElementTransition = getSharedElementExitTransition();
    //获取其他view的过渡效果
    Transition viewsTransition = getExitTransition();
    //合并
    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();
    }
}

主要是transition的监听

//ExitTransitionCoordinator.class
private Transition getSharedElementExitTransition() {
    Transition sharedElementTransition = null;
    if (!mSharedElements.isEmpty()) {
        sharedElementTransition = configureTransition(getSharedElementTransition(), false);
    }
    if (sharedElementTransition == null) {
        sharedElementTransitionComplete();
    } else {
        sharedElementTransition.addListener(new ContinueTransitionListener() {
            @Override
            public void onTransitionEnd(Transition transition) {
                //end的操作
                sharedElementTransitionComplete();
                if (mIsHidden) {
                    showViews(mSharedElements, true);
                }
                super.onTransitionEnd(transition);
            }
        });
        mSharedElements.get(0).invalidate();
    }
    return sharedElementTransition;
}

这里会进入重写的sharedElementTransitionComplete,

//ExitTransitionCoordinator.class
@Override
protected void sharedElementTransitionComplete() {
    mSharedElementBundle = mExitSharedElementBundle == null
            ? captureSharedElementState() : captureExitSharedElementsState();
    super.sharedElementTransitionComplete();
}

此处会进入captureSharedElementState 方法以及super.sharedElementTransitionComplete();方法。
先进入父类的captureSharedElementState方法,

//ActivityTransitionCoordinator.class
protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
        Matrix tempMatrix, RectF tempBounds) {
    ****省略部分代码****
    if (mListener != null) {
        bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
    }
    ****省略部分代码****
}

从而找到了mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds)回调。

同时super.sharedElementTransitionComplete();会进入startInputWhenTransitionsComplete,再进入onTransitionsComplete,最后进入notifyComplete方法

//ExitTransitionCoordinator.class
protected void notifyComplete() {
    if (isReadyToNotify()) {
        if (!mSharedElementNotified) {
            mSharedElementNotified = true;
            delayCancel();
            if (mListener == null) {
                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
                notifyExitComplete();
            } else {
                final ResultReceiver resultReceiver = mResultReceiver;
                final Bundle sharedElementBundle = mSharedElementBundle;
                mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
                        new OnSharedElementsReadyListener() {
                            @Override
                            public void onSharedElementsReady() {
                                resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
                                        sharedElementBundle);
                                notifyExitComplete();
                            }
                        });
            }
        } else {
            notifyExitComplete();
        }
    }
}

但是因为此处isReadyToNotify()为false,所以这个方法等于没有执行。所以到目前为止,A页面已经没法继续分析了。

开始分析页面B。页面B会经过onCreate,onStart等方法。找到performStart方法,再找到mActivityTransitionState.enterReady(this); 进入enterReady

//ActivityTransitionState.class
public void enterReady(Activity activity) {
    ****省略部分代码****
    ArrayList sharedElementNames = mEnterActivityOptions.getSharedElementNames();
    ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
    if (mEnterActivityOptions.isReturning()) {
        //这个方法很熟悉,在onResume中分析过
        restoreExitedViews();
        activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
    }
    //创建了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();
    }
}

发现创建了EnterTransitionCoordinator对象,最后调用了startEnter方法。

先分析EnterTransitionCoordinator对象。进入构造方法

//EnterTransitionCoordinator.class
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
        ArrayList sharedElementNames, boolean isReturning, boolean isCrossTask) {
    super(activity.getWindow(), sharedElementNames,
            getListener(activity, isReturning && !isCrossTask), isReturning);
    mActivity = activity;
    mIsCrossTask = isCrossTask;
    setResultReceiver(resultReceiver);
    prepareEnter();
    Bundle resultReceiverBundle = new Bundle();
    resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
    mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
    ****省略部分代码****
}

主要看mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);方法,发送的消息,接收者就是ExitTransitionCoordinator。

//ExitTransitionCoordinator.class
case MSG_SET_REMOTE_RECEIVER:
            stopCancel();
            mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
            if (mIsCanceled) {
                mResultReceiver.send(MSG_CANCEL, null);
                mResultReceiver = null;
            } else {
                notifyComplete();
            }
            break;

此处会执行notifyComplete,同时notifyComplete方法中的isReadyToNotify()为true,所以此处就会进入onSharedElementsArrived回调。到目前为止,A页面的回调已经全部找到。

//ExitTransitionCoordinator.class
protected boolean isReadyToNotify() {
    return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
}

isReadyToNotify方法中mResultReceiver这个对象,只有在页面B发送MSG_SET_REMOTE_RECEIVER这个消息之后,才会赋值。

最后notifyComplete方法中会有这么一行代码resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElementBundle); 内部通过handler发送了一个消息,发送给谁?我猜应该是对应的enter类吧。果然在EnterTransitionCoordinator类中搜到了。

//EnterTransitionCoordinator.class
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        case MSG_TAKE_SHARED_ELEMENTS:
            if (!mIsCanceled) {
                mSharedElementsBundle = resultData;
                onTakeSharedElements();
            }
            break;
        ****省略部分代码****
    }
}

对mSharedElementsBundle赋值,然后调用onTakeSharedElements方法。到这里先暂停下。

接着上面的startEnter方法,然后调用namedViewsReady方法,再调用triggerViewsReady方法。

//EnterTransitionCoordinator.class
private void triggerViewsReady(final ArrayMap sharedElements) {
    ****省略部分代码****
    if (decor == null || (decor.isAttachedToWindow() &&
            (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
        viewsReady(sharedElements);
    } else {
        mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> {
            mViewsReadyListener = null;
            viewsReady(sharedElements);
        });
        decor.invalidate();
    }
}

然后进入复写的viewsReady方法

//EnterTransitionCoordinator.class
@Override
protected void viewsReady(ArrayMap sharedElements) {
    super.viewsReady(sharedElements);
    mIsReadyForTransition = true;
    hideViews(mSharedElements);
    Transition viewsTransition = getViewsTransition();
    if (viewsTransition != null && mTransitioningViews != null) {
        removeExcludedViews(viewsTransition, mTransitioningViews);
        stripOffscreenViews();
        hideViews(mTransitioningViews);
    }
    if (mIsReturning) {
        sendSharedElementDestination();
    } else {
        moveSharedElementsToOverlay();
    }
    if (mSharedElementsBundle != null) {
        onTakeSharedElements();
    }
}

此处有两个注意点,1.super.viewsReady(sharedElements); 2.最后的onTakeSharedElements();
父类的viewsReady,上面已经有分析,会回调onMapSharedElements方法,因为是EnterTransitionCoordinator类,所以是页面B的回调。

重点是onTakeSharedElements方法。首先,刚才暂停的一段代码,也是这个方法。在当前类EnterTransitionCoordinator中搜索此方法,总共就这两处调用。但是这里调用前提是mSharedElementsBundle != null,而mSharedElementsBundle只有在前面的地方赋值,重复粘贴一遍代码,如下

//EnterTransitionCoordinator.class
case MSG_TAKE_SHARED_ELEMENTS:
            if (!mIsCanceled) {
                mSharedElementsBundle = resultData;
                onTakeSharedElements();
            }
            break;

所以,真正执行onTakeSharedElements方法的,是在收到MSG_TAKE_SHARED_ELEMENTS消息之后。继续分析onTakeSharedElements

//EnterTransitionCoordinator.class
private void onTakeSharedElements() {
    ****省略部分代码****
    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 {
        mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
    }
}

这里调用了B页面的onSharedElementsArrived。同时监听的回调中调用了startSharedElementTransition方法。

//EnterTransitionCoordinator.class
private void startSharedElementTransition(Bundle sharedElementState) {
    ****省略部分代码****
    ArrayList rejectedNames = new ArrayList(mAllSharedElementNames);
    rejectedNames.removeAll(mSharedElementNames);
    //创建非共享元素的view
    ArrayList rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
    if (mListener != null) {
        //回调onRejectSharedElements
        mListener.onRejectSharedElements(rejectedSnapshots);
    }
    removeNullViews(rejectedSnapshots);
    //执行非共享元素的动画
    startRejectedAnimations(rejectedSnapshots);
    // 创建共享元素的view,此处会回调页面B的onCreateSnapshotView方法
    ArrayList sharedElementSnapshots = createSnapshots(sharedElementState,
            mSharedElementNames);
    showViews(mSharedElements, true);
    //此处会回调页面B的onSharedElementEnd方法
    scheduleSetSharedElementEnd(sharedElementSnapshots);
    //此处会回调页面B的onSharedElementStart方法
    ArrayList 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);
    //开始Transition
    if (startEnterTransition) {
        startEnterTransition(transition);
    }

    setOriginalSharedElementState(mSharedElements, originalImageViewState);

    if (mResultReceiver != null) {
        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.
                }
            }
        });
    }
}

此处就会调用页面B的其他方法。已经在上方代码中备注。目前为止页面A进入页面B,已经大概的过了一遍。

坑啊,都N久了,还没给出最终的解决方案。莫急,马上。

继续看上面代码的结尾处,mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);发送了个消息,接收者是ExitTransitionCoordinator,然后触发了hideSharedElements方法,最终调用了hideViews方法。

 //ActivityTransitionCoordinator.class
 protected void hideViews(ArrayList views) {
    int count = views.size();
    for (int i = 0; i < count; i++) {
        View view = views.get(i);
        if (!mOriginalAlphas.containsKey(view)) {
            mOriginalAlphas.put(view, view.getAlpha());
        }
        view.setAlpha(0f);
    }
}

又将view的alpha设为了0,即全透明。走了一大圈,终于和onResume方法呼应了。所以解决方案就是,将view的alpha重新设为1。

本文只分析页面A进入页面B的过程,页面B返回页面A的过程,和解决问题没有直接关联,所以这里不做分析。有兴趣的,可以自行分析。

那在哪里设置呢?立马就想到,当然是B页面的onSharedElementEnd方法中通知页面A,将共享元素的view设为alpha = 1。

当然是错误的,因为就算设为了alpha = 1 ,最后又调用了hideViews方法,又设为了0。而且hideViews之后没有提供任何回调的方法。似乎又进入了死胡同。

回到项目中。既然没法知道真正的动画结束的时刻,那只能在其他时刻去将alpha设置为1。因为拖拽,我能知道拖拽开始的事件,所以在页面B拖拽开始的时候,通过Rxbus/EventBus通知页面A,将view的alpha设置为1。好了问题解决。

效果图


SVID_20190515_114941_123.gif

黑人问号脸。怎么在返回动画执行的时候,view又变空白了?
由于篇幅问题,长话短说了。因为返回动画是在页面A上面执行的,并且动画执行的时候,view的alpha又被设为0了。所以,只要在页面A的监听中,如下处理即可。

@Override
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {
    sharedElement.setAlpha(1f);
    return super.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, screenBounds);
}

效果图


SVID_20190515_115018_123.gif

最后再推荐一波DragCloseHelper。具体的解决代码也在对应的demo中。

参考资料
https://www.jianshu.com/p/fa1c8deeaa57
备注:我的分析和文中略有出入,出入的地方在图中用红线标出。原因上面已经分析,当然也有可能是我分析错了,但是不影响大家了解整个过程。如果确定是我分析错了,请联系我,我会改正过来,感谢。

QQ20190515-113801.png

你可能感兴趣的:(Android 共享元素动画分析及背景空白的解决方案)