深入理解 Shared Element Transition (part 3a)

深入理解 Shared Element Transition (part 3a)

深入理解共享元素 Transition

这篇文章会深度分析共享元素 transitions 和它在 Activity & Fragment Transitions API 中的作用。这篇文章是下面这个系列中的第三篇:

Part 3 会分成三个部分: part3a 介绍 共享元素 transitions 的底层操作,part3b 和 part3c
主要关注 API 的具体实现细节,例如推迟某些 共享元素 transition 的重要性和如何实现
SharedElementCallbacks

我们首先会总结下在 part 1 中提到的关于 共享元素 transition 的知识点,然后说一说在 Android Lollipop 中是怎样使用它来构建合适的过渡动画。

什么是 共享元素 Transition ?

共享元素 transition 决定了 共享元素 视图(也叫做主角视图)在
Activity/Fragment 场景过渡时的动画效果。共享元素 的动画
在被调用 Activity/Fragment 的 进入/返回 共享元素 transition
<a id="1" href="#b1">(1)</a> 中执行,
可以通过下面的Window/Fragment 方法设置:

  • setSharedElementEnterTransition() - B进入 共享元素 transition ,执行将
    共享元素视图 从 A 中的起始位置移动到它在 B 中的最终位置的动画。
  • setSharedElementReturnTransition() - B返回 共享元素 transition ,执行将
    共享元素视图 从 B 中起始位置移动到它在 A 中的最终位置的动画。

Video 3.1 展示了在 Google Play Music 中是怎样使用共享元素 transition
的。这个 transition 包含两个元素:一个 ImageView和它的父视图 CardView
Transition 期间,CardView 会扩展到全屏或收缩回原状,
ImageView 能在这两个 Activity 里无缝的衔接。

part1 里只是简单的介绍了下这个话题,这篇文章将会对 共享元素 transition
做更深度的分析。例如 共享元素 Transition 在底层是如何实现的?都有哪些类型的 Transition 对象可以使用? Transition 期间 共享元素视图 是在哪里怎样绘制的?接下来的几章里
我们会逐个解答这些问题。

深入共享元素 Transitions 底层

之前的文章已经介绍过 Transition 的两个主要任务分别是获取目标视图的 开始&结束 状态和创建这两个状态间视图的过渡动画(Animator)。共享元素 Transition 也一样:
在创建动画前需要捕获每一个
共享元素视图的起始和结束状态(就是共享元素在 调用/被调用 Activity/Fragment
里的位置,大小和外观)。有了这些信息 共享元素 Transition 就可以确定每一个 共享元素
视图应该执行的动画。

Content Transitions 的底层相似,框架通过在运行时明确的
更改每个 共享元素视图 的属性将这个状态信息提供给 共享元素 Transition 。
更准确地说 Activity A 启动 Activity B 时将会出现以下事件:<a id="2" href="#b2">(2)</a>


  1. Activity A 调用 startActivity() 构造,测量,布局了一个
    最初背景色为透明的半透明窗口 Activity B
  2. 框架将 B 中每一个共享元素视图复位到对应的原来在 A 中时的位置,接着 B 的进入 transition 捕获 B 中所有共享元素视图的起始状态。
  3. 框架将 B 中每一个共享元素视图复位到对应的在 B 的最终位置,接着 B 的进入 transition 捕获 B 中所有共享元素视图的结束状态。
  4. B 的进入 transition 比较所有共享元素视图的起始和结束状态,根据它们的不同
    创建一个 Animator
  5. 框架命令 A 隐藏共享元素视图,并运行返回的 AnimatorB 中的
    共享元素视图到位之后,B 的窗口背景在 **A **上逐渐显示,直到 B
    完全的显示出来,transition 运行完毕。

Content transitions 是根据每个过渡视图的可见性变化来调节的,共享元素 transition
是根据每个共享元素视图的位置,大小和外观的变化来调节的
。从 API 21 开始,框架提供了
几个自定义共享元素场景切换动画的 Transition 实现。

  • ChangeBounds - 捕获共享元素布局边界根据不同构造动画。
    ChangeBounds 在共享元素 Transition 中经常使用,大多数共享元素在两个
    Activity/Fragment 间会有大小 或/和 位置不同。
  • ChangeTransform - 捕获共享元素缩放和角度,
    根据不同构建动画<a id="3" href="#b3">(3)</a>。
  • ChangeClipBounds - 捕获共享元素的 clip bounds
    (剪辑边界) ,根据不同构建动画。
  • ChangeImageTransform - 捕获共享元素 ImageView 的
    变换矩阵( transform matrices) ,根据不同构建动画。结合 ChangeBounds
    可以让 ImageView
    无缝的改变大小,形状和 ImageView.ScaleType
  • @android:transition/move - 一个 TransitionSet ,同时执行上面四种
    transition 。在 part 1 里提到过,如果没有明确的声明 进入/返回 共享元素
    transition ,框架会默认运行这个 transition。

在上面的例子中,我们还可以发现 共享元素视图实例并没有在 Activities/Fragments 间
“共享”
。事实上,进入/返回 共享元素 transitions期间,用户看到的绝大多数东西都是在
B 的 content view 中绘制的。框架并没有从 AB
传递 共享元素视图实例,
而是采用了不同的方法实现相同的视觉效果。当 A 启动 B ,框架收集 A
中共享元素的所有相关信息,并传递给 B。接下来 B 使用这些信息初始化
共享元素视图的起始状态(它们在 A 中时对应的大小,位置和外观)。Transition
开始时,B 中除了共享元素视图外的所有东西都被初始化为对用户不可见。Tansition
的执行过程中,框架将 B 的 Activity 窗口逐渐显示,直到 B
中共享元素结束动画窗口变为不透明。


