Flutter 中的 Animations(三)

官方文档ide

若是没有看过以前两节的,建议先看看前两节的内容post

上一节中咱们主要作的事情以下:
  • AnimationaddListener(...) 中调用 setState(...) 来给 widget 添加动画
  • 使用 AnimatedWidget 的方法来给 widget 添加动画

上一节中咱们也经过案例的形式展示出以上两种方式的区别,得出的结论就是以下: 使用 addListener(...)setState(...)Widget 添加动画的时候,会致使其余的 Widget 也跟着重绘,而使用 AnimatedWidget 的方式给 Widget 添加动画的时候只会重绘当前的 Widget动画

这是为何呢 ???

咱们先来看看动画的执行流程。ui

咱们在使用动画的时候会初始化一个 AnimationControllerthis

AnimationController controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
复制代码

能够看出,初始化 AnimationController 须要传递两个参数,一个是 vsync 一个是 duration ,这个 duration 很容易理解,就是动画执行完毕须要的时长,那 vsync 是什么啊?上述代码中咱们传入了 this ,那是由于咱们在 Statewith(混入,第一节 有讲)SingleTickerProviderStateMixin,下面咱们看看初始化 AnimationController 时都干了什么事,源码以下:spa

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);
  }
复制代码

咱们抽重点出来看debug

_ticker = vsync.createTicker(_tick);
复制代码

这里调用了 vsynccreateTicker(...) 方法建立了一个 Ticker, 而咱们在初始化 AnimationController 的时候 vsync 参数传的是 SingleTickerProviderStateMixinSingleTickerProviderStateMixin#createTicker(...) 的源码以下:code

Ticker createTicker(TickerCallback onTick) {

    ...
    
    _ticker = new Ticker(onTick, debugLabel: 'created by $this');
    
    ...
    
    return _ticker;
  }
复制代码

这个 Ticker 是个什么东西呢?看看它的注释吧!如下为 Ticker 类的部分注释对象

Calls its callback once per animation frame.继承

When created, a ticker is initially disabled. Call [start] to enable the ticker.

Creates a ticker that will call the provided callback once per frame while running.

  • 第一句的大概意思就是 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集合,并执行集合中的每个回调方法
  • 给动画添加监听
    • 这里监听时传入的回调会被添加到 _listeners 集合中去
  • 启动动画
    • 会调用 ticker.srtart() 来启动 Ticker,而后 Ticker 在动画的每一帧都会 执行回调,也就是咱们在第一步中传入的

接下来就是咱们在 addListener((){...}) 中干的事情了,一般会这样

_controller.addListener(() {
      setState(() {

      });
    });
复制代码

也就是咱们会在传入的回调方法中去执行 setState((){}) 方法,咱们能够看看文档:

setState(VoidCallback fn) 部分文档以下:

Notify the framework that the internal state of this object has changed.

Calling [setState] notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a [build] for this [State] object.

大概意思就是:通知 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);
复制代码

明白了吧!!!

_AnimatedStateinitState 中给 widgetlistenable 添加了监听,这里的listenable 就是咱们在初始化一个 AnimatedWidget 是传入的 Animation 对象。

addListener 中传入的回调是 _handleChange(), 在 _handleChange() 中一样调用了 setState(...)来触发当前对象重绘。

好了, 整个过程就是这样了。若是有什么错误,欢迎你们指出,谢谢!!!

相关文章
相关标签/搜索