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

2018-08-27 by Liuqingwen | Tags: Android 翻译 | Hits

一、前言

本篇是续集,第一篇翻译直达链接:【翻译】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 字段:

res/xml/collapsing_toolbar.xml
1
2
3
4
5
6
7
<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 的定义中进行同样的操作:

res/xml/collapsing_toolbar.xml
1
2
3
4
5
6
7
<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 字段是作为过渡元素的一个子元素:

res/xml/collapsing_toolbar.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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 代码的前提下,也不用改变缩放速度就能让标题文本更快地到达动画最终位置:

res/xml/collapsing_toolbar.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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 属性来实现:

res/layout/activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
<?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自学不成才


Comments: