目录
前言
我们之前介绍了不少有关动画的篇章。前面介绍的动画都是只有一个动画效果,那如果我们想对某个组件实现一组动效,比如下面的效果,该怎么办?
staggered animation
这个时候我们需要用到组合动效, flutter 提供了交错动画(staggered animation)的方式实现。对于多个 anmation
对象,可以共用一个 animationcontroller
,然后在不同的时间段执行动画效果。这就有点像 gif 图片一样,一帧帧图像播放实现连续的动画。
交错动画机制
交错动画的实现基于以下几个要点:
- 所有的 animation对象使用同一个
animationcontroller
驱动; - 不管实际动画持续的时间长度多长,动画控制器
controller
的值必须在0-1之间; - 每个动画对象都有一个0-1范围内的间隔(
interval
); - 在间隔时间内,
tween
对象从起始值过渡到结束值。 - 由
animationcontroller
统一管理这些tween 产生的animation
对象。
听起来有点抽象,我们以一张图来表述就清晰多了,假设我们有4个动画对象,分别控制组件的透明度(opacity
),宽度(width
),高度(height
)和颜色(color
),交错动画过程如下:
时序示意图
controller
是一个从0到1的归一化的动画控制,其实对应的就是动画时长的归一化。然后 opacity
透明度动效占据了0-0.25区间;width
占据了0.25-0.5区间;height
占据了0.5-0.75区间;最后是 color
占据了0.75-1.0的区间。区间对应就是动画的时间间隔,只是每个区间内的tween
动画对象的取值范围都是0-1以控制从起始值到结束值。我们可以理解为是 animationcontroller
将多个 animation
对象按序(也可以重合)拼接起来形成复合形式的动画。
代码实现
看上面的说明是不是觉得还有些难以理解,我们来一段示例代码就很容易明白了。下面的代码我们定义了一个共用的_controller
,然后四段动画对象_opaticy
,_width
,_height
和_color
。其中关键的实现是使用了 tween
对象的 animate
方法,并指定了一个 curvedanimation
对象作为 其parent
参数。而这个curvedanimation
实际使用 interval
来切分_controller
的动画时间,从而可以将多个 animation
对象组合起来。
import 'package:flutter/material.dart'; class staggeredanimationdemo extends statefulwidget { staggeredanimationdemo({key? key}) : super(key: key); @override _staggeredanimationdemostate createstate() => _staggeredanimationdemostate(); } class _staggeredanimationdemostate extends statewith singletickerproviderstatemixin { late animationcontroller _controller; late animation _opacity; late animation _width; late animation _height; late animation _color; @override void initstate() { _controller = animationcontroller(duration: duration(seconds: 2), vsync: this) ..addlistener(() { setstate(() {}); }); _opacity = tween (begin: 0.5, end: 1.0).animate( curvedanimation( parent: _controller, curve: interval( 0.0, 0.25, curve: curves.easein, ), ), ); _width = tween (begin: 0.0, end: 2.0).animate( curvedanimation( parent: _controller, curve: interval( 0.25, 0.5, curve: curves.easein, ), ), ); _height = tween (begin: 0.0, end: 2.0).animate( curvedanimation( parent: _controller, curve: interval( 0.5, 0.75, curve: curves.easein, ), ), ); _color = colortween(begin: colors.green, end: colors.blue).animate( curvedanimation( parent: _controller, curve: interval( 0.75, 1.0, curve: curves.easein, ), ), ); super.initstate(); } @override widget build(buildcontext context) { return scaffold( appbar: appbar( title: const text('交错动画'), ), body: center( child: opacity( opacity: _opacity.value, child: container( width: 100 100 * _width.value, height: 100 100 * _height.value, color: _color.value, ), ), ), floatingactionbutton: floatingactionbutton( child: icon(icons.play_arrow), onpressed: () { if (_controller.iscompleted) { _controller.reverse(); } else if (!_controller.isanimating) { _controller.forward(); } }, ), ); } }
我们来看一下运行效果,可以看到运行的动画过程其实就是4段动画效果拼接来的,先是透明度改变,然后是宽度改变,再之后是高度改变,最后是颜色的改变。
运行效果
interval 介绍
我们来看一下关键的 interval
类的介绍。
a curve that is 0.0 until [begin], then curved (according to [curve]) from 0.0 at [begin] to 1.0 at [end], then remains 1.0 past [end].
interval
类继承自 curve
,所不同的是,在 begin
之前曲线的值一直保持为0.0,而在 end
之后一直保持为1.0。所以可以理解为,在 animationcontroller
启动动画后,interval
曲线其实也已经在绘制,只是有效的取值区间只在 begin
到 end
之间,下面就是 interval
的一种示例曲线图。
image.png
从 interval
的源码也能看出来,其中 clamp
方法限制了取值范围,当 t <= begin
的时候取值就是0,当 t >= end
的时候,取值就是1.0。
@override double transforminternal(double t) { assert(begin >= 0.0); assert(begin <= 1.0); assert(end >= 0.0); assert(end <= 1.0); assert(end >= begin); t = ((t - begin) / (end - begin)).clamp(0.0, 1.0); if (t == 0.0 || t == 1.0) return t; return curve.transform(t); }
总结
本篇介绍了交错动画的实现机制和示例,通过交错动画给了我们更多动效组合的空间,从而可以实现类似 gif图片的那种多帧组合在一起的动画效果。