AnimatedWidget简化了什么?
首先看一段不使用AnimatedWidget的代码:
给animation添加了Listener,动画执行的每一帧都会回调这个Listener,在这个Listener回调中,调用setState()
来完成widget的更新(刷新)。
class ScaleAnimationRoute extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute> with SingleTickerProviderStateMixin{
Animation<double> animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//图片宽高从0变到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(()=>{});
});
//启动动画(正向执行)
controller.forward();
}
@override
Widget build(BuildContext context) {
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
这样是不是很麻烦,还需要手动调用setState()
那么AnimatedWidget就是省略了setState()的步骤,AnimatedWidget
类封装了调用setState()
的细节,来看下面的代码:
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
}
class ScaleAnimationRoute1 extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//图片宽高从0变到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
//启动动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedImage(animation: animation,);
}
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
可以看到不再需要添加Listener并手动调用setState()
方法了。AnimatedWidget
自己会使用当前 Animation
的 value
来绘制自己。
AnimatedBuilder是干嘛的?
我们可以看到上面的AnimatedImage
, 用AnimatedWidget
可以从动画中分离出widget
,而动画的渲染过程(即设置宽高)仍然在AnimatedWidget
中。也就是animation.value仍然设置在Image的width,height属性中。
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
}
而AnimatedBuilder
是在AnimatedWidget
的基础上将显示内容和动画拆分开来,更加方便的为特定的显示内容添加具体的动画。看下嘛的例子:Image
和Animation
就完美的分开了。
class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: animation.value,
width: animation.value,
child: child
);
},
child: child
),
);
}
}
...
Widget build(BuildContext context) {
return GrowTransition(
child: Image.asset("images/avatar.png"),
animation: animation,
);
}
AnimatedWidget和AnimatedBuilder就完美了吗?
可以看到上面的代码,AnimationController依然暴露在外面,需要调用controller.forward()
执行动画,能不能把AnimationController封装到动画widget的内部,答案是肯定的,直接看代码:
class AnimatedDecoratedExampleBox extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimatedDecoratedExampleBoxState();
}
}
class _AnimatedDecoratedExampleBoxState
extends State<AnimatedDecoratedExampleBox> {
Color _bgColor = Colors.blue;
var duration = Duration(seconds: 1);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
title: Text("AnimatedSwitcher"),
),
body: Center(
child: AnimatedDecoratedBox(
duration: duration,
decoration: BoxDecoration(color: _bgColor),
child: FlatButton(
onPressed: () {
setState(() {
_bgColor = _bgColor == Colors.blue
? Colors.red
: Colors.blue;
});
},
child: Text(
"AnimatedDecoratedBox",
style: TextStyle(color: Colors.white),
),
),
))));
}
}
class AnimatedDecoratedBox extends StatefulWidget {
AnimatedDecoratedBox({
Key key,
@required this.decoration,
this.child,
this.curve = Curves.linear,
@required this.duration,
this.reverseDuration,
});
final BoxDecoration decoration;
final Widget child;
final Duration duration;
final Curve curve;
final Duration reverseDuration;
@override
_AnimatedDecoratedBoxState createState() => _AnimatedDecoratedBoxState();
}
class _AnimatedDecoratedBoxState extends State<AnimatedDecoratedBox>
with SingleTickerProviderStateMixin {
@protected
AnimationController get controller => _controller;
AnimationController _controller;
Animation<double> get animation => _animation;
Animation<double> _animation;
DecorationTween _tween;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return DecoratedBox(
decoration: _tween.animate(_animation).value,
child: child,
);
},
child: widget.child,
);
}
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this,
);
_tween = DecorationTween(begin: widget.decoration);
_updateCurve();
}
void _updateCurve() {
if (widget.curve != null)
_animation = CurvedAnimation(parent: _controller, curve: widget.curve);
else
_animation = _controller;
}
@override
void didUpdateWidget(AnimatedDecoratedBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.curve != oldWidget.curve) _updateCurve();
_controller.duration = widget.duration;
_controller.reverseDuration = widget.reverseDuration;
if (widget.decoration != (_tween.end ?? _tween.begin)) {
_tween
..begin = _tween.evaluate(_animation)
..end = widget.decoration;
_controller
..value = 0.0
..forward();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
封装了AnimatedDecoratedBox
这样一个动画组件,在内部管理AnimationController
,那它的动画是何时执行的呢?
AnimatedDecoratedBox(
duration: duration,
decoration: BoxDecoration(color: _decorationColor),
child: FlatButton(
onPressed: () {
setState(() {
_decorationColor = Colors.red;
});
},
child: Text(
"AnimatedDecoratedBox",
style: TextStyle(color: Colors.white),
),
),
)
外部,点击按钮时调用了setState()
方法,那么AnimatedDecoratedBox
内部的didUpdateWidget()
方法就会被调用,在这个方法里面判断新旧属性不一样,就执行动画。
@override
void didUpdateWidget(AnimatedDecoratedBox1 oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.curve != oldWidget.curve)
_updateCurve();
_controller.duration = widget.duration;
_controller.reverseDuration = widget.reverseDuration;
if(widget.decoration!= (_tween.end ?? _tween.begin)){
_tween
..begin = _tween.evaluate(_animation)
..end = widget.decoration;
_controller
..value = 0.0
..forward();
}
}
关于didUpdateWidget()
何时执行:
didUpdateWidget()
:在widget重新构建时,Flutter framework会调用Widget.canUpdate
来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate
返回true
则会调用此回调。正如之前所述,Widget.canUpdate
会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()
就会被调用。如果key或runtime不一样,整个widget state都会重构,initState()方法会重新执行。详见我之前的文章:juejin.cn/post/684490…
上面是在didUpdateWidget()
方法中进行手动控制的,flutter提供了两个类简化了这种控制:
ImplicitlyAnimatedWidget
和ImplicitlyAnimatedWidgetState
,详见:book.flutterchina.club/chapter9/an…
AnimatedWidget,AnimatedBuilder同时执行多个动画
同时执行大小和颜色透明度的动画
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation,this.animation_1, this.animation_2})
: super(key: key, listenable: animation);
final Animation<double> animation_1;
final Animation<double> animation_2;
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Container(
width: animation_1.value,
height: animation_1.value,
color: Colors.orange.withOpacity(animation_2.value),
),
);
}
}
class ScaleAnimationRoute1 extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
Animation<double> animation_1;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//图片宽高从0变到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
animation_1 = new Tween(begin: 0.0, end: 1.0).animate(controller);
//启动动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedImage(animation: controller, animation_1:animation, animation_2: animation_1);
}
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
交织动画
有些时候我们可能会需要一些复杂的动画,这些动画可能由一个动画序列或重叠的动画组成,比如:有一个柱状图,需要在高度增长的同时改变颜色,等到增长到最大高度后,我们需要在X轴上平移一段距离。可以发现上述场景在不同阶段包含了多种动画,要实现这种效果,使用交织动画(Stagger Animation)
待续
通用“动画切换”组件(AnimatedSwitcher)
待续
自定义路由切换动画
待续
Hero动画
待续