深入理解共享元素变换(Shared Element Transition)-上

本文将深入分析共享元素变换(shared element transition)以及它在Activity 和Fragment Transitions API中所扮演的角色。这是这Transition系列文章的第三部分:

第三章(本章)将分为两部分:上着重底层的原理,下着重api的具体实现,比如延迟某些元素变换的重要性以及SharedElementCallbacks的实现。

什么是共享元素变换?

元素共享式变换(shared element transition)决定了共享的view元素从一个Activity/Fragment 到另一个Activity/Fragment t的切换中是如何动画变化的。共享元素在被调用Activity进入和返回时播放动画,共享元素在进入和返回时的变换效果通过window和Fragment的如下方法来设置:

进入:

setSharedElementEnterTransition()

设置在B进入时播放的动画,共享元素以A中的位置作为起始,B中的位置为结束来播放动画。

返回:

setSharedElementReturnTransition()

设置在B返回A时的动画,共享元素以B中的位置作为起始,A中的位置为结束来播放动画。

注意,Activity Transition API 也可以使用 setSharedElementExitTransition() 和setSharedElementReenterTransition()方法分别设置共享元素的exit 和reenter 变换。但是一般来讲这是不必要的。如果你想看先关的例子,可以查看这篇博客this blog post.至于为什么Fragment中没有共享元素的exit 和reenter 变换,请查看George Mount在stackoverflow上的回答:this StackOverflow post

8942111.gif

上图演示了google play music应用中的共享元素变换效果。变换包含了两个共享的view元素:一个ImageView以及他的父亲CardView。ImageView在两个activity之间无缝的动画切换,而CardView则是渐渐的扩展到界面上。

在第一章中我们简单的介绍了这个话题,这篇文章则是更深入的去分析共享元素变换(shared element transition)。共享元素变换的原理是什么?有哪些共享元素变换效果可用?共享元素变换动画是如何绘制的,又是在哪里绘制的?接下来的小节中我们将一一回答。

共享元素变换揭秘

从前两篇文章中我们知道,一个变换(Transition )主要有两方面的职责:

捕获view开始和结束状态以及创建一能在两个状态间渐变的动画。共享元素变换没有什么不同。在共享元素变换开始之前,必须首先捕获每个共享元素的开始和结束状态(调用activity以及被调用activity中的位置、大小、外观),有了这些信息才能决定每个共享元素的入场动画。

深入理解Content Transition 中类似,framework的共享元素变换是通过运行时改变其属性实现的,当Activity A 调用 Activity B ,发生的事件流如下:

1.Activity A调用startActivity(), Activity B被创建,测量,同时初始化为半透明的窗口和透明的背景颜色。

2.framework重新分配每个共享元素在B中的位置与大小,使其跟A中一模一样。之后,B的进入变换(enter transition)捕获到共享元素在B中的初始状态。

3.framework重新分配每个共享元素在B中的位置与大小,使其跟B中的最终状态一致。之后,B的进入变换(enter transition)捕获到共享元素在B中的结束状态。

4.B的进入变换(enter transition)比较共享元素的初始和结束状态,同时基于前后状态的区别创建一个Animator(属性动画对象)。

5.framework 命令A隐藏其共享元素,动画开始运行。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

与内容变换(content transition)取决于view的可见性不同(visibility),共享元素变换取决于每个共享元素的位置、大小以及外观。在api 21中,框架层提供了几个Transition 的实现,可以用于定义共享元素在场景中的切换效果。

ChangeBounds -捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

ChangeTransform -  捕获共享元素的缩放(scale)与旋转(rotation)属性 ,然后播放缩放(scale)与旋转(rotation)属性变化动画。

ChangeClipBounds -  捕获共享元素clip bounds,然后播放clip bounds变化动画。

ChangeImageTransform -  捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType 属性平滑过度。

@android:transition/move -  将上述所有变换同时进行的一个TransitionSet 。就如在第一章中所讲的一样,如果共享元素的进入和返回变换没有特别声明,框架将使用它作为默认的变换。

我们可以看到,共享元素变换并不是真正实现了两个activity或者Fragment之间元素的共享,实际上我们看到的几乎所有变换效果中(不管是B进入还是B返回A),共享元素都是在B中绘制出来的。Framework没有真正试图将A中的某个元素传递给B,而是采用了不同的方法来达到相同的视觉效果。A传递给B的是共享元素的状态信息。B利用这些信息来初始化共享View元素,让它们的位置、大小、外观与在A中的时候完全一致。当变换开始的时候,B中除了共享元素之外,所有的其他元素都是不可见的。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

使用共享元素的 Overlay

最后,我们需要讨论一下shared element overlay这个概念才算是对共享元素变换的绘制过程有了一个完整的了解。

虽然不是非常明显的可以看到,共享元素默认其实是绘制在整个view树结构的最上层,在一个叫ViewOverlay的东西上面。你可能没听说过ViewOverlay,他是4.3之后才有的一个新类,它是view的最上面的一个透明的层,添加到ViewOverlay上面的Drawable和view可以被绘制到任何东西的上面,甚至是ViewGroup的子元素。这似乎可以解释为什么framework 会选择ViewOverlay来作为共享元素变换的绘制空间了。

-关于ViewOverlay,除了官方解释还可以看看这篇文章:ViewOverlay与animation介绍 

虽然共享元素默认是绘制在ViewOverlay上面,但是framework 还是提供了关闭这个选项的功能,调用Window的setSharedElementsUseOverlay(false) 方法。这样主要是为了防止万一有这样的开发需要。如果你选择了关闭overlay,那么请注意这是有一定副作用的。

568.gif

在上图的效果中,我们运行了两次不同方式的动画,第二次便是关闭overlay之后的效果,我们可以明显的看到这导致了一个问题。

总之除非有一万个理由,否则不要关闭shared element overlay。

总结

这篇文章涵盖了如下几个要点:

1.元素共享式变换(shared element transition)决定了共享的view元素从一个Activity/Fragment 到另一个Activity/Fragment t的切换中是如何动画变化的。

2.共享元素变换取决于每个共享元素的位置、大小以及外观。

3.共享元素默认其实是绘制在整个view树结构的最上层,在一个叫ViewOverlay的东西上面。

4.共享元素变换并不是真正实现了两个activity或者Fragment之间元素的共享,Framework采用了不同的方法来达到相同的视觉效果。

注:作者在github上上传了一个transaction的demo,虽然没有在文章中明确说明,但我觉得就是这系列文章的相关demo:activity-transitions

本文翻译自 Shared Element Transitions In-Depth (part 3a) 

欢迎转载,但请保留本文链接 http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2394.html