Android 共享元素動(dòng)畫分析及背景空白的解決方案

背景

前段時(shí)間寫了一篇Android 仿微信朋友圈圖片拖拽返回,有朋友指出為什么在拖拽的時(shí)候捏浊,發(fā)現(xiàn)上一個(gè)頁面點(diǎn)擊的圖片是空白的皮官,可以看下效果圖叭爱。

SVID_20190515_114901_123.gif

出現(xiàn)問題的本能反應(yīng)撮躁,先對(duì)比下微信朋友圈的效果,發(fā)現(xiàn)沒問題买雾。[手動(dòng)黑人問號(hào)臉]
后來無意中發(fā)現(xiàn)馒胆,當(dāng)手機(jī)休眠喚醒之后,這個(gè)問題就沒有了凝果。那就說明在onResume中的部分代碼對(duì)view做了處理。

onResume分析

既然發(fā)現(xiàn)onResume是沒問題的睦尽,所以進(jìn)入該方法器净。(注意,全篇都是基于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;
}

因?yàn)槭窃诠蚕碓匕l(fā)現(xiàn)的問題当凡,那就很明顯山害,主要就看mActivityTransitionState.onResume(this, isTopOfTask());這行代碼。繼續(xù)追蹤

//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);
    }
}

其實(shí)就兩個(gè)關(guān)鍵方法沿量,restoreExitedViews();和restoreReenteringViews(); 那就分別看下這兩個(gè)方法做了什么浪慌。

restoreExitedViews,字面翻譯意思就是朴则,恢復(fù)已經(jīng)退出的view权纤。

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

很簡(jiǎn)單,只有一行有效代碼乌妒,繼續(xù)跟進(jìn)汹想,進(jìn)入ExitTransitionCoordinator.class。

//ExitTransitionCoordinator.class
public void resetViews() {
    ViewGroup decorView = getDecor();
    if (decorView != null) {
        //如果decorView不為空撤蚊,則強(qiáng)制將動(dòng)畫結(jié)束
        TransitionManager.endTransitions(decorView);
    }
    if (mTransitioningViews != null) {
        //如果有過渡動(dòng)畫的view的話古掏,就開始顯示view
        showViews(mTransitioningViews, true);
        setTransitioningViewsVisiblity(View.VISIBLE, true);
    }
    showViews(mSharedElements, true);
    mIsHidden = true;
    if (!mIsReturning && decorView != null) {
        decorView.suppressLayout(false);
    }
    //從遮罩層上移除共享元素
    moveSharedElementsFromOverlay();
    //清除狀態(tài)
    clearState();
}

關(guān)鍵方法1.showViews,2.setTransitioningViewsVisiblity侦啸,3.moveSharedElementsFromOverlay槽唾。其實(shí)看字面意思就是重新顯示view。不妨在跟蹤看下光涂。

進(jìn)入父類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);
    }
}

本以為這個(gè)方法主要做的是setVisibility庞萍,結(jié)果發(fā)現(xiàn)主要做了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設(shè)為1,即不透明葵蒂,
setTransitioningViewsVisiblity 將view重新setVisiblity交播,
moveSharedElementsFromOverlay 從遮罩層中移除共享元素

其實(shí)到這里就已經(jīng)知道,為什么onResume之后就沒問題了践付。因?yàn)関iew setVisiblity 和 setAlpha了秦士。但是還是看下restoreReenteringViews方法。

//ActivityTransitionState.class
private void restoreReenteringViews() {
    if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
            !mEnterTransitionCoordinator.isCrossTask()) {
        //關(guān)鍵代碼
        mEnterTransitionCoordinator.forceViewsToAppear();
        mExitingFrom = null;
        mExitingTo = null;
        mExitingToView = null;
    }
}

繼續(xù)進(jìn)入關(guān)鍵代碼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();
    }
    ****省略部分代碼****
}

其實(shí)會(huì)發(fā)現(xiàn)永高,和上面分析的restoreExitedViews方法差不多隧土,基本都已經(jīng)分析過了。所以命爬,這個(gè)問題到目前為止曹傀,其實(shí)已經(jīng)算結(jié)束了。只要在共享元素動(dòng)畫結(jié)束之后饲宛,再手動(dòng)的調(diào)一次onResume方法即可皆愉。但是,onResume方法太重了艇抠,還沒見過解決方法用的這么重的方法的幕庐。那就繼續(xù)分析吧。

共享元素分析

略長家淤,對(duì)源碼分析不感興趣的异剥,可以直接滑到最后。
首先絮重,共享元素的動(dòng)畫監(jiān)聽冤寿,可以通過setExitSharedElementCallback 和 setEnterSharedElementCallback監(jiān)聽。其中共有7個(gè)方法青伤,如下

//動(dòng)畫開始
void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)
//動(dòng)畫結(jié)束
void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)
//被移除的共享元素疚沐,即不需要進(jìn)行共享元素動(dòng)畫的view
void onRejectSharedElements(List<View> rejectedSharedElements)
//共享元素view和name的鍵值對(duì)
void onMapSharedElements(List<String> names, Map<String, View> sharedElements)
//將view的信息以parcelable的對(duì)象類型保存,用于activity之間傳遞
Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)
 //將parcelable對(duì)象重新轉(zhuǎn)成view
View onCreateSnapshotView(Context context, Parcelable snapshot) 
 //共享元素已經(jīng)拿到
void onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener)

我們不妨先打印下log潮模,看下調(diào)用順序亮蛔。
注意:A進(jìn)入B的過程,A使用setExitSharedElementCallback退出監(jiān)聽擎厢,B使用setEnterSharedElementCallback進(jìn)入監(jiān)聽究流。
結(jié)果如下

A進(jìn)入B
A:
onMapSharedElements
onCaptureSharedElementSnapshot
onSharedElementsArrived
B:
onMapSharedElements
onSharedElementsArrived
onRejectSharedElements
onCreateSnapshotView
onSharedElementStart
onSharedElementEnd

B返回到A
B:
onMapSharedElements
A:
onMapSharedElements
onCaptureSharedElementSnapshot
B:
onCreateSnapshotView
onSharedElementEnd//沒看錯(cuò),先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開始动遭,一步步跳轉(zhuǎn)芬探,進(jìn)入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以鍵值對(duì)的形式保存
        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);
        }
    }
    
    //創(chuàng)建ExitTransitionCoordinator
    ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
            callback, names, names, views, false);
    ****省略部分代碼****
    return exit;
}

主要就是將view和name以鍵值對(duì)的形式保存,并且創(chuàng)建了ExitTransitionCoordinator對(duì)象厘惦,進(jìn)入此對(duì)象

//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;
}

在進(jìn)入父類的viewsReady

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

成功找到第一個(gè)回調(diào)onMapSharedElements偷仿。所以ActivityOptionsCompat.makeSceneTransitionAnimation哩簿,主要就是將view和對(duì)應(yīng)的name保存到ExitTransitionCoordinator對(duì)象中,期間會(huì)執(zhí)行onMapSharedElements回調(diào)酝静。

然后分析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();
                }
            }
        });
    }
}

主要就是兩個(gè)方法节榜,1.moveSharedElementsToOverlay,2.startTransition别智。方法1理解字面意思即可宗苍。主要還是方法2。
因?yàn)閙Activity不為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的監(jiān)聽

//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;
}

這里會(huì)進(jìn)入重寫的sharedElementTransitionComplete讳窟,

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

此處會(huì)進(jìn)入captureSharedElementState 方法以及super.sharedElementTransitionComplete();方法。
先進(jìn)入父類的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)回調(diào)丽啡。

同時(shí)super.sharedElementTransitionComplete();會(huì)進(jìn)入startInputWhenTransitionsComplete,再進(jìn)入onTransitionsComplete硬猫,最后進(jìn)入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();
        }
    }
}

但是因?yàn)榇颂巌sReadyToNotify()為false补箍,所以這個(gè)方法等于沒有執(zhí)行。所以到目前為止浦徊,A頁面已經(jīng)沒法繼續(xù)分析了。

開始分析頁面B天梧。頁面B會(huì)經(jīng)過onCreate盔性,onStart等方法。找到performStart方法呢岗,再找到mActivityTransitionState.enterReady(this); 進(jìn)入enterReady

//ActivityTransitionState.class
public void enterReady(Activity activity) {
    ****省略部分代碼****
    ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
    ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
    if (mEnterActivityOptions.isReturning()) {
        //這個(gè)方法很熟悉冕香,在onResume中分析過
        restoreExitedViews();
        activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
    }
    //創(chuàng)建了EnterTransitionCoordinator對(duì)象
    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();
    }
}

