Android转场动画深度解析(3)

3,242 阅读3分钟

终于到了material design转场动画中最好玩,最有特色的一部分了。我们沿用上一篇的图,不过将跳转Activity的代码作如下更改:

 Intent intent = new Intent(this,BBBActivity.class);
ActivityOptionsCompat activityOptionsCompat =ActivityOptionsCompat.makeSceneTransitionAnimation(this
                , new Pair<View, String>(shared_image, "shared_image_")
                , new Pair<View, String>(shared_text, "shared_text_"));
startActivity(intent, activityOptionsCompat.toBundle());

然后在BBBActivity的布局文件想要设置共享元素的部分设置android:transitionName,值和上个页面中设置的值要一一对应,比如:

 <TextView
            android:id="@+id/tv_show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我是一行文字"
            android:transitionName="shared_text_"/>

当然也可以在代码中设置(注意要在调用时机,不能晚于OnResume):
shared_image.setTransitionName("shared_image_");

这样简单两步,咱们的带共享元素的转场动画就改造完成了:

自定义共享元素动画

自带的共享元素动画很简单,可以通过如下代码定义进入和返回动画:

 getWindow().setSharedElementEnterTransition(Transition transition) 
 getWindow().setSharedElementReturnTransition(Transition transition)

仔细一看这两个方法都只需要一个transition作为动画,所以意味着自定义共享元素动画就是自定义Transition了。套用第一篇自定义的那个直角移动ChangeRect,效果如下:

其他方法

上面我们只用两个方法就完成了一次完整的共享元素进入到返回动画。但其实和普通的转场动画一样,设置共享元素的转场动画有四个,除了上面介绍的还有两个就是

setSharedElementExitTransition
setSharedElementReenterTransition

看命名方式和普通的转场动画非常相似,也就是共享元素离开和重现动画的方法。但是共享元素转场是为了表现两个页面相似内容连贯性而设计的,一组动画就足以完成了。但如果我们都加上后会怎么样呢?为了动画更明显,我们把普通动画设为串行:


可以看到新增的两个并没有生效,通过日志打印也可以卡出这一点:

SharedElementExitTransition和SharedElementReenterTransition开始后立即就结束了。关于这一点,参看stackoverflow上的回答,简单来说这两个动画的设计只是为了作一些初始化而存在的。当我们点击跳转按钮的时候,马上就已经跳到了B(参看上一篇生命周期的分析),而共享元素动画没有所谓的串行机制,会马上执行SharedElementEnterTransition,所以转场动画内部会立即结束掉ExitTransition。而ReenterTransition我们也可以从gif图看到,SharedElementReturnTransition已经完成了动画,将目标View变为目标状态,所以不再进行SharedElementReturnTransition(也因为没必要),所以只进行了普通转场动画的ReenterTransition。

共享元素执行空间

Window中有个关于共享元素的设置setSharedElementsUseOverlay(boolean sharedElementsUseOverlay),我们将其设为false,重启App:


可以看到动画执行流程没有变但是共享元素在移动过程中被遮住了,我们来看源码

  protected void moveSharedElementsToOverlay() {
        if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
            return;
        }
        setSharedElementMatrices();
        int numSharedElements = mSharedElements.size();
        ViewGroup decor = getDecor();
        if (decor != null) {
            boolean moveWithParent = moveSharedElementWithParent();
            Matrix tempMatrix = new Matrix();
            for (int i = 0; i < numSharedElements; i++) {
                View view = mSharedElements.get(i);
                tempMatrix.reset();
                mSharedElementParentMatrices.get(i).invert(tempMatrix);
                GhostView.addGhost(view, decor, tempMatrix);
                ViewGroup parent = (ViewGroup) view.getParent();
                if (moveWithParent && !isInTransitionGroup(parent, decor)) {
                    GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
                    parent.getViewTreeObserver().addOnPreDrawListener(listener);
                    mGhostViewListeners.add(listener);
                }
            }
        }
    }

可以看到,如果getSharedElementsUseOverlay==true(也就是默认状态),系统会得到这个View,然后GhostView.addGhost(view, decor, tempMatrix),放置在decorView的Overlay上,因为是decorView,所以也就是在整个view树结构的最上层。Overlay它是view的最上面的一个透明的层,添加到上面的和view不会被其他View遮挡住。

拓展:共享元素形变动画

前面的动画效果都是操作View原有的一些属性,View的内容没有(或者内容没有改变),所以如果与Svg矢量动画配合一番,会产生怎么样的效果呢:


其实很简单,监听SharedElementEnterTransition和SharedElementReturnTransition动画,在其执行的时候执行矢量动画就行了。在更多关于这个矢量动画实现的东西可以参考这篇文章

写在最后

好了关于Android转场动画的内容完结了,代码已上传gitHub,欢迎指正!