官方文档
AnimatedWidget
还记得 上一节 里面是怎么更新 widget 的状态的吗?我们上次的步骤是:首先创建动画,然后给动画添加监听 addListener(...)
, 在 addListener(...)
方法中我们干了件 很重要 的事儿:setState((){})
,因为只有调用这个,才会让 widget 重绘。
这一次我们使用 AnimatedWidget
来实现动画,使用它就不需要给动画 addListener(...)
和 setState((){})
了,AnimatedWidget
自己会使用当前 Animation
的 value
来绘制自己。当然,这里 Animation
我们是以构造参数的方式传递进去的。
看代码:
class AnimatedContainer extends AnimatedWidget {
AnimatedContainer({Key key, Animation animation})
: super (key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation animation = listenable;
return Center(
child: Container(
decoration: BoxDecoration(
color: Colors.redAccent
),
margin: EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
),
);
}
}
上述代码中,我们定义了一个 AnimatedContainer
继承了 AnimatedWidget
,然后定义了一个构造方法,注意,构造方法中我们定义了一个 Animation
然后把这个 Animation
传到父类(super)中去了,我们可以看看 listenable: animation
这个参数,是一个 Listenable
类型,如下:
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
然后再看看 Animation 类:
abstract class Animation extends Listenable implements ValueListenable {
...
}
可以看到 Animation
是 Listenable
的子类,所以在我们自定义的 AnimatedContainer
类中可以传一个 Animation
类型的的参数作为父类中 listenable
的值。
使用我们上面定义的 AnimatedContainer
也很简单,直接作为 widget
使用就好,部分代码如下:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedWidgetDemo',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedWidgetDemo'),
),
body: AnimatedContainer(animation: animation,)
),
);``
}
可以看出我们在实例化 AnimatedContainer
的时候传入了一个 Animation
对象。
AnimatedWidgetDemo.dart
效果如下:
AnimatedBuilder
我们先看看如何使用 AnimatedBuilder
做一个上面一样的效果
部分代码如下:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilderExample',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('animatedBuilderExample'),
),
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
),
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
),
);
}
因为 AnimatedBuilder
是继承于 AnimatedWidget
的,
class AnimatedBuilder extends AnimatedWidget { ... }
所以可以直接把 AnimatedBuilder
当作 widget
使用
上述代码关键部分如下:
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
),
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
效果如下:
AnimatedBuilderExample_2.dart
builder
这个匿名类是每次动画值改变的时候就会被调用
AnimatedBuilder 使用简化后的结构如下:
AnimatedBuilder(
animateion: ... ,
child: ... ,
builder: (context, child) {
return Container(
width: ... ,
height: ... ,
child: child
)
}
)
上述代码看着可能会有迷糊的地方,里面的 child
好像被指定了两次,外面一个,里面一个。其实,外面的 child
是传给 AnimatedBuilder
的,而 AnimatedBuilder
又将这个 child
作为参数传递给了里面的匿名类(builder
)。
我们可以验证上述说明,就是给外面的 child
指定一个 key
,然后在 builder
里面打印出参数 child
的 key
。
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
key: Key("android"),
),
builder: (context, child) {
print("child.key: ${child.key}");
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
我们在外面的 child
里面的添加了一个 key
值,然后在 builder
里面打印出参数 child
的 key
值
输出如下:
flutter: child.key: [<'android'>]
区别
我们来看看 AnimatedBuilder
AnimatedWidget
和添加 addListener{}
监听并在里面触发 setState(...)
这三种方式做动画有什么区别。
为了更直观的看出它们的区别,这里使用一个第三方控件:RandomContainer
,这个控件会在屏幕每次重绘的时候改变自身的颜色。参照
首先在pubspec.yaml中添加依赖 random_pk: any
,如下:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
# RandomContainer
random_pk: any
先写一个小例子来看看 RandomContainer
这个控件每次在屏幕重绘的时候自身颜色改变的情况。
在屏幕上绘制一个宽高都为200.0
的 RandomContainer
然后给 RandomContainer
添加点击事件,点击事件里面做的就是调用 setState(...)
让 widget 重绘,部分代码如下:
body: Center(
child: GestureDetector(
onTap: _changeColor,
child: RandomContainer(
width: 200.0,
height: 200.0,
),
),
),
使用
RandomContainer
的时候需要引入import 'package:random_pk/random_pk.dart';
点击事件代码如下:
void _changeColor() {
setState(() {});
}
AnimatedBuilderExample_1.dart
效果如下:
接下来我们使用三种方式实现同一种效果来看看有什么不同:
AnimatedWidget
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: AnimatedWidgetDemo( // new
animation: _controller,
),
),
),
),
);
}
效果如下:
AnimatedBuilder
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: AnimatedBuilderDemo( // new
child: getContainer(),
animation: _controller,
),
),
),
),
);
}
AnimatedBuilder 的效果和 AnimatedWidget 的效果是一样的。
接下来我们看看在 addListener{}
里面调用 setState(...)
的效果,也就是我们在上一节中实现动画的方式
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat()
..addListener(() {
setState(() {});
});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: Transform.rotate( // new
child: getContainer(),
angle: _controller.value * 2.0 * pi,
),
),
),
),
);
}
效果如下
看出来了吧。。。
AnimatedBuilderExample_3.dart
如有错误,还请指出,谢谢!