文章目录
系列文章为:
Android新控件MotionLayout介绍(一)
Android新控件MotionLayout介绍(二)
Android新控件MotionLayout介绍(三)
本文标题:
在MotionLayout中定义运动路径
介绍
在ConstraintLayout 2.0库中我们介绍了MotionLayout,这是一种主要是注重动画的布局。先前的文章中对它有了一些浏览,我非常鼓励你先把它们读完再来读这篇文章(就是上面三篇文章)。
这个MotionLayout的动画系统主要是根据两个状态之间的插值完成的(典型的,我们用的比较多的,组件的位置或者组件的大小),特别是在约束布局中使用约束条件,或者使用view的属性。在开始到结束的两种状态之间的任意转换,都能通过触摸来进行驱动。MotionLayout这个系统将会对你的转换有非常友好的支持。
上面说的状态,MotionLayout也会支持关键帧,我们在第二篇文章有过简单的介绍,这篇文章中我们将深入讨论一下关键帧。关键帧非常的好用,同时它绝对是一个专业的工具;及时你平时用不到,或者用得蛮少的。
但是始终要记住,在你的应用中使用Motion应该有意义,而不是滥用它。
但是,你需要有额外的能力来定义你的转换,使用MotionLayout中的关键帧能够丰满你的应用。就像你看到的,这里有很多:
- keyframes 关键帧
- Position Keyframes 位置关键帧
- Arc Motion 弧形运动
- Easing [可以理解为时间模型]
- Attribute keyframes 属性关键帧
- Cycle Keyframes & TimeCycle Keyframes 循环关键帧 & 单位时间内循环关键帧
关键帧: 按时的约定(a Rendez-vous in Time)
关键帧可以允许你在两个差值状态中,在给定的时间点,指定你的组件做出一个转换。
在MotionLayout中可以支持多个不同类型的关键帧:
- 位置关键帧 keyPosition
- 属性关键帧 KeyAttribute
- 循环关键帧 KeyCycle
- 时间关键帧 KeyTimeCycle
各种类型的关键帧都是独立的,不相互影响。而且也没有必要在同一个点定义全部类型的关键帧(但是你也不能在同一个点定义相同类型的关键帧)
一些常见的属性
所有的关键帧(位置、属性、循环、时间循环)共享着共同的属性:
- motion:framePosition 定义关键帧在什么时候起作用(0 - 100) 0为开始时 100为完成时
- motion:target 作用的组件是谁
- motion:transtionEasing 时间模型(默认是是线性的,这个和属性动画的插值器是类似的含义)
- motion:curveFit 组件在动画的过程中,路径时是曲线(默认) 还是 直线。曲线路径会使得转换时更加的平滑,当然你也可以定义成直线的。
位置关键帧
位置关键帧应该是我们最平常的关键帧了。它能让你在屏幕上为组件在转换的过程中定义路径。举个例子,下面的简单动画来一个:
开始的状态是(相对于父组件是 左 下);结束的状态是 (相对于父组件是 右 上),运动的路径是简单的直线插值,组件将会以直线的形式运动:
为了介绍这个位置关键帧,我们可以改变它的运动路径,由直线变为曲线:
通过添加更多的关键帧能够让你实现复杂的运动路径:
这个复杂的路径代码为:
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentX="0.75"
motion:percentY="-0.3"
motion:framePosition="25"
motion:target="@id/button"/>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentY="-0.4"
motion:framePosition="50"
motion:target="@id/button"/>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentX="0.25"
motion:percentY="-0.3"
motion:framePosition="75"
motion:target="@id/button"/>
</KeyFrameSet>
为什么是位置关键帧?
也许你会问你自己,如果在复杂的布局系统中,约束规则已经让你摆放了组件的位置,那关键帧有什么意义呢?这里有一些解释:
- 布局规则表达的是静止的状态,但是关键帧表达的是 瞬态的修改。(它只是针对某一个瞬间的修改)
- 关键帧比布局规则更加的轻量级,更容易计算。
- 关键帧能够接触到组件的运动轨迹,但是布局规则只能指定组件的位置,以及组件之间的相对位置。
使用MotionScene也是可以让布局规则接触到组件的运动轨迹的,所以当你的运动过程中有多个静止的状态时,你可以使用MotionScene来替代关键帧完成你的目标。转换过程只能在代码中进行(改变可用的监听器)。
XML 表示
关键帧(Keyframes)在 <KeyFrames
> 中属性中,它被包含在 <Transition
> 中。位置关键帧使用<KeyFrames
> 表示,必须至少包含:
- target: 作用的目标
- framePosition: 从0 - 100,什么时候关键帧起作用
- keyPositionType: 坐标系统(coordinate system )用的,取值为:parentRelative、deltaRelative、pathRelative,下面将仔细介绍:
- percentX/ percentY : (x,y)系统中的坐标位置
<Transition ...>
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
</KeyFrameSet>
</Transition>
不同的坐标系统
在MotionLayout中,开始和结束状态允许你有负责的位置变化。作为约束规则(ConstraintSets),拥有了ConstraintLayout的全部能力,在不同分辨率、屏幕方向、语言等不同的状态下,(约束规则这个)系统都会正确处理这些变化。
在这样的系统下,位置关键帧将会非常有用,在类似的自适应方式中,使用关键帧能够为组件自己找到相对位置,我们不能将他们定位到固定位置。
为了达到这个要求,也为了保持关键帧系统的轻量性,我们想出了一个灵活的方法 — 每一个关键帧的位置被表示为所给定的坐标系中的(x,y)坐标:
motion:percentX = “float”
motion:percentY = “float”
这就意味着这些坐标依赖着我们使用的不同类型的坐标系统,parentRelative、deltaRelative 或者 pathRelative.
每一个关键帧的位置都是独立的,每一个坐标都可以用它们独立的坐标系,完全与其他关键帧无关。
parentRelative
这个坐标系与父容器有关。这是一种非常直接和全凭直觉的方式,来表示了关键帧所在的位置,我们基本上这个就足够了。大多相对于父容器的运动,我们一般都会使用它。
这个坐标系只依赖父容器的大小,并不依赖与组件开始或者结束位置,你可能见过有些关键帧最终停留在一个理想的位置(当然这个位置是相对于开始/结束位置的)。
deltaRelative
定义开始和结束位置为坐标的起点和终点。这个坐标系表达了开始点到结束点之间的距离的百分比。
和parentRelative
类似,也是一种相对于直觉的坐标系,也会生成好的结果。如果你想要你的组件在水平方向或者竖直方向上开始移动或者结束移动,这个相对坐标系将会非常有用。
当然这里也有一个潜在的问题----它是基于组件不同的的开始位置和结束位置,如果开始位置和结束位置非常的靠近(或者二者重合),那么作用在它们身上的关键帧将会发生改变。举个例子,如果一个组件保持相同的高度从屏幕的左边移动到左边,我们使用 deltaRelative
中的percentY
作用到关键帧 将不会有任何的影响(因为percentY一直不变)。
pathRelative
最后一种坐标系被定义为相对于开始状态到结束状态而作为直接路劲。它主要解决了deltaRelative
坐标系中的问题 — 即使组件垂直方向没有位移,仍然可以使用pathRelative
来创建一个位置关键帧来偏离轨道,即使负的坐标也是被支持的。这是一个更加专业的坐标系,但有时也会特别的有用。这里有一个例子:实现一个曲线的形状(就像一个“S”型),即使结束点改变了,这个形状依旧保持原来的样子。(这个例子,将会在下面展示,文章本身有些不容易读,图到处都是…)
弧形运动 (Arc Motion)
在Material Design
中非常典型的运动就是弧形运动。通过MotionLayout来实现弧形运动的一个方式是,在开始状态和技术状态之间添加正确的位置关键帧,其实已经在以前的章节中讲过。
在ConstraintLayout 2.0 aplha 2
版本中,我们引入了一个新的方式来实现完美弧形运动— 或者它更容易使用。你需要添加属性motion:pathMotionArc
在开始的约束规则中(也就是那个startState), 来切换默认的直线运动还是曲线运动。
来看一个简单的例子,开始的状态是屏幕的右下方,结束的位置是屏幕上方的中间位置。添加这个属性就能曲线运动了。
motion:pathMotionArc = "startHorizontal"
我们来切换一下参数:
motion:pathMotionArc = "startVertical"
就是翻转弧形的起始位置:
当然了,你可以继续使用位置关键帧,来构建更加复杂的弧形路径。下面来个例子:
上面的例子是通过在竖直方向的中点 添加一个关键帧,来达到目的:
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
在这种方案中,位置关键帧可以被用来改变弧形的运动方向,通过设置motion:pathMotionArc
属性,这个属性值可以是filp
(翻转当前方向), none
(直接转变成直线运动), 或者明确的指定 startHorizontal
或者 startVertical
。
下面来一个翻转的例子:
具体代码为:
<KeyPosition
motion:keyPositionType="parentRelative"
motion:pathMotionArc="flip"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
我motion:pathMotionArc改成none
之后,就接下来的一段,将会走直线运动了。
<KeyPosition
motion:keyPositionType="parentRelative"
motion:pathMotionArc="none"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
那么结果为:
Easing [我理解为时间模型]
前几个例子中,我们提供了不同的机制允许你定义一个运动路劲。然而动画不仅仅是路径,时间也很至关重要。
就像位置关键帧被指定了时间,你可以定义组件运动得是快是慢。
但是在一个单独的场景中 — 在开始/结束之间, 或者关键帧之间 — 时间的插值是线性变化的。
但是你可以通过指定一个缓和曲线来改变它(时间插值模型), 使用motion:transitionEasing
属性,你可以将这个属性作用到约束规则(ConstraintSets ) 或者关键帧(keyframes)上面。motion:transitionEasing
可以取下面几个值:
- cubic(float, float,. float, float), 参数x1,x2,y1,y2代表了贝塞尔曲线控制的两个点, 起点是0,0 到 1,1
- 使用关键字:standard, acclerate. decelerate,它们以前提前被定义了(你直接拿过来用即可)
标准的easing
加速的easing
用的比较多的场景是,将组件移除屏幕。
减速的easing
这个场景比较多的是,将组件移入屏幕。
键属性(KeyAttribute)
关键帧属性让在动画的过程中,在给定点的上,更改组件的属性。换句话说,它很像位置关键帧,但是它是作用在属性上而非位置上面。
上面的代码可以在MotionScene 文件中使用KeyAttribute
元素来指定.
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
</KeyFrameSet>
对于keyPosition
,我们需要指定framePosition(关键帧什么时候执行)和一个target(关键帧作用在哪个对象上面)
支持的属性
下列这些开箱即用的view属性,都支持:
- android:visibility
- android:alpha
- android:elevation
- android:rotation
- android:rotationX
- android:rotationY
- android:scaleX
- android:scaleY
- android:translationX
- android:translationY
- android:translationZ
鉴于你的应用支持的SDK level,有些属性将不会被支持:
- android:elevation SDK 21 引入
- android:translationZ SDK 21 引入
自定义属性
你可以在ConstraintSets
和KeyAttribute
中使用自定义属性,通过添加一个子节点(CustomAttribute
), 这个节点需要一个名称(attributeName), 需要有getter/setter方法,属性值被应用到(动画插值)中,属性值应该指定为下面的几种:
- customColorValue :应用颜色值
- customColorDrawableValue : 应用drawable
- customIntegerValue : 应用整型值
- customFloatValue :
- customStringValue
- customDimension
- customBoolean
举个例子,来一个自定义的例子,对应上面动画的例子(颜色和大小都变化的例子):
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
</Constraint>
</ConstraintSet>
结论
本文介绍了MotionLayout中最常见的关键帧和路径规范。我们将在本系列的第5部分(还没有出来)中讨论KeyCycle和KeyTimeCycle关键帧,它们引入了一种非常强大的方法,可以向属性(基于路径或时间的)添加扰动(作为波形),允许各种有趣但可预测的循环效果(反弹、抖动、脉动等)。
本文的例子将在github中杯找到:
https://github.com/googlesamples/android-ConstraintLayoutExamples