發(fā)現(xiàn)創(chuàng)建了EnterTransitionCoordinator對(duì)象,最后調(diào)用了startEnter方法后豫。

先分析EnterTransitionCoordinator對(duì)象悉尾。進(jìn)入構(gòu)造方法

//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);方法,發(fā)送的消息挫酿,接收者就是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;

此處會(huì)執(zhí)行notifyComplete,同時(shí)notifyComplete方法中的isReadyToNotify()為true早龟,所以此處就會(huì)進(jìn)入onSharedElementsArrived回調(diào)惫霸。到目前為止,A頁面的回調(diào)已經(jīng)全部找到葱弟。

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

isReadyToNotify方法中mResultReceiver這個(gè)對(duì)象壹店,只有在頁面B發(fā)送MSG_SET_REMOTE_RECEIVER這個(gè)消息之后,才會(huì)賦值芝加。

最后notifyComplete方法中會(huì)有這么一行代碼resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElementBundle); 內(nèi)部通過handler發(fā)送了一個(gè)消息硅卢,發(fā)送給誰?我猜應(yīng)該是對(duì)應(yīng)的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;
        ****省略部分代碼****
    }
}

對(duì)mSharedElementsBundle賦值脉顿,然后調(diào)用onTakeSharedElements方法。到這里先暫停下抬旺。

接著上面的startEnter方法弊予,然后調(diào)用namedViewsReady方法,再調(diào)用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();
    }
}

然后進(jìn)入復(fù)寫的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();
    }
}

此處有兩個(gè)注意點(diǎn)汉柒,1.super.viewsReady(sharedElements); 2.最后的onTakeSharedElements();
父類的viewsReady,上面已經(jīng)有分析责鳍,會(huì)回調(diào)onMapSharedElements方法碾褂,因?yàn)槭荅nterTransitionCoordinator類,所以是頁面B的回調(diào)历葛。

重點(diǎn)是onTakeSharedElements方法正塌。首先,剛才暫停的一段代碼恤溶,也是這個(gè)方法乓诽。在當(dāng)前類EnterTransitionCoordinator中搜索此方法,總共就這兩處調(diào)用咒程。但是這里調(diào)用前提是mSharedElementsBundle != null鸠天,而mSharedElementsBundle只有在前面的地方賦值,重復(fù)粘貼一遍代碼帐姻,如下

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

所以稠集,真正執(zhí)行onTakeSharedElements方法的,是在收到MSG_TAKE_SHARED_ELEMENTS消息之后饥瓷。繼續(xù)分析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);
    }
}

這里調(diào)用了B頁面的onSharedElementsArrived剥纷。同時(shí)監(jiān)聽的回調(diào)中調(diào)用了startSharedElementTransition方法。

//EnterTransitionCoordinator.class
private void startSharedElementTransition(Bundle sharedElementState) {
    ****省略部分代碼****
    ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
    rejectedNames.removeAll(mSharedElementNames);
    //創(chuàng)建非共享元素的view
    ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
    if (mListener != null) {
        //回調(diào)onRejectSharedElements
        mListener.onRejectSharedElements(rejectedSnapshots);
    }
    removeNullViews(rejectedSnapshots);
    //執(zhí)行非共享元素的動(dòng)畫
    startRejectedAnimations(rejectedSnapshots);
    // 創(chuàng)建共享元素的view呢铆,此處會(huì)回調(diào)頁面B的onCreateSnapshotView方法
    ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
            mSharedElementNames);
    showViews(mSharedElements, true);
    //此處會(huì)回調(diào)頁面B的onSharedElementEnd方法
    scheduleSetSharedElementEnd(sharedElementSnapshots);
    //此處會(huì)回調(diào)頁面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.
                }
            }
        });
    }
}

此處就會(huì)調(diào)用頁面B的其他方法晦鞋。已經(jīng)在上方代碼中備注。目前為止頁面A進(jìn)入頁面B棺克,已經(jīng)大概的過了一遍鳖宾。

坑啊,都N久了逆航,還沒給出最終的解決方案鼎文。莫急,馬上因俐。

繼續(xù)看上面代碼的結(jié)尾處拇惋,mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);發(fā)送了個(gè)消息周偎,接收者是ExitTransitionCoordinator,然后觸發(fā)了hideSharedElements方法撑帖,最終調(diào)用了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設(shè)為了0,即全透明胡嘿。走了一大圈蛉艾,終于和onResume方法呼應(yīng)了。所以解決方案就是衷敌,將view的alpha重新設(shè)為1勿侯。

本文只分析頁面A進(jìn)入頁面B的過程,頁面B返回頁面A的過程缴罗,和解決問題沒有直接關(guān)聯(lián)助琐,所以這里不做分析。有興趣的面氓,可以自行分析兵钮。

那在哪里設(shè)置呢?立馬就想到舌界,當(dāng)然是B頁面的onSharedElementEnd方法中通知頁面A掘譬,將共享元素的view設(shè)為alpha = 1。

當(dāng)然是錯(cuò)誤的呻拌,因?yàn)榫退阍O(shè)為了alpha = 1 葱轩,最后又調(diào)用了hideViews方法,又設(shè)為了0柏锄。而且hideViews之后沒有提供任何回調(diào)的方法酿箭。似乎又進(jìn)入了死胡同复亏。

回到項(xiàng)目中趾娃。既然沒法知道真正的動(dòng)畫結(jié)束的時(shí)刻,那只能在其他時(shí)刻去將alpha設(shè)置為1缔御。因?yàn)橥献疲夷苤劳献ч_始的事件,所以在頁面B拖拽開始的時(shí)候耕突,通過Rxbus/EventBus通知頁面A笤成,將view的alpha設(shè)置為1。好了問題解決眷茁。

效果圖


SVID_20190515_114941_123.gif

黑人問號(hào)臉炕泳。怎么在返回動(dòng)畫執(zhí)行的時(shí)候,view又變空白了上祈?
由于篇幅問題培遵,長話短說了浙芙。因?yàn)榉祷貏?dòng)畫是在頁面A上面執(zhí)行的,并且動(dòng)畫執(zhí)行的時(shí)候籽腕,view的alpha又被設(shè)為0了嗡呼。所以,只要在頁面A的監(jiān)聽中皇耗,如下處理即可南窗。

@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。具體的解決代碼也在對(duì)應(yīng)的demo中郎楼。

參考資料
http://www.reibang.com/p/fa1c8deeaa57
備注:我的分析和文中略有出入万伤,出入的地方在圖中用紅線標(biāo)出。原因上面已經(jīng)分析箭启,當(dāng)然也有可能是我分析錯(cuò)了壕翩,但是不影響大家了解整個(gè)過程。如果確定是我分析錯(cuò)了傅寡,請(qǐng)聯(lián)系我放妈,我會(huì)改正過來,感謝荐操。

QQ20190515-113801.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芜抒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子托启,更是在濱河造成了極大的恐慌宅倒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屯耸,死亡現(xiàn)場(chǎng)離奇詭異拐迁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疗绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門线召,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人多矮,你說我怎么就攤上這事缓淹。” “怎么了塔逃?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵讯壶,是天一觀的道長。 經(jīng)常有香客問我湾盗,道長伏蚊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任格粪,我火速辦了婚禮躏吊,結(jié)果婚禮上肺孵,老公的妹妹穿的比我還像新娘。我一直安慰自己颜阐,他們只是感情好平窘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凳怨,像睡著了一般瑰艘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肤舞,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天紫新,我揣著相機(jī)與錄音,去河邊找鬼李剖。 笑死俺叭,一個(gè)胖子當(dāng)著我的面吹牛偏竟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惠猿,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼闹啦!你這毒婦竟也來了劫窒?” 一聲冷哼從身側(cè)響起湃窍,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宰僧,沒想到半個(gè)月后材彪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琴儿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年段化,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片造成。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡显熏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谜疤,到底是詐尸還是另有隱情佃延,我是刑警寧澤现诀,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布夷磕,位于F島的核電站,受9級(jí)特大地震影響仔沿,放射性物質(zhì)發(fā)生泄漏坐桩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一封锉、第九天 我趴在偏房一處隱蔽的房頂上張望绵跷。 院中可真熱鬧膘螟,春花似錦、人聲如沸碾局。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽净当。三九已至内斯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間像啼,已是汗流浹背俘闯。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忽冻,地道東北人真朗。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像僧诚,于是被迫代替她去往敵國和親遮婶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容