平移动画/补间动画:TranslationAnimatedWidget
TranslationAnimatedWidget是一个继承于StatefulWidget的widget。
1、widget
包含成员变量如下:
final List<Offset> _values;
final Duration duration;
final Duration delay;
final bool enabled;
final Curve curve;
final Widget child;
final Function(bool) animationFinished;
- duration:动画持续的时间
- delay:动画开始前的延迟时间
- enabled:true为正向执行动画,false为反向执行动画。对应于forward/reverse
- curve:动画执行的曲线,常见的有:ease、easeIn、linear等。更多类型可参考官方文档
- child:执行动画的widget
- animationFinished:动画执行完毕的回调方法
- _values:widget偏移量数组,放的内容是Offset对象
注意:在flutter中,变量或函数前面加_
表示这是个私有的变量或函数,外部不能访问。
TranslationAnimatedWidget中包含一个tween方法和一个构造方法,并赋予了某些初始值。
代码:
TranslationAnimatedWidget.tween({
Duration duration = const Duration(milliseconds: 500),
Duration delay = const Duration(),
Offset translationDisabled = const Offset(0, 200),
Offset translationEnabled = const Offset(0, 0),
bool enabled = true,
Function(bool) animationFinished,
Curve curve = Curves.linear,
@required Widget child,
}) : this(
duration: duration,
enabled: enabled,
curve: curve,
delay: delay,
child: child,
animationFinished: animationFinished,
values: [translationDisabled, translationEnabled],
);
TranslationAnimatedWidget({
this.duration = const Duration(milliseconds: 500),
this.delay = const Duration(),
List<Offset> values = const [const Offset(0, 0), const Offset(0, 200)],
this.enabled = true,
this.curve = Curves.linear,
this.animationFinished,
@required this.child,
}) : this._values = values,
assert(values.length > 1);
此外,在TranslationAnimatedWidget中还声明了一个对外的values,即_values,还有一个方法来判断两次的动画是否相等。
bool isAnimationEqual(TranslationAnimatedWidget other) =>
listEquals(values, other.values) &&
duration == other.duration &&
curve == other.curve &&
delay == other.delay;
最后就是重写createState方法。
2、state部分
先看代码:
class _State extends State<TranslationAnimatedWidget>
with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _translationXAnim;
Animation<double> _translationYAnim;
@override
void didUpdateWidget(TranslationAnimatedWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isAnimationEqual(oldWidget)) {
if (widget.enabled != oldWidget.enabled) {
_updateAnimationState();
}
} else {
_createAnimations();
if (widget.enabled != oldWidget.enabled) {
_updateAnimationState();
}
}
}
void _updateAnimationState() async {
if (widget.enabled ?? false) {
await Future.delayed(widget.delay);
_animationController.forward();
} else {
_animationController.reverse();
}
}
void _createAnimations() {
_animationController?.dispose();
_animationController = AnimationController(
duration: widget.duration,
vsync: this,
)..addStatusListener((status) {
if (widget.animationFinished != null) {
widget.animationFinished(widget.enabled);
}
});
_translationXAnim = chainTweens(
widget.values.map((it) => it.dx).toList(),
).animate(
CurvedAnimation(parent: _animationController, curve: widget.curve),
)..addListener(() {
setState(() {});
});
_translationYAnim = chainTweens(
widget.values.map((it) => it.dy).toList(),
).animate(
CurvedAnimation(parent: _animationController, curve: widget.curve),
)..addListener(() {
setState(() {});
});
_updateAnimationState();
}
@override
void initState() {
_createAnimations();
super.initState();
}
@override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(_translationXAnim.value, _translationYAnim.value),
child: widget.child,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
看到了这么个东西TickerProviderStateMixin,这是个啥?先继续往下看,稍候解释。
首先声明了三个变量:_animationController、_translationXAnim、_translationYAnim,看名字就可以理解,分别是控制动画的控制器、动画过程中x的值和y的值,因为位移动画是一个平面上的动画,只涉及到了横纵坐标。
再往下是重写的一个方法:didUpdateWidget
,该方法在组件的状态改变的时候就会被调用。在这个方法中做了些判断:首先是调用在上面定义的方法来判断状态改变前后的两个widget是否相等,相等时判断两个widget的enabled是否一样,不一样则说明发生了改变,调用另一个函数_updateAnimationState()
。如果不想等,则通过_createAnimations()
重新创建,然后再比较enabled的值。逻辑比较简单。
下面是刷新动画状态的方法:_updateAnimationState
。改方法是一个异步函数,首先判断了当前widget的enabled,在第一部分也说过为true则正向执行动画,即_animationController.forward();
;为false则反向执行动画,即_animationController.reverse();
。这里有一个??
的操作符,这个操作符的作用是给前面的变量赋予一个初始值,如果该变量为null的话。还有一个在_animationController.forward();
之前做了一个延时,而反向则没有延时,立即执行。
接着就是_createAnimations
方法,顾名思义就是创建动画。在这里面对上面生命的变量进行了初始化,并赋值。先看第一行,调用了dispose方法。这么做是为了避免重复创建。可以看到在_animationController
后面跟着一个?
,这是为了防止_animationController
为null的情况,若为null,则不执行后面的操作。
下面是创建的具体代码。看到了一个陌生的参数:vsync
,并赋值为this
。官网上有相关说明:
存在vsync时会防止屏幕外动画消耗不必要的资源。vsync对象会绑定动画的定时器到一个可视的widget,所以当widget不显示时,动画定时器将会暂停,当widget再次显示时,动画定时器重新恢复执行,这样就可以避免动画相关UI不在当前屏幕时消耗资源。 如果要使用自定义的State对象作为vsync时,请包含TickerProviderStateMixin。
这里就回答上面的疑问。实际上还有另外一个关键词:SingleTickerProviderStateMixin
,这个跟TickerProviderStateMixin
有什么关系呢?这个官网上没有找到,不过在《Flutter实战》这本书中有一句话,是这么说的:如果有多个AnimationController,则应该使用TickerProviderStateMixin。链接如下: 动画基本结构及状态监听 。
后面有一个奇怪的符号:..
,这是级联操作符,改操作符可以对同一对象执行一系列操作,能够节省中间步骤和临时变量,让代码更高效。这部分代码等价于下面的代码:
_animationController.addStatusListener((status) {
if (widget.animationFinished != null) {
widget.animationFinished(widget.enabled);
}
});
可以看到这是给_animationController
添加了一个状态的监听,若widget.animationFinished不为null,则将当前状态通过回调函数传递回去。
再往下则是对另外两个边路进行了初始化,通过调用chainTweens方法返回一个TweenSequence对象。TweenSequence<T>是一个继承与Animatable<T>的类,跟Tween的意义是相近的。不过TweenSequence可以实现多组补间动画,而tween只能实现一组。
方法实现如下:
TweenSequence chainTweens(List<double> values) {
if (values.length < 2) {
return TweenSequence<double>([]);
}
List<TweenSequenceItem<double>> items = List<TweenSequenceItem<double>>();
var lastValue = values[0];
for (int i = 1; i < values.length; ++i) {
double newValue = values[i];
items.add(TweenSequenceItem<double>(
tween: Tween(begin: lastValue, end: newValue), weight: 1));
lastValue = newValue;
}
return TweenSequence<double>(items);
}
传入一个double类型的数组,若数组个数小于2,则返回一个空的double类型的TweenSequence对象。主体内容是一个for循环,在循环中创建一个TweenSequenceItem对象并添加到数组中,最后将数组传入TweenSequence并返回。TweenSequenceItem中包含一个Tween对象和weight,tween提供补间动画的初始值,weight标识该动画的权重,在整个动画持续时间内,当前的tween所占的时间比。
此外,还调用了animate()
方法,传入了一个控制器对象,并添加了监听,在监听中刷新当前的state。最后调用_updateAnimationState()
方法。
最后就是重写了三个系统的方法,在initState()
中调用_createAnimations()
,在build方法中调用系统的平移动画方法,并将参数传递进去,在dispose()中释放_animationController
。
其他动画
其他的动画的实现方式跟上述基本一致,主要的不同之处就是build方法中调用的系统动画方法的不同,所以不再单独进行描述。
唯一不同的是CustomAnimatedWidget,这个是在build里面返回了一个builder函数,在函数中传递了一个从0到1的percent,需要在执行动画的widget中标明显示的内容。
终于结束了,菜鸟,勿喷。😀