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<View> 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<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)
//动画结束
void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)
//被移除的共享元素,即不需要进行共享元素动画的view
void onRejectSharedElements(List<View> rejectedSharedElements)
//共享元素view和name的键值对
void onMapSharedElements(List<String> names, Map<String, View> sharedElements)
//将view的信息以parcelable的对象类型保存,用于activity之间传递
Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)
 //将parcelable对象重新转成view
View onCreateSnapshotView(Context context, Parcelable snapshot) 
 //共享元素已经拿到
void onSharedElementsArrived(List<String> sharedElementNames, List<View> 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<View, String>[] sharedElements) {
    ****省略部分代码****
    opts.mAnimationType = ANIM_SCENE_TRANSITION;
    ArrayList<String> names = new ArrayList<String>();
    ArrayList<View> views = new ArrayList<View>();
    if (sharedElements != null) {
        //将view和name以键值对的形式保存
        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);
        }
    }
    
    //创建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<String> names,
        ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
    super(window, names, listener, isReturning);
    viewsReady(mapSharedElements(accepted, mapped));
    stripOffscreenViews();
    mIsBackgroundReady = !isReturning;
    mActivity = activity;
}

在进入父类的viewsReady

//ActivityTransitionCoordinator.class
protected void viewsReady(ArrayMap<String, View> 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<String> 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<String> 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<String, View> 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<String, View> 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<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
    rejectedNames.removeAll(mSharedElementNames);
    //创建非共享元素的view
    ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
    if (mListener != null) {
        //回调onRejectSharedElements
        mListener.onRejectSharedElements(rejectedSnapshots);
    }
    removeNullViews(rejectedSnapshots);
    //执行非共享元素的动画
    startRejectedAnimations(rejectedSnapshots);
    // 创建共享元素的view,此处会回调页面B的onCreateSnapshotView方法
    ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
            mSharedElementNames);
    showViews(mSharedElements, true);
    //此处会回调页面B的onSharedElementEnd方法
    scheduleSetSharedElementEnd(sharedElementSnapshots);
    //此处会回调页面B的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);
    //开始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<View> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容