从源码了解共享元素的具体实现逻辑

2,500 阅读5分钟

关键名词

  • ExitTransitionCoordinator、EnterTransitionCoordinator

退出和进入动画协调者,主要负责动画执行的消息交互(使用handler通讯),同时包含一些其他功能的封装(如保存view信息),旧activity使用ExitTransitionCoordinator,新activity使用EnterTransitionCoordinator

  • GhostView

每个view中都有一个mGhostView,如果不为空则跳过当前view的绘制,只绘制GhostView

  • Transition

转场动画的最终效果实现,本质是对属性动画的封装,通过对比前后view的属性值进行变换

总流程

  • 通过makeSceneTransitionAnimation生成ExitTransitionCoordinator,保存view信息,等待EnterTransitionCoordinator信息
  • 启动新activity,在performStart中做准备工作,准备工作主要有两步
    • 创建EnterTransitionCoordinator,将当前activity置为透明,发送MSG_SET_REMOTE_RECEIVER信息给ExitTransitionCoordinator
    • 调用startEnter,在对应共享view中添加ghostView
  • ExitTransitionCoordinator接收MSG_SET_REMOTE_RECEIVER,触发onSharedElementsArrived(activity A的ExitSharedElementCallback),发送MSG_TAKE_SHARED_ELEMENTS
  • EnterTransitionCoordinator接收MSG_TAKE_SHARED_ELEMENTS,触发onSharedElementsArrived(activity B的EnterSharedElementCallback),然后通过transition执行动画

监听回调顺序

Activity A只触发ExitSharedElementCallback,activity B只触发EnterSharedElementCallback

exit activity A: onMapSharedElements
exit activity A: onPause
exit activity A: onCaptureSharedElementSnapshot
enter activity B: onStart
enter activity B: onResume
exit activity A: onSharedElementsArrived
enter activity B: onMapSharedElements
enter activity B: onSharedElementsArrived
enter activity B: onRejectSharedElements
enter activity B: onCreateSnapshotView
enter activity B: onSharedElementStart
enter activity B: onSharedElementEnd
exit activity A: onStop

源码

生成数据bundle

在我们使用共享元素动画时,需要先通过ActivityOptionsCompat.makeSceneTransitionAnimation(...)传入activity,sharedView,SharedName,生成共享元素的bundle数据

Bundle bundle=ActivityOptionsCompat.
  makeSceneTransitionAnimation(MainActivity.this, navToNextWithAnim, "tran").toBundle();
makeSceneTransitionAnimation
  • 判断是否支持转场动画
  • 保存共享view信息
  • 生成ExitTransitionCoordinator,并保存到当前activity的mActivityTransitionState中
static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
        ActivityOptions opts, SharedElementCallback callback,
        Pair<View, String>[] sharedElements) {
    if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
        opts.mAnimationType = ANIM_DEFAULT;
        return null;
    }
    ...
    ArrayList<View> views = new ArrayList<View>();
    if (sharedElements != null) {
        for (int i = 0; i < sharedElements.length; i++) {
            ...
            views.add(sharedElement.first);
        }
    }

    ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
            callback, names, names, views, false);
    ...
    if (activity == null) {
        opts.mExitCoordinatorIndex = -1;
    } else {
        opts.mExitCoordinatorIndex =
                activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
    }
    return exit;
}

ExitTransitionCoordinator是退出过渡的协调者,继承于ActivityTransitionCoordinator,而ActivityTransitionCoordinator又继承于ResultReceiver。其中ResultReceiver包含mHandler和mReceiver,主要用于消息的发送和接收;ActivityTransitionCoordinator是ExitTransitionCoordinator和EnterTransitionCoordinator的公共类,包含它们的共用方法。

ExitTransitionCoordinator
  • 保存其他数据信息(如window,监听等)
  • 触发退出监听的onMapSharedElements方法
  • 生成回调消息监听,等候EnterTransitionCoordinator的消息

ActivityTransitionCoordinator的构造方法

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

ExitTransitionCoordinator的构造方法

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(触发onMapSharedElements)