使用共享元素 Overlay <a id="4" href="#b4">(4)</a>

最后,如果想要完全理解共享元素 transition 的运作,我们必须先说说共享元素 overlay。
可能不是很明显,共享元素默认是在整个窗口视图层的顶层 ViewOverlay
上绘制
。简单介绍下 ,
ViewOverlay 这个类是在 API 18 中为了方便在视图层顶层
绘制引入的。添加到视图 ViewOverlay 之中的Drawable
和 view (甚至是一个 ViewGroup 的子类) ,
将会被绘制在视图的最上层。这就解释了框架为什么
默认选择在窗口视图层的 ViewOverlay 中绘制共享元素。
共享元素视图应该是贯穿整个 transition 的焦点;
如果 transitioning views 意外的绘制在共享元素之上就会
破坏这个效果<a id="5" href="#b5">(5)</a>。


虽然共享元素默认绘制在共享元素的 ViewOverlay 之中,但是
框架也提供了关闭 overlay 的方法,只要调用
Window#setSharedElementsUseOverlay(false)
就可以了。如果你关闭了 overlay
,要留意这样做可能会引起的副作用。例如,Video 3.2
执行了一个简单的共享元素 transition 两次,
一次开启和一次关闭 共享元素 overlay 。第一次达到了预期想要的结果,
第二次关闭 overlay 后运行的效果不理想。Transition view 从底部向上移入
调用 Activity 的 content view 时挡住了部分 共享元素 ImageView 。虽然
可以改变在 View 上绘制视图的顺序或者通过在共享元素 parent 里调用
setClipChildren(false) 这些旁门左道来修复问题,但是与可能带来的维护问题
相比真是得不偿失。总之,除非你感觉必须要关掉共享元素 overlay 才能达到你想要的效果,
其他情况尽量不要关闭它,这样会保持代码简洁,并且共享元素 transition 效果更引人注目。

结语

综上所诉,这篇文章讲了三个重点:

  1. 共享元素 transition 确定 共享元素视图(主角视图) 从一个 Activity/Fragment 移动到
    另一个其间场景过渡的动画。
  2. 共享元素 transition 是根据每一个 共享元素视图 的位置,大小,和外观的变化调节的。
  3. 共享元素默认是绘制在窗口视图的顶层 ViewOverlay 上面的。

希望这篇文章对你有所帮助 ~


1 Note that the Activity Transition API gives you the ability to also specify exit and reenter shared element transitions using the setSharedElementExitTransition() and setSharedElementReenterTransition() methods, although doing so is usually not necessary. For an example illustrating one possible use case, check out this blog post. For an explanation why exit and reenter shared element transitions are not available for Fragment Transitions, see George Mount's answer and comments in this StackOverflow post. ↩

2 A similar sequence of events occurs during the exit/return/reenter transitions for both Activities and Fragments. ↩

3 One other subtle feature of ChangeTransform is that it can detect and handle changes made to a shared element view's parent during a transition. This comes in handy when, for example, the shared element's parent has an opaque background and is by default selected to be a transitioning view during the scene change. In this case, the ChangeTransform will detect that the shared element's `parent is being actively modified by the content transition, pull out the shared element from its parent, and animate the shared element separately. See George Mount's StackOverflow answer for more information. ↩

4 Note that this section only pertains to Activity Transitions. Unlike Activity Transitions, shared elements are not drawn in a ViewOverlay by default during Fragment Transitions. That said, you can achieve a similar effect by applying a ChangeTransform transition, which will have the shared element drawn on top of the hierarchy in a ViewOverlay if it detects that its parent has changed. See this StackOverflow post for more information. ↩

5 Note that one negative side-effect of having shared elements drawn on top of the entire view hierarchy is that this means it will become possible for shared elements to draw on top of the System UI (such as the status bar, navigation bar, and action bar). For more information on how you can prevent this from happening, see this Google+ post. ↩

  1. Activity Transition API 也提供了
    setSharedElementExitTransition()setSharedElementReenterTransition()
    这两个方法来设置 退出/重入 共享元素过渡,虽然通常来说是不必要的。
    这篇文章介绍了一个可能会遇到的用例。在这个 stackoverflow
    提问中 George Mount 的回答解释了为什么 退出/重入 共享元素 transition 在
    Fragment Transitions 中不可用。 <a id="b1" href="#1">↩</a>

  2. Activities 和 Fragments 的 退出/返回/重入 transition 过程中出现事件序列相似 <a id="b2" href="#2">↩</a>

  3. ChangeTransform 还有一个超赞的特性,它可以检测并处理共享元素父视图过渡期间
    的改变。当共享元素父视图有一个不透明背景,在场景变换过程中默认被选为
    transitioning view 时,ChangeTransform 就有了用武之地。如果它检测出
    共享元素父视图已被 content transition 更改,就会将共享元素提取出来,单独执行
    共享元素的动画。StackOverflow answer 这里有 George Mount
    的详细说明。
    <a id="b3" href="#3">↩</a>

  4. 注意,这部分只与 Activity Transition 有关。和Activity Transition 不同,Fragment Transition
    期间共享元素默认不在 ViewOverlay 中绘制。尽管如此,你仍可以使用 ChangeTransform
    transition 来达到相似的效果,如果它检测到父视图改变了,就会把共享元素绘制在
    ViewOverlay 的顶层。StackOverflow 这里有更多信息。
    <a id="b4" href="#4">↩</a>

  5. 注意,将共享元素绘制在整个图层最顶层也有一些负面效果。有可能
    会将共享元素绘制在 System UI 之上(比如 status bar, navigation bar还有 action bar)。
    解决方法看这里 Google+ post
    <a id="b5" href="#5">↩</a>


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

推荐阅读更多精彩内容