动画曲线天天用,你能自己整一个吗?看完这篇你就会了!
前言
最近在写动画相关的篇章,经常会用到 Curve
这个动画曲线类,那这个类到底怎么实现的?如果想自己来一个自定义的动画曲线该怎么弄?本篇我们就来一探究竟。
Curve 类定义
查看源码, Curve
类定义如下:
看上去好像没定义什么, 实际这里只是做了两个处理,一个是明确的数据类型为 double
,另一个是对 transform
做了重载,也只是对参数 t 做了特殊处理,保证参数 t 的范围在 0-1 之间,且起点值 0.0 和终点值 1.0 不被转换函数转换。主要定义在上一层的ParametricCurve
。文档是建议子类重载transformInternal
方法,那我们就继续往上看ParametricCurve
这个类的实现,代码如下:
可以看到,实际上 transform
方法除了做参数合法性验证以外,其实就是调用了transformInternal
方法,因此子类必须要实现该方法,否则会抛出UnimplementedError
异常。
实例解析
上面的源码可以看到,关键在于参数 t
。这个参数 t
代表什么呢?注释里说的是:
Returns the value of the curve at point
t
. — 返回 t 点的曲线对应的值。
因此 t
可以认为是曲线的横坐标,而为了保证曲线的一致性,做了归一化处理,也就是t
的取值都是在 0-1 之间。这么说可能有点抽象,我们来看 2 个例子来对比就明白了,先看最简单 Curves.linear
的实现。
超级简单吧,直接返回 t,其实对应我们的数学的函数就是:
对应的曲线就是一条斜线。也就是说在设定的动画时间内,会完成从 0-1 的线性转变,也就是变化是均匀的。线性这个很好理解,我们再来看一个减速曲线decelerate
的实现。
我们先看一下_DecelerateCurve 的计算表达式是什么。
回忆一下我们高中物理学的匀减速运动,加速度为负(即减速)的距离计算公式:
上面的减速曲线其实就可以看做是初始速度是 2,加速度也是 2 的减速运动。为什么要是 2 这个值呢,这是因为 t 的取值范围是 0-1,这样计算完的结果的取值范围还是 0-1。你肯定会问,为什么要保证曲线的计算结果要是 0-1?
我们来假设计算结果不为 0-1 会发生什么情况,比如我们要在屏幕上移动一个组件为 60 像素。假设动画曲线初始值不为 0。那就意味着一开始的移动距离是跳变的。同样的,如果结束值不为 1.0,意味着在最后一个点的距离值不是 60.0,那么就意味着结束时需要从最后一个点跳到最终的 60 像素的位置(动画需要保证最终的移动距离是 60 像素)这样意味着动画会出现跳变的效果,绘制曲线的话会是下的样子(绿色是正常的,红线是异常的)。这样的动画体验是很糟糕的!因此,这是一个关键点,如果你的自定义曲线的 transformInternal
方法的返回值范围不是 0-1,就意味着动画会出现跳变,导致动画缺帧的感觉。
有了这个基础,我们就可以解释动画曲线的基本机制了,实际上就是在给定的动画时间(Duration
)范围内,完成组件的初始状态到结束状态的转变,这个转变是沿着设定的 Curve
类完成的,而其横坐标是 0-1.0,曲线的初始值和结束值分别是 0 和 1.0,而至于中间值是可以低于 0 或超过 1 的。我们可以想像是我们沿着设定的曲线运动,最终无论如何都会达到设定的目的地,而至于怎么走,拐多少道弯,速度怎么变化都是曲线控制的。但是,如果你的曲线初始值不为 0 或结束值不为 1,就像是跳悬崖的那种感觉!
正弦动画曲线
我们来一个正弦曲线的动画验证一下上面的说法。
count
参数用于控制周期,即达到目的地之前可以多来几个来回。这里我们发现,初始值是 0,但是一个周期(2π)结束值也是 0,这样在动画结束前会出现跳变的结果。来看一下示例代码,这个示例是让圆形向下移动 60 像素。
运行效果如下,注意看最后一帧从 0 的位置直接跳到了 60 的位置。
这个怎么调呢,我们来看一下正弦曲线的样子。
如果我们要满足 0-1 范围的要求,那么要往后再移动 90 度才能够达到。但是,这样还有个问题,这样破坏了周期性,比如设置 count=2
的时候结果又不对了。我们来看一下规律,实际上只有第一个周期需要多移动 90 度(途中箭头指向的点),后面的都是按 360 度(即 2π)为周期了。也就是角度其实是按 2.5π,4.5π,6.5π……规律来的,对应的角度公式其实就是:
所以调整后的正弦曲线代码为:
再看调整后的效果,是不是丝滑般地过渡了?
总结
本篇介绍了 Flutter 动画曲线类的原理和控制动画的机制,实际上 Curve 类就是在指定的时间内,沿曲线完成从起点到终点的过渡。但是为了保证动画平滑过渡,应该保证自定义曲线的transformInternal
方法返回值的起始值和结束值分别是 0 和 1。
欢迎关注个人公众号:岛上码农,或加本人微信:island-coder。
版权声明: 本文为 InfoQ 作者【岛上码农】的原创文章。
原文链接:【http://xie.infoq.cn/article/61c65cb1f926e4b1b2823b78e】。文章转载请联系作者。
评论