ConstraintLayout2.0一篇写不完之KeyCycles的秘密
KeyCycle与KeyFrame类似,但是又比KeyFrame复杂,复杂在于KeyFrame只是单帧,而KeyCycle则是在KeyFrame的基础上,增加了周期性的处理,所以,KeyCycle的核心就是周期,KeyCycle决定了在Scene中所有需要重复处理的部分操作,它的核心API如下所示。
- framePosition:作为一个KeyFrame,KeyCycle必须知道在场景的哪一点上进行操作
- motionTarget:指定的View ID
- wavePeriod:周期数量
- waveOffset:起始位置的偏移
- waveShape:Cycle的波形
MotionLayout提供了CycleEditor来帮助开发者编辑KeyCycle,下载地址如下:
直接执行即可,点击file中的parse,就可以将编辑区域的xml转换为波形。
java -jar CycleEditor.jar
分割Scene
在创建KeyCycle之前的第一件事,就是通过使用不同的framePositions把你的Scene分成多部分组合的Partial Scene,接下来就可以通过使用wavePeriod来指定你想要的每个部分的周期数,以及waveShape来指定具体的波形。
wavePeriod是KeyCycle最难理解的一部分,要掌握wavePeriod的定义,就必须先了解Partial Scene,Partial Scene指的是当前指定点的前一个点和后一个点,总共三个点构成的区域,这点非常重要。
在某个framePosition的KeyCycle中指定wavePeriod,其实就是指在这个Partial Scene中,有几个周期的波形来填满这个区域。
但是这里问题又来了,每个framePosition都被周围的framePosition有关,那么wavePeriod不是被重复计算了吗?
没错。。。所以在整个Partial Scene中的wavePeriod是由Partial Scene中所有framePosition的的wavePeriod之和确定的。很绕是不是,是就对了。
这也是为什么KeyCycle有个单独的生成工具的原因,结合KeyCycleEditor,还是比较能理解的。
wavePeriod已经很绕了,但是绕的还在后面。
我们再来看看KeyCycle中指定的具体属性值的含义。
例如,我们在KeyCycle中指定rotation为20,代码如下所示。
<KeyCycle
motion:framePosition="0"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="20"/>
这个rotation为20是什么意思?你以为是当然framePosition的属性值为20吗?太年轻了。。。
其实这个属性值与View在当前framePosition的属性值,并没有直接联系。。。
是不是很奇怪,的确如此,那么这玩意儿到底是干嘛的呢???
这里我们需要转换一下思路,那就是KeyCycle里面设置的一切东西,都是为了画出「波形图」,所以,这些参数的设置,就是为了修改波形图的具体形状。
<KeyFrameSet>
<KeyCycle
motion:framePosition="0"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="0"/>
<KeyCycle
motion:framePosition="50"
motion:target="@+id/button"
motion:wavePeriod="1"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="10"/>
<KeyCycle
motion:framePosition="100"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="30"/>
</KeyFrameSet>
这样一个KeyCycle最后形成的波形图就是这样。
由此可以发现,每个framePosition的属性值,就是为了画出波形图的波峰。
在这个的基础上,waveOffset就好理解了,它的作用就是给framePosition的当前value增加一个初始值,这个初始值同样是为了修改波形。
要干嘛
你说KeyCycle这玩意儿整这么复杂,到底有什么用呢??
我们有了KeyFrame,可以用来添加中间态关键帧,那么还要KeyCycle干嘛呢?
说到这来,就不能不提下Monotonic Spline(单调采样)了,通常的关键帧插值算法都是使用的单调采样,但是这样无法做到曲线的圆滑过渡,就像下图中的绿色曲线,这样四个点使用单调采样,就变成了下面这样的曲线,过渡会非常生硬。
那么为了让曲线圆滑过渡,KeyCycle使用的是Typical Spline(特征采样),就如上图中的紫色曲线,四个点被圆滑的连接了起来。
如果仅仅是为了让曲线能圆滑过渡,那么你就太小看KeyCycle了,不得不说老外做的这些东西,总能在一些你觉得无关紧要的地方,做的非常深入。
KeyCycle的核心在于波形,而波是什么呢?
上面这张图表达了sin和cos的几何含义,也是sin和cos的来源。
说句不像傅里叶变换的话,我们可以将一个View的曲线运动,拆解成多个不同波形运动的叠加。
例如我们对一个View的translationX同时设置sin和cos的KeyCycle,最终形成的运动轨迹,就是一个圆形!
所以,由此及彼,我们可以复合多个属性的同时,通过不同的波形叠加,实现任何你想要的运动轨迹!这TM就牛逼了啊,简直就是傅里叶变换在Android动画中的实现了。
在CycleEditor中,有一些自带的Demo,可以让你充分的了解这个思想,例如下面这个例子。
太复杂了是吗?
CustomWave shape in keyCycle
CL2.1之后,motion:waveShape除了之前定义的sin、cos、bounce这些预设曲线外,你还可以设置自定义的波形曲线,定义方式如下所示。
<KeyCycle motion:waveShape=”spline(0.0, 1.0, -1.0, 0)” />
这就有点牛逼了,本来就很复杂了,这下还来了自定义曲线,再见。
KeyCycle确实比较强大,但是也非常复杂,强烈建议大家使用CycleEditor来学习,KeyCycle这种东西,就像核武器一样,可以不用,但是不能没有。