【翻译】MotionLayout实现折叠工具栏(Part 2)

motionlayout_part2.jpg

一、前言

本篇是续集,第一篇翻译直达链接:【翻译】MotionLayout实现折叠工具栏(Part 1)

本文特点:没有 Kotlin/Java 代码,讲解部分全为 XML 代码,阅读时间短,获取技能: MotionLayout 的入门和使用!发布时间: 8 月 17 号 ,作者: Mark Allison ,原文链接: https://blog.stylingandroid.com/motionlayout-collapsing-toolbar-part-2/

二、正文

谷歌 IO 2018 发布了 ConstraintLayout 2.0 版本,其中最重要的部分就是 MotionLayout 了,这玩意就是一个全新的、超牛的布局动画工具! Nicolas Roard 哥们早已发布了一个关于 MotionLayout 的完美详情介绍,我强烈推荐大家去阅读一下,从中理解 MotionLayout 组件的基础架构。本系列教程中,我会讲解如何使用 MotionLayout 来创建一个我们已经非常熟悉的动画行为:一个折叠工具栏动画( a Collapsing Toolbar )。

通过上一篇文章我们了解了基本的折叠工具栏动画行为,使用的是 MotionLayout ,第一次尝试的效果与在 CoordinatorLayout 中使用 CollapsingToolbarLayout 的效果非常接近。不过有一个细微的小动画在 MotionLayout 中没有实现出来。移动和缩放动画在文字上表现确实已经非常接近,但是背景图片的渐变在最边缘上却没有完全相同。让我们先看下 CoordinatorLayout 版本的实现效果,注意图片在工具栏几乎快要完全折叠之前是不会开始渐变到主色彩动画的:

motionlayout_part2_traditional.gif

现在我们看看 MotionLayout 的实现,我们会发现图片渐变在整个过渡动画中是统一稳定的。也就是说:随着工具栏折叠动画的开始,图片便立刻发生渐变,一直持续到工具栏完全到达折叠状态:

motionlayout_part2_motion_basic.gif

这个问题实际上很容易解决,这要感谢 MotionLayout 的另一个非常重要的特性:关键帧。我们已经讨论过 MotionLayout 是如何在 ConstraintSets 中所定义的固定布局之间进行过渡动画了。而关键帧允许我们在两个固定布局之间定义一个中间点,并对此点的属性值进行操作控制。

我们之前在 ImageView 控件上定义的关于 imageAlpha 属性的过渡动画,设定的是从展开位置的值 255 到折叠位置的值 0 之间进行,同时 MotionLayout 在动画过程中会进行插值运算。因此我们得到的是一个非常平滑的过渡动画,从工具栏开始发生折叠一直到工具栏完全达到折合状态为止。这也很好的解释了我们所看到的在 MotionLayout 中对动画行为的实现。

利用关键帧特性我们甚至可以做到修改相关的行为动画,使得这些行为动画时间在整个过渡动画中往后延迟。为了实现这个目标,我们首先需要在展开状态 ConstraintSet 的定义中删除自定义属性 imageAlpha 字段:

  <ConstraintSet android:id="@+id/expanded">
    <Constraint
      android:id="@id/toolbar_image"
      android:layout_height="200dp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

同时也需要在折叠状态 ConstraintSet 的定义中进行同样的操作:

  <ConstraintSet android:id="@+id/collapsed">
    <Constraint
      android:id="@id/toolbar_image"
      android:layout_height="?attr/actionBarSize"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

这样同时把透明度的渐变动画一起删除了,不过接下来我们会使用一个 KeyFrameSet 来代替它,这个关键帧设置 KeyFrameSet 字段是作为过渡元素的一个子元素:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">
 
  <Transition
    app:constraintSetEnd="@id/collapsed"
    app:constraintSetStart="@id/expanded">
 
    <OnSwipe
      app:dragDirection="dragUp"
      app:touchAnchorId="@id/recyclerview"
      app:touchAnchorSide="top" />
 
    <KeyFrameSet>
      <KeyAttribute
        app:framePosition="60"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="255" />
      </KeyAttribute>
      <KeyAttribute
        app:framePosition="90"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="0" />
      </KeyAttribute>
    </KeyFrameSet>
  </Transition>
  ...
</MotionScene>

这里 KeyFrameSet 包含了两个 KeyAttribute 字段,每一个字段分别定义了指定位置下的一个状态,第一个位于第 60 帧,也就是说整个过渡动画过程中的 60% 的位置,而第二个在 90 的位置,同样的道理,这意味着位于过渡动画的 90% 的位置。这两个字段通过设置 ID 分别指定作用目标控件对象(在这里两个字段都是指定的 @id/toolbar_image )。每一个字段还定义了一个 CustomAttribute 元素,它的意思和我们之前在开头、结尾状态中定义的意思是一样的。

目前来说,发生的情况是:图片的透明度在过渡动画还没有达到 60% 之前是不会发生变化的(也就是至少超过一半的折叠状态下不发生变化),接下来会慢慢开始淡出,直到工具栏达到 90% 折叠时完全透明。

motionlayout_part2_motion_offset_fade.gif

现在已经更加接近我们所见到的 CoordinatorLayout 所实现的标准动画了。不过仍然并非完全一样,但是至少我们能看到,通过这种方式我们可以取得对动画过渡的更好的控制权,如果使用 CoordinatorLayout 来进行这样的调整那会非常的麻烦。

事实上关键帧是非常非常强大的, Nicolas Roard 已经对此作了一个深入介绍。我们在此不会重复 Nicolas Roard 所介绍的那样,相反我们来尝试一些其他的方式并投入使用。

首先我们并不局限于目前仅使用两个关键帧的限制,事实上我们可以创建更多精细动画。甚至使用关键帧我们都能够创建出自定义的渐进曲线来(对于安卓开发者来说也就是所谓的插值)。举个例子,假设我们设置 imageAlpha 的开始和结束值分别是 255 和 0 ,然后在 25% 的位置添加一个关键帧,设置值为 205 ,在 75% 的位置设置另一个关键帧值为 50 。结果会给我们实现一个和加速-减速插值器一样的效果。

更牛逼的是,我们可以在动画进行时对动画进行动态更改。标题文字的移动和缩放在整个过渡动画中是同时进行的,但是通过添加一个单独关键帧后我们可以做到在不更改 ConstraintSets 代码的前提下,也不用改变缩放速度就能让标题文本更快地到达动画最终位置:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">
 
  <Transition
    app:constraintSetEnd="@id/collapsed"
    app:constraintSetStart="@id/expanded">
 
    <OnSwipe
      app:dragDirection="dragUp"
      app:touchAnchorId="@id/recyclerview"
      app:touchAnchorSide="top" />
 
    <KeyFrameSet>
      <KeyAttribute
        app:framePosition="60"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="255" />
      </KeyAttribute>
      <KeyAttribute
        app:framePosition="90"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="0" />
      </KeyAttribute>
      <KeyPosition
        app:type="pathRelative"
        app:framePosition="50"
        app:target="@id/title"
        app:percentX="0.9" />
    </KeyFrameSet>
  </Transition>

以上代码能实现在 50% 的过渡动画进程中完成 90% 的移动效果。最终标题文本会走在工具栏折叠动画之前,接着在折叠完全结束的时候直接回落到正确的位置上:

motionlayout_part2_motion_key_position.gif

虽然这只是弃用 CoordinatorLayout 过渡动画的一个开始,但是恰恰通过这个例子告诉了我们,如何使用关键帧来帮助我们动态地进行过渡动画修改,实现在同样的过渡中产生不同的动画效果。

最后值得一提的是:有时候它还能帮我们实现过渡动画的可视化,我们可以通过开启布局中的 showPaths 属性来实现:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:layoutDescription="@xml/collapsing_toolbar"
  tools:context=".MainActivity"
  tools:showPaths="true"
  app:showPaths="true">
  ...
</androidx.constraintlayout.motion.widget.MotionLayout>

这里的 tools:showPaths="true" 设置如果在 Android Studio 编辑器里配合使用会更爽(这个功能应该会出现在 Android Studio 3.4 的 alpha 版本中)。但是在目前来说,添加 tools:showPaths="true" 这段代码能够让 MotionLayout 计算并显示这三个被过渡动画所影响的视图控件的轨迹路线:标题文本控件(顶部,中心左侧),工具栏的海滩小排屋图片(顶部中心),以及列表 RecyclerView 控件(中心位置):

motionlayout_part2_motion_show_paths.gif

值得注意的是,我们在文本控件上添加的关键帧就是位于左边路径顶部下方的那一个红点。如果你仔细查看标题文本的移动,你会清楚的看到这一行轨迹始终穿行在字母 n 和 g 之间,并且它到达关键点位置要相对快些。这种显示路径的方式有助于我们理解刚才创建的关键帧是如何影响到过渡动画的特定部分的。你只需要记得在最终发布版本中要关闭这个功能——我建议定义一个布尔值资源,在布局中使用,然后你就可以在发布版本时总能设置它为 false 就可以了。

好吧,这次就到这里。即使如此,我相信大多数人还是会认同 MotionLayout 不仅灵活、强大,而且还为设计用户交互控制的布局动画开辟了一个非常有趣的可能性哦。 :sunglasses:

三、总结

本篇的源代码请移步这里

© 2018 , Mark Allison 。保留所有版权。

我的博客地址: http://liuqingwen.me ,欢迎关注我的微信公众号:

IT自学不成才

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

推荐阅读更多精彩内容