protected void viewsReady(ArrayMap<String, View> sharedElements) {
    sharedElements.retainAll(mAllSharedElementNames);
    if (mListener != null) {
        mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
    }
    setSharedElements(sharedElements);
    if (getViewsTransition() != null && mTransitioningViews != null) {
        ViewGroup decorView = getDecor();
        if (decorView != null) {
            decorView.captureTransitioningViews(mTransitioningViews);
        }
        mTransitioningViews.removeAll(mSharedElements);
    }
    setEpicenter();
}

消息监听

protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        case MSG_SET_REMOTE_RECEIVER:
            ...
            break;
        case MSG_HIDE_SHARED_ELEMENTS:
            ...
            break;
        case MSG_START_EXIT_TRANSITION:
            ...
            break;
        case MSG_SHARED_ELEMENT_DESTINATION:
            ...
            break;
        case MSG_CANCEL:
            ...
            break;
    }
}

新activity

之后执行activity的启动流程,并在新activity的performStart中调用mActivityTransitionState.enterReady(this),初始化数据,创建EnterTransitionCoordinator,并调用startEnter,触发进入的onMapSharedElements

final void performStart(String reason) {
		....
    mActivityTransitionState.enterReady(this);
    ...
}
public void enterReady(Activity activity) {
    ...
    mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
            resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
            mEnterActivityOptions.isCrossTask());
    ...
    if (!mIsEnterPostponed) {
        startEnter();
    }
}
EnterTransitionCoordinator
  • 将当前activity置为透明
  • 发送MSG_SET_REMOTE_RECEIVER给ExitTransitionCoordinator
  • 生成回调消息监听,等待其他操作

EnterTransitionCoordinator的构造方法

public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
        ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
    super(activity.getWindow(), sharedElementNames,
            getListener(activity, isReturning && !isCrossTask), isReturning);
    ...
    prepareEnter();
    ...
    mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
    ...
}

prepareEnter主要是将当前activity置为透明

protected void prepareEnter() {
    ViewGroup decorView = getDecor();
    if (mActivity == null || decorView == null) {
        return;
    }
    if (!isCrossTask()) {
        mActivity.overridePendingTransition(0, 0);
    }
    if (!mIsReturning) {
        mWasOpaque = mActivity.convertToTranslucent(null, null);
        Drawable background = decorView.getBackground();
        if (background == null) {
            background = new ColorDrawable(Color.TRANSPARENT);
            mReplacedBackground = background;
        } else {
            getWindow().setBackgroundDrawable(null);
            background = background.mutate();
            background.setAlpha(0);
        }
        getWindow().setBackgroundDrawable(background);
    } else {
        mActivity = null; // all done with it now.
    }
}

回调监听

protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        case MSG_TAKE_SHARED_ELEMENTS:
            ...
            break;
        case MSG_EXIT_TRANSITION_COMPLETE:
            ...
            break;
        case MSG_CANCEL:
            ...
            break;
        case MSG_ALLOW_RETURN_TRANSITION:
            ...
            break;
    }
}

startEnter会触发EnterTransitionCoordinator的viewInstancesReady,viewInstancesReady再调用triggerViewsReady,triggerViewsReady最终触发EnterTransitionCoordinator的viewsReady

startEnter()
  • 调用父类(ActivityTransitionCoordinator)的viewsReady,触发进入的onMapSharedElements
  • 隐藏共享元素对应的views
  • 调用moveSharedElementsToOverlay,将共享元素移动到顶层view
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();
    }
}

moveSharedElementsToOverlay会将共享view取出来,然后依次添加GhostView

protected void moveSharedElementsToOverlay() {
    ...
    if (decor != null) {
        boolean moveWithParent = moveSharedElementWithParent();
        Matrix tempMatrix = new Matrix();
        for (int i = 0; i < numSharedElements; i++) {
            View view = mSharedElements.get(i);
            if (view.isAttachedToWindow()) {
                tempMatrix.reset();
                mSharedElementParentMatrices.get(i).invert(tempMatrix);
                GhostView.addGhost(view, decor, tempMatrix);
                ...
            }
        }
    }
}

GhostView本质也是一个view,相当于view的附属产物,如果view中存在mGhostView实例,则只会绘制GhostView,而跳过原view的绘制。 而GhostView具体的绘制内容则是调用绑定的view的绘制

//View Class
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
		...
}
//GhostView Class
protected void onDraw(Canvas canvas) {
    if (canvas instanceof RecordingCanvas) {
        RecordingCanvas dlCanvas = (RecordingCanvas) canvas;
        mView.mRecreateDisplayList = true;
        RenderNode renderNode = mView.updateDisplayListIfDirty();
        if (renderNode.hasDisplayList()) {
            dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
            dlCanvas.drawRenderNode(renderNode);
            dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
        }
    }
}

addGhost中会以decorView的大小创建一个新的父布局(FrameLayout),并添加新建的GhostView

public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
    if (!(view.getParent() instanceof ViewGroup)) {
        throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
    }
    ViewGroupOverlay overlay = viewGroup.getOverlay();
    ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
    GhostView ghostView = view.mGhostView;
    int previousRefCount = 0;
    if (ghostView != null) {
        ...
    }
    if (ghostView == null) {
        if (matrix == null) {
            matrix = new Matrix();
            calculateMatrix(view, viewGroup, matrix);
        }
        ghostView = new GhostView(view);
        ghostView.setMatrix(matrix);
        FrameLayout parent = new FrameLayout(view.getContext());
        parent.setClipChildren(false);
        copySize(viewGroup, parent);
        copySize(viewGroup, ghostView);
        parent.addView(ghostView);
        ArrayList<View> tempViews = new ArrayList<View>();
        int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
        insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
        ghostView.mReferences = previousRefCount;
    } else if (matrix != null) {
        ghostView.setMatrix(matrix);
    }
    ghostView.mReferences++;
    return ghostView;
}

ExitTransitionCoordinator接收MSG_SET_REMOTE_RECEIVER消息

会做取消的特殊判断,比如进入一个新界面,又立马退出的情况,正常流程会直接触发notifyComplete()

protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        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()
  • 触发退出的onSharedElementsArrived
  • 发送MSG_TAKE_SHARED_ELEMENTS给EnterTransitionCoordinator
protected void notifyComplete() {
    if (isReadyToNotify()) {
        if (!mSharedElementNotified) {
            ....
            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();
        }
    }
}

EnterTransitionCoordinator接收MSG_TAKE_SHARED_ELEMENTS消息

调用onTakeSharedElements

protected void onReceiveResult(int resultCode, Bundle resultData) {
    switch (resultCode) {
        case MSG_TAKE_SHARED_ELEMENTS:
            if (!mIsCanceled) {
                mSharedElementsBundle = resultData;
                onTakeSharedElements();
            }
            break;
        ...
    }
}
onTakeSharedElements
  • 触发进入的onSharedElementsArrived
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);
    }
}

触发之后调用onSharedElementsReady,最终执行startSharedElementTransition

public void onSharedElementsArrived(List<String> sharedElementNames,
        List<View> sharedElements, OnSharedElementsReadyListener listener) {
    listener.onSharedElementsReady();
}
startSharedElementTransition
  • 获取被拒绝的elements
  • 触发进入的onRejectSharedElements
  • 执行被拒绝的elements动画,通过属性动画实现,透明度由1变0
  • 创建Snapshots,并回调进入的onCreateSnapshotView
  • scheduleSetSharedElementEnd中添加decorView监听,在onPreDraw中回调onSharedElementEnd
  • 设置共享元素的状态
  • 触发进入的onSharedElementStart
  • 最后通过transition执行动画
private void startSharedElementTransition(Bundle sharedElementState) {
    ViewGroup decorView = getDecor();
    if (decorView == null) {
        return;
    }
    // Remove rejected shared elements
    ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
    rejectedNames.removeAll(mSharedElementNames);
    ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
    if (mListener != null) {
        mListener.onRejectSharedElements(rejectedSnapshots);
    }
    removeNullViews(rejectedSnapshots);
    startRejectedAnimations(rejectedSnapshots);

    // Now start shared element transition
    ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
            mSharedElementNames);
    showViews(mSharedElements, true);
    scheduleSetSharedElementEnd(sharedElementSnapshots);
    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);

    if (startEnterTransition) {
        startEnterTransition(transition);
    }

    setOriginalSharedElementState(mSharedElements, originalImageViewState);

    if (mResultReceiver != null) {
        // We can't trust that the view will disappear on the same frame that the shared
        // element appears here. Assure that we get at least 2 frames for double-buffering.
        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.
                }
            }
        });
    }
}

scheduleSetSharedElementEnd中会添加decorView的onPreDraw监听,然后在onPreDraw中触发进入的onSharedElementEnd

protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
    final View decorView = getDecor();
    if (decorView != null) {
        OneShotPreDrawListener.add(decorView, () -> {
            notifySharedElementEnd(snapshots);
        });
    }
}
protected void notifySharedElementEnd(ArrayList<View> snapshots) {
    if (mListener != null) {
      	mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
    }
}

设置共享元素状态时,默认有elevation、位置(上下左右)、大小。并且对ImageView做了特殊判断,会多添加scaleType和matrix

private void setSharedElementState(View view, String name, Bundle transitionArgs,
        Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
    Bundle sharedElementBundle = transitionArgs.getBundle(name);
    if (sharedElementBundle == null) {
        return;
    }

    if (view instanceof ImageView) {
        int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
        if (scaleTypeInt >= 0) {
            ImageView imageView = (ImageView) view;
            ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
            imageView.setScaleType(scaleType);
            if (scaleType == ImageView.ScaleType.MATRIX) {
                float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
                tempMatrix.setValues(matrixValues);
                imageView.setImageMatrix(tempMatrix);
            }
        }
    }

    float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
    view.setTranslationZ(z);
    float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
    view.setElevation(elevation);

    float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
    float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
    float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
    float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);

    if (decorLoc != null) {
        left -= decorLoc[0];
        top -= decorLoc[1];
        right -= decorLoc[0];
        bottom -= decorLoc[1];
    } else {
        // Find the location in the view's parent
        getSharedElementParentMatrix(view, tempMatrix);
        tempRect.set(left, top, right, bottom);
        tempMatrix.mapRect(tempRect);

        float leftInParent = tempRect.left;
        float topInParent = tempRect.top;

        // Find the size of the view
        view.getInverseMatrix().mapRect(tempRect);
        float width = tempRect.width();
        float height = tempRect.height();

        // Now determine the offset due to view transform:
        view.setLeft(0);
        view.setTop(0);
        view.setRight(Math.round(width));
        view.setBottom(Math.round(height));
        tempRect.set(0, 0, width, height);
        view.getMatrix().mapRect(tempRect);

        left = leftInParent - tempRect.left;
        top = topInParent - tempRect.top;
        right = left + width;
        bottom = top + height;
    }

    int x = Math.round(left);
    int y = Math.round(top);
    int width = Math.round(right) - x;
    int height = Math.round(bottom) - y;
    int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
    view.measure(widthSpec, heightSpec);

    view.layout(x, y, x + width, y + height);
}

Transition的效果主要是通过属性动画来实现,通过createAnimator创建Animator,默认实现有ChangeBounds,ChangeClipBounds,ChangeImageTransform,ChangeScroll,ChangeTransform,Visibility

void playTransition(ViewGroup sceneRoot) {
    ...
    createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
    runAnimators();
}
protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
        TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
        ArrayList<TransitionValues> endValuesList) {
    ...
    for (int i = 0; i < startValuesListCount; ++i) {
        TransitionValues start = startValuesList.get(i);
        TransitionValues end = endValuesList.get(i);
        ...
        if (isChanged) {
            ...
            Animator animator = createAnimator(sceneRoot, start, end);
            if (animator != null) {
                // Save animation info for future cancellation purposes
                ...
                if (animator != null) {
                    ...
                    mAnimators.add(animator);
                }
            }
        }
    }
    ...
}