前言
Flutter 在刚出来的时候学习过一下,之后就没有碰过了,感觉东西太少,那时候我也没还没写前端,不习惯这种方式写代码;但是最近疫情关系,有时候没事干开始看 Flutter,也喜欢上了,下面来介绍一下我最喜欢的 ImplicitlyAnimatedWidget(隐式动画组件),如何自定义和源码解读。
什么是 ImplicitlyAnimation
在 Flutter 里面,动画分成 ExplicitAnimation
(显示动画)和 ImplicitlAnimation
(隐式动画),如果你希望通过 setState
的方式就可以改变 widget
状态,同时增加过度动画,比如我希望 widget
变成透明,然后是慢慢透明的。而如果你希望用 controller
来开始动画,或者有其他动画一起配合,可能就需要 ExplicitAnimation
,请注意这里需要你自己去管理生命周期,比较麻烦。
官方视频里面提到,如果:
- 我的动画永远重复吗?比如很多播放器都会有一直旋转的黑胶唱片的元素。
- 动画不是连续的吗?比如只会从小到大的动画,而不会有大到小的动画。
- 会有很多 widget 配合动画吗?
如果这需要任意一个,请选择 ExpicitAnimatin
。
源码解析
源码分为两部分,ImplicitlyAnimatedWidget
和 ImplicitlyAnimatedWidgetState
下面分辨解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| abstract class ImplicitlyAnimatedWidget extends StatefulWidget { const ImplicitlyAnimatedWidget({ Key key, this.curve = Curves.linear, @required this.duration, this.onEnd, }) : assert(curve != null), assert(duration != null), super(key: key);
final Curve curve;
final Duration duration;
final VoidCallback onEnd;
@override ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
@override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms')); } }
|
State
ImplicitlyAnimatedWidget
其实很简单,先定义了 curve
(动画曲线), duration
(时间), onEnd
(结束会调),没什么太多内容,接下来看看核心 ImplicitlyAnimatedWidgetState
,但是由于代码过多,一部分一部分来:
1 2 3 4 5 6
| @protected AnimationController get controller => _controller; AnimationController _controller;
Animation<double> get animation => _animation; Animation<double> _animation;
|
默认定义了 _controller
, _animation
,接下来看 initState
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, debugLabel: kDebugMode ? '#123;widget.toStringShort()}' : null, vsync: this, ); _controller.addStatusListener((AnimationStatus status) { switch (status) { case AnimationStatus.completed: if (widget.onEnd != null) widget.onEnd(); break; case AnimationStatus.dismissed: case AnimationStatus.forward: case AnimationStatus.reverse: } }); _updateCurve(); _constructTweens(); didUpdateTweens(); }
void _updateCurve() { if (widget.curve != null) _animation = CurvedAnimation(parent: _controller, curve: widget.curve); else _animation = _controller; }
bool _constructTweens() { bool shouldStartAnimation = false; forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) { if (targetValue != null) { tween ??= constructor(targetValue); if (_shouldAnimateTween(tween, targetValue)) shouldStartAnimation = true; } else { tween = null; } return tween; }); return shouldStartAnimation; }
bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) { return targetValue != (tween.end ?? tween.begin); }
@protected void didUpdateTweens() { }
|
initState
就是这样了,流程大概就是这样,初始化 _controller
或 _animation
,然后创建 tween
,注意的点就是,因为 shouldStartAnimation
是取决于最后一次调用 visitor
,所以如果我们有多个:
1 2 3 4 5 6 7
| @override void forEachTween(visitor) { _colorTween = visitor( _colorTween, widget.color, (dynamic value) => ColorTween(begin: value)); _sizeTween = visitor( _sizeTween, widget.size, (dynamic value) => SizeTween(begin: value)); }
|
shouldStartAnimation 是取决 size 的调用。
update 情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @override void didUpdateWidget(T oldWidget) { super.didUpdateWidget(oldWidget); if (widget.curve != oldWidget.curve) _updateCurve(); _controller.duration = widget.duration; if (_constructTweens()) { forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) { _updateTween(tween, targetValue); return tween; }); _controller ..value = 0.0 ..forward(); didUpdateTweens(); } }
void _updateTween(Tween<dynamic> tween, dynamic targetValue) { if (tween == null) return; tween ..begin = tween.evaluate(_animation) ..end = targetValue; }
@override void dispose() { _controller.dispose(); super.dispose(); }
|
这里我们基本上了解了动画的控制过程,里面是有 controller
去维护,比较简单,但是值得注意一点是,假设我们做了个自定义 ImplicitlyAnimatedWidget
是从 0 位置跑到 100,但是在 50 的位置改变成 90,那么所花费时间是 150,每次改变都是按照 duration
的时间来决定。
visitor
最容易混乱的地方,只要记住,第一个参数是 tween
,第二个参数是 targetValue
,也就是 tween.end
,第三个参数是初始化,就很简单了~
实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class ImplicitlyAnimatedDemo extends ImplicitlyAnimatedWidget { final Color color;
ImplicitlyAnimatedDemo({ Key key, Curve curve = Curves.linear, Duration duration, VoidCallback onEnd, @required this.color, }) : super( key: key, curve: curve, duration: duration, onEnd: onEnd, );
@override ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _ImplicitlyAnimatedDemoState(); }
class _ImplicitlyAnimatedDemoState extends AnimatedWidgetBaseState<ImplicitlyAnimatedDemo> { ColorTween _colorTween;
@override Widget build(BuildContext context) { return Container( width: 100, height: 100, color: _colorTween?.evaluate(animation), ); }
@override void forEachTween(visitor) { _colorTween = visitor( _colorTween, widget.color, (dynamic value) => ColorTween(begin: value)); } }
|
这个 widget
很简单,主要就是改变颜色,可以发现到,我这里没有继承 ImplicitlyAnimatedWidgetState
,因为其内部没有 setState
,而 AnimatedWidgetBaseState
实现了 setState
去更新 widget
。
总结
ImplicitlyAnimatedWidget 是我最喜欢的控件,他足够简单,配合 bloc 下更是舒服;在大部分情况下我们可能只是需要改变一下 widget,增加一个过渡动画十分好看,但是绝大部分我们可能也不需要自定义,使用 Animated 开头的 widget 很多都有封装,比如 AnimatedOpacity,AnimatedPadding 等,如果是在需要自定义,可以使用 TweenAnimationBuilder,更为方便的创建。
最后,许多人在学习 Flutter 的时候会找个 api 来写练手,为此我封装了一个官方文档提及到的 newsapi 封装库 newsapi package,如果快速练手可以考虑选择~