官方文档
如果没有看过之前两节的,建议先看看前两节的内容
- Flutter 中的 Animations(一)
- Flutter 中的 Animations(二)
上一节中我们主要做的事情如下:
- 在 Animation 的
addListener(...)中调用setState(...)来给 widget 添加动画 - 使用
AnimatedWidget的方法来给 widget 添加动画
上一节中我们也通过案例的形式展现出以上两种方式的区别,得出的结论就是如下:
使用 addListener(...) 和 setState(...) 给 Widget 添加动画的时候,会导致其他的 Widget 也跟着重绘,而使用 AnimatedWidget 的方式给 Widget 添加动画的时候只会重绘当前的 Widget
这是为什么呢 ???
我们先来看看动画的执行流程。
我们在使用动画的时候会初始化一个 AnimationController
AnimationController controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
可以看出,初始化 AnimationController 需要传递两个参数,一个是 vsync 一个是 duration ,这个 duration 很容易理解,就是动画执行完毕需要的时长,那 vsync 是什么啊?上述代码中我们传入了 this ,那是因为我们在 State 类 with(混入,第一节 有讲) 了 SingleTickerProviderStateMixin,下面我们看看初始化 AnimationController 时都干了什么事,源码如下:
AnimationController({
double value,
this.duration,
this.debugLabel,
this.lowerBound: 0.0,
this.upperBound: 1.0,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
我们抽重点出来看
_ticker = vsync.createTicker(_tick);
这里调用了 vsync 的 createTicker(...) 方法创建了一个 Ticker, 而我们在初始化 AnimationController 的时候 vsync 参数传的是 SingleTickerProviderStateMixin,SingleTickerProviderStateMixin#createTicker(...) 的源码如下:
Ticker createTicker(TickerCallback onTick) {
...
_ticker = new Ticker(onTick, debugLabel: 'created by $this');
...
return _ticker;
}
这个 Ticker 是个什么东西呢?看看它的注释吧!以下为 Ticker 类的部分注释
- 第一句的大概意思就是 Ticker 类在每一帧动画中都会调用他自己的回调一次
- 第二句的大概意思就是 当 Ticker 被创建之后,默认是 不可用(disabled)的,可以调用
start方法来使 Ticker 变得可用(enable) - 第三句的大概意思就是 创建一个 Ticker,他将在运行时的每一帧都会调用一次所提供的回调
创建 Ticker 到这儿就完了。
如何让Ticker可用呢,也就是如何调用它的 start 方法呢?
我们在启动动画的时候会调用 AnimationController#forward() , 在 forward() 方法中就间接的会调用到 Ticker#start(), Ticker#start() 最后会调用到 scheduleFrame() ,而这个方法里面调用了 ui.window.scheduleFrame(); ,这个 scheduleFrame() 是一个 Native 方法,如下:
void scheduleFrame() native 'Window_scheduleFrame';
上面的方法应该就是调度屏幕刷新的(如有错误,请指出,谢谢)。
到这里,Ticker 也就 start 了,然后 Ticker 就会在动画的每一帧去调一次自己的回调,这个回调
是在 AnimationController 中的构造方法中创建 Ticker 的时候传入的 _ticker = vsync.createTicker(_tick);
我们来看看 _tick 部分源码:
void _tick(Duration elapsed) {
...
notifyListeners();
_checkStatusChanged();
}
这里面调用了 notifyListeners() 和 _checkStatusChanged(),我们先来看看 notifyListeners(),部分源码如下:
void notifyListeners() {
final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
// 执行回调
listener();
} catch (exception, stack) {
...
}
}
}
可以看出,notifyListeners() 中有一个 VoidCallback 的集合,然后遍历这个集合,并且执行集合中所有的回调。
通常我们会使用 .addListener((){...}) 给动画添加监听,我们在调用addListener((){...})之后会执行如下源码
void addListener(VoidCallback listener) {
didRegisterListener();
_listeners.add(listener);
}
将我们传入的回调方法添加到_listeners集合中,也就是上面 notifyListeners() 方法中遍历的集合。
这里我们来小结一下动画的执行流程
- 创建动画
- 创建 Ticker,并 传入回调,回调里面会执行
notifyListeners()方法,此方法中会去遍历_listeners集合,并执行集合中的每一个回调方法
- 创建 Ticker,并 传入回调,回调里面会执行
- 给动画添加监听
- 这里监听时传入的回调会被添加到
_listeners集合中去
- 这里监听时传入的回调会被添加到
- 启动动画
- 会调用
ticker.srtart()来启动Ticker,然后Ticker在动画的每一帧都会 执行回调,也就是我们在第一步中传入的
- 会调用
接下来就是我们在 addListener((){...}) 中干的事情了,通常会这样
_controller.addListener(() {
setState(() {
});
});
也就是我们会在传入的回调方法中去执行 setState((){}) 方法,我们可以看看文档:
setState(VoidCallback fn) 部分文档如下:
大概意思就是:通知 framework 当前对象的内部状态发生改变,这个操作会导致再次调用当前 State 对象的 build 方法,相当于重绘当前对象。
结合之前的分析,整个动画过程就是,当动画启动之后,会不停的去执行addListener((){...})中的回调,而我们在监听回调中又调用了setState(...)方法,所以这就导致当前对象不停的重绘,也就出现了屏幕上的动画的效果。
回到之前的问题:
使用 addListener(...) 和 AnimatedWidget 为什么会出现那种区别呢?
其实我们来看下 AnimatedWidget 的源码立马就明白了
abstract class AnimatedWidget extends StatefulWidget {
const AnimatedWidget({
Key key,
@required this.listenable
}) : assert(listenable != null),
super(key: key);
final Listenable listenable;
@protected
Widget build(BuildContext context);
@override
_AnimatedState createState() => new _AnimatedState();
...
}
我们可以看到 AnimatedWidget 继承自 StatefulWidget, 而 StateFulWidget 在初始化的时候会去创建一个 State 对象来管理自身的状态,也就是回去执行 createState() 方法。
其实到这里已经可以解释两种动画方式存在的区别,因为 AnimatedWidget 内部也有一个 State 对象来管理这自身的状态,而我们之前通过查看文档也知道一个 State 对象只会维护当前对象的状态,所以即使重绘,也只会导致当前 State 对象的重绘,而不会导致其他 State 对象的重绘。
我们来继续看看 AnimatedWidget 中的 createState() 干了什么事,它里面调用了_AnimatedState() 方法,部分源码如下:
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
}
...
@override
void dispose() {
widget.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleChange() {
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
明白了吧!!!
_AnimatedState 在 initState 中给 widget 的 listenable 添加了监听,这里的listenable 就是我们在初始化一个 AnimatedWidget 是传入的 Animation 对象。
而 addListener 中传入的回调是 _handleChange(), 在 _handleChange() 中同样调用了 setState(...)来触发当前对象重绘。
好了, 整个过程就是这样了。如果有什么错误,欢迎大家指出,谢谢!!!










