深入理解Content Transition (part 2)

转载自:Android开发技术前线 ( android-tech-frontier )

深入理解Content Transition (part 2)

深入理解Content Transition

这篇文章会深度分析 Content Transitions 和它在 Activity & Fragment Transitions API 中的作用。这篇文章是下面这个系列中的第二篇:

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


Content Transition 是什么?

Content transition 决定了非共享元素 View (也称为 transitioning view) 在
Activity & Fragment 过渡期间是如何进入或退出场景的。出于 Google 新的设计语言
Material Design , content transitions 允许我们协调 Activity/Fragment 中每一个
view 的进入和退出 transition,轻松搞定流畅的屏幕切换动作。
开始使用 Android Lollipop,调用下面这些 Window
Fragment 方法可以通过程序设置 content transitions :

  • setExitTransition() - 当 A 启动 B 时, A 中 views 离开场景的退出过渡动画

  • setEnterTransition() - 当 A 启动 B 时, B 中 views 进入场景的进入过渡动画

  • setReturnTransition() - 当 B 返回 A 时, B 中 views 离开场景的返回过渡动画

  • setReenterTransition() - 当 B 返回 A 时, A 中 views 进入场景的重入过渡动画

(注:A 和 B 是Activity 见 part 1)。

Video 2.1 - Content transitions in the Google Play Games app (as of v2.2). Click to play.


Video 2.1作为示例阐明了在 Google Play Games app 中是如何使
用 content transitions 实现流畅的 activitiy 切换动画。当第二个 activity 启动,
它的进入 content transition 轻轻地从屏幕底部边缘将头像推入场景。按下返回按钮后,
第二个 activity 的返回 content transition 将图层分成上下两块,分别推出屏幕。


目前为止我们对 content transitions 的分析仅仅停留在表面,一些重要的问题仍然没有涉及。
例如 Content Transition 在底层是如何实现的?都有哪些类型的 Transition 对象可以使用?框架是如何确定哪些 view
是 transitioning view? 在 content transitions 中一个 ViewGroup 和它的子视图能不能当成一个整体
执行动画?下面我们就来一个一个解答这些问题。


Content Transitions 底层深入

上篇文章我们说过 Transition 的两个主要职责分别是获取目标视图的开始结束状态和创建这两个状态间的过渡动画。同样,框架必须先改变每个 transitioning view 的可见性并将状态信息交给 content
transition ,它才能创建过渡动画。更准确的说,当 Activity A 启动 Activity B 将会出现以下事件:<a id="1" href="#b1">(1)</a>

  1. Activity A 调用 startActivity()

    • 框架首先会遍历 A 的 view 层次结构,确定当 A 的退出 transition 运行后有哪些
      transitioning views 会退出场景。
    • A 的退出 transition 捕获 A 中 transitioning views 的起始状态。
    • 框架将 A 中所有 transitioning views 设置为不可见
    • 在下一个画面中,A 的退出 transition 捕获 A 中所有 transitioning views 的结束状态。
    • A 的退出 transition 比较每一个 transitioning view 开始和结束状态的不同,
      并基于这些信息创建一个 Animator,最后运行 Animator 将所有 transitioning views
      移出场景。
  2. Activity B 被启动

    • 框架遍历 B 的 view 层次结构, 确定当 B 的进入 transition 运行后有哪些
      transitioning views 会进入场景。
    • B 的进入 transition 捕获 B 中 transitioning views 的起始状态。
    • 框架将 B 中所有 transitioning views 设置为可见
    • 在下一个画面中,B 的进入 transition 捕获 B 中所有 transitioning views 的结束状态。
    • B 的进入 transition 比较每一个 transitioning view 开始和结束状态的不同,
      并基于这些信息创建一个 Animator,最后运行 Animator 将所有 transitioning views
      移入场景。

框架通过在可见不可见之间切换每个 transitioning view 的可见性来保证
content transition 能够获得用来构建目标动画所需要的状态信息。
显然所有的 content Transition 对象至少要能够获取和记录每个 transitioning view
起始和结束状态的可见性。还好 Visibility 这个抽象类已经提供了这个功能 :
需要创建并返回 (让 view 进入/退出场景的) AnimatorVisibility
子类只需要实现 onAppear()onDisappear() 这两个工厂方法。
从 API 21 开始,有三个已经写好的 Visibility 实现( Fade, SlideExplode),可以使用它们来构建 Activity 和 Fragment 的 content transitions。
有必要的话也可以自己实现 Visibility 类达到想实现的效果。后边的文章会有具体介绍。


Transitioning Views 和 Transition Groups

直到现在,我们已经假设 content transitions 操作一组叫做 transitioning views 的非共享 view 。
在这节中,我们将探讨 Transition 框架如何确定哪些 View 是非共享 View,以及如何使用 transition group 深度定制框架

Transition 开始前,框架会在 Activity 窗口(或 Fragment) 的视图层上执行一个递归的搜索,用来
构建 transitioning views 的集合。这个搜索通过对图层的根视图递归调用重写的ViewGroup#captureTransitioningViews 方法启动,部分源码如下:

/** @hide */
@Override
public void captureTransitioningViews(List<View> transitioningViews) {
    if (getVisibility() != View.VISIBLE) {
        return;
    }
    if (isTransitionGroup()) {
        transitioningViews.add(this);
    } else {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.captureTransitioningViews(transitioningViews);
        }
    }
}

Video 2.2 - A simple Radiohead app that illustrates a potential bug involving transition groups and WebViews. Click to play.


这个递归调用很简单: 框架遍历树的每一层,直到找到一个可见的 leaf view (子视图)或者一个 transition group。Transition groups 本质上允许我们在 Activity/Fragment 的 transition
期间将全部 ViewGroups 当作一个整体执行过渡动画。如果一个 ViewGroup
isTransitionGroup () <a id="2" href="#b2">(2)</a>方法返回值为 true,它和它的子视图会被当作一
个整体来执行过渡动画。否则,这个递归搜索会继续执行下去, 这个 ViewGroup 的子视图在动画期间
会执行自己的独立的过渡动画。搜索最终会返回一个全部由 content transition
执行动画的 transitioning views 集合 。<a id="3" href="#b3">(3)</a>


上面的 Video 2.1 展示了 transition groups 的效果。在 enter transition ,
用户头像是作为一个单独的 View 进入屏幕,return transition 时却是和包含它的 parent
ViewGroup 一起消失。在 Google Play Games 里可能用了一个 transition group
来实现在返回前一个 activity 时,让当前场景拦腰斩断的效果。


有时 transition groups 还被用来修复 Activity/Fragment transitions 中诡异的 bugs。
例如,Video 2.2中: 调用 Activity 显示了一个电台司令专辑图的网格布局,
被调用 Activity 展示了一个背景标题图,一个共享元素专辑封面图还有一个 WebView
这个 App 使用了一个和 Google Play Games app 类似的 return transition,将背景图和底部的
WebView 分别推出屏幕。然而这里有个小故障导致 WebView 不能流畅的退出屏幕。

好吧,错误在哪呢?原来 WebView 是一个 ViewGroup,因此在默认情况下 WebView 不会被当作 transitioning view
的。当 return transition 被执行时,WebView 会被完全忽略,直到过渡动画结束才会被移除屏幕。
找到问题就好解决了,只要在 return transition 开始前调用 webView.setTransitionGroup(true)
就能修复这个bug。


结语

总之,这篇文章讲了三个重点:

  • Content transition 决定了 Activity/Fragment 中非共享元素视图(被称为 transitioning views)
    在 Activity/Fragment transition 期间如何进入或退出场景。
  • Content transitions 被触发是因为它的 transitioning views 可见性改变 ,并且应该总是继承
    Visibility 这个抽象类。
  • Transition groups 可以让我们在 content transition 期间将 ViewGroups
    当作一个整体执行过渡动画。

希望这篇文章能够帮到你,欢迎留下评论~


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

推荐阅读更多精彩内容