Flutter 的动画

人间烟火/张佳伟版 Lv2
Flutter 的动画

要素:Animation、Curve、Controller、Tween

Animation:主要功能是保存动画的插值和状态,其中比较常用的是 Animation
Animation对象在整个动画执行过程中输出的值是可以线性的、曲线的或其他曲线函数,这由Curve来决定
Animation对象是一个在一段时间内依次生成一个区间(tween)之间值的类
Animation对象的控制Controller可以控制动画的一些属性,正向、反向运行等

1)Animation

Animation除了可以生成double类型值还可以Animation、Animation
在动画的每一帧中,我们可以通过Animation对象的value 属性获取动画的当前状态值

原理:可以通过Animation来监听动画每一帧以及执行状态的变化,Animation有如下两种方法:
1、addListener();他可以用于给Animation添加监听器,在每一帧都会被调用。
监听器中最常见的行为是改变状态后调用setState()来触发UI重建

2、addStatusListener();它可以给Animation添加“动画状态改变”监听器;动画开始、结束、正向或者反向时会调用状态改变的监听器

2)Curve

动画过程是可以匀速的、匀加速的或者先加速后减速等。
flutter通过Curve(曲线)来描述动画过程,匀加速动画称为线性的(Curves.linear)

原理:我们可以通过 CurveAnimation 来指定动画的曲线

1
fianl CurveAnimation curve = CurvedAnimation(parent:controller, curve:Curves,easIn);

上面得知:CurveAnimation 可以通过包装 Animation 和 Curve 生成一个新的动画对象,我们正是通过这种方式来将动画和动画执行的曲线关联起来的

Curves曲线 动画过程

linear 匀速的
decelerate 匀减速
ease 开始加速,后面减速
easeIn 开始慢,后面快
easeOut 开始快,后面慢
easeInOut 开始慢,然后加速,最后再减速

尝试自己制作一个Curve,通过继承再重写transform方法,例如定义一个正弦曲线:

1
2
3
4
5
6
class ShakeCurve extend Curve{
@override
double transform(double t ){
return math.sin(t *math.PI *2);
}
}

3)AnimationController

AnimationController用于控制动画,它包含动画的启动forward()、停止stop()、反向播放reverse()等方法
AnimationController会在动画的每一帧生成一个新的值

默认情况下
AnimationController在给定的时间段内线性的生成从0.0到1.0的数字
例如下面代码创建一个Animation对象,但不会启动动画
{duration 表示动画执行的时长,通过它们我们可以控制动画的速度}

1
2
3
4
final AnimationController controller =  AnimationController(
duration: const Duration(milliseconds:2000),
vsunc:this,
);

AnimationController 生成数字的区间可以通过lowerBound 和 upperBound 来指定,如:

1
2
3
4
5
6
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds:2000),
lowerBound: 10.0,
upperBound: 20.0,
vsync: this,
);

原理:在动画开始执行后 开始生成 动画帧,屏幕每刷新一次就是一个动画帧,在动画帧的每一帧,
会随着根据动画的曲线来生成当前的动画值(Animation.value),然后根据当前的动画值去构建UI,
当所有动画帧依次触发时,动画值会依次改变,所以构建的UI也会依次变化,所有最终我们可以看到
一个完成的动画。

另外在动画的每一帧,Animation对象会调用其帧监听器,等动画状态发生改变时会调用状态改变监听器

注意: 在某些情况下,动画值可能会超出AnimationController的[0.0,1.0]的范围,这取决于具体的曲线。例如,fling()函数可以根据我们手指滑动(甩出)的速度(velocity)、力量(force)等来模拟一个手指甩出动画,因此它的动画值可以在[0.0,1.0]范围之外 。也就是说,根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。

  1. Ticker

当创建一个AnimationController时,需要传递一个vsync参数,它接收一个TickerProvider类型的对象,它的主要职责是创建Ticker,定义如下:

1
2
3
4
abstract class TickerProvider {
//通过一个回调创建一个Ticker
Ticker createTicker(TickerCallback onTick);
}

通常我们会将SingleTickerProviderStateMixin添加到State的定义中,然后将State对象作为vsync的值

  1. Tween

默认情况下,AnimationController对象值的范围是[0.0,1.0]。如果我们需要构建UI的动画值在不同的范围或不同的数据类型,则可以使用Tween来添加映射以生成不同的范围或数据类型的值。例如,像下面示例,Tween生成[-200.0,0.0]的值:

1
final Tween doubleTween = Tween<double> (begin: -200.0, end: 0.0)

Tween构造函数需要begin和end两个参数。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为[0.0,1.0],但这不是必须的,我们可以自定义需要的范围。

Tween继承自Animatable,而不是继承自Animation,Animatable中主要定义动画值的映射规则。

下面我们看一个ColorTween将动画输入范围映射为两种颜色值之间过渡输出的例子:

1
final Tween colorTween = ColorTween(begin: Colors.trabsparent, end: Colors.black54);

Tween对象不存储任何状态,相反,它提供了evaluate(Animation animation)方法,它可以获取动画当前映射值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其他处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

  1. Tween.animate
    要使用 Tween 对象,需要调用其animate()方法,然后传入一个控制器对象。例如,以下代码在 500 毫秒内生成从 0 到 255 的整数值。
1
2
3
4
5
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

注意animate()返回的是一个Animation,而不是一个Animatable。

以下示例构建了一个控制器、一条曲线和一个 Tween:

1
2
3
4
5
6
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
  1. 线性插值lerp函数
    动画的原理其实就是每一帧绘制不同的内容,一般都是指定起始和结束状态,然后在一段时间内从起始状态逐渐变为结束状态,而具体某一帧的状态值会根据动画的进度来算出,因此,Flutter 中给有可能会做动画的一些状态属性都定义了静态的 lerp 方法(线性插值),比如:
    1
    2
    //a 为起始颜色,b为终止颜色,t为当前动画的进度[0,1]
    Color.lerp(a, b, t);

lerp 的计算一般遵循: 返回值 = a + (b - a) * t,其他拥有 lerp 方法的类:

1
2
3
4
5
6
// Size.lerp(a, b, t)
// Rect.lerp(a, b, t)
// Offset.lerp(a, b, t)
// Decoration.lerp(a, b, t)
// Tween.lerp(t) //起始状态和终止状态在构建 Tween 的时候已经指定了

需要注意,lerp 是线性插值,意思是返回值和动画进度t是成一次函数(y = kx + b)关系,因为一次函数的图像是一条直线,所以叫线性插值。如果我们想让动画按照一个曲线来执行,我们可以对 t 进行映射,比如要实现匀加速效果,则 t’ = at²+bt+c,然后指定加速度 a 和 b 即可(大多数情况下需保证 t’ 的取值范围在[0,1],当然也有一些情况可能会超出该取值范围,比如弹簧(bounce)效果),而不同 Curve 可以按照不同曲线执行动画的原理本质上就是对 t 按照不同映射公式进行映射实现的。

  • Title: Flutter 的动画
  • Author: 人间烟火/张佳伟版
  • Created at: 2024-06-05 22:36:33
  • Updated at: 2024-06-05 21:13:33
  • Link: https://945912035.github.io/2024/06/05/2024-06-05/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
On this page
Flutter 的动画