Flutter 小而美系列|TickerProviderStateMixin 对生命周期的影响

8,630 阅读4分钟

本篇源码来源 flutter_stable_1.22.5

0. 问题背景

  1. 针对页面 A (with SingleTickerProviderStateMixinwith TickerProviderStateMixin) 跳转页面 B,再返回页面 A 过程中的生命周期变化。

1. Demo 代码如下:

/// Page A:
class TickerPage1 extends StatefulWidget {
  @override
  _TickerPage1State createState() => _TickerPage1State();
}

class _TickerPage1State extends State<TickerPage1> with SingleTickerProviderStateMixin{
 // 测试点-0
  // class _TickerPage1State extends State<TickerPage1> with TickerProviderStateMixin{
  AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    print('$runtimeType ----- initState');
    
    // 测试点-1
    // _animationController = AnimationController(vsync: this);

    // 测试点-2
    // createTicker((_){});
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('$runtimeType ----- didChangeDependencies');
    
    // 测试点-3
    // print(TickerMode.of(context));
  }


  @override
  void didUpdateWidget(covariant TickerPage1 oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('$runtimeType ----- didUpdateWidget');
  }

  @override
  void dispose() {
    print('$runtimeType ----- dispose');
    _animationController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('$runtimeType ----- build');
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: RaisedButton(
          child: Text('TickerPage1'),
          onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => _TickerPage2())),
        ),
      ),
    );
  }
}

/// Page B
class _TickerPage2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('$runtimeType ----- build');
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text('TickerPage2'),
      ),
    );
  }
}

Page A 标注了四个测试点,可以试着放开任一测试点,看看打印结果。

1.省时间版本

  1. 正常没有任何 TickerProviderStateMixin(无论是否 Single),生命周期回调顺序如下:

Page A ----- initState

Page A ----- didChangeDependencies

Page A ----- build

Page A --- push ---> Page B

Page B ----- build

Page B --- pop ---> Page A

// nothing

  1. Page A with SingleTickerProviderStateMixin 后变化(无 AnimtionController 之类依赖 TickerProvider):

同上结论1

  1. Page A with TickerProviderStateMixin 变化:

Page A ----- initState

Page A ----- didChangeDependencies

Page A ----- build

Page A --- push ---> Page B

Page B ----- build

Page B --- pop ---> Page A

Page A ----- didChangeDependencies

Page A ----- build

  1. Page A with SingleTickerProviderStateMixin 后变化(有 AnimtionController 之类依赖 TickerProvider ):

同上结论3

  1. 本质不在于是否 with SingleTickerProviderStateMixinwith TickerProviderStateMixin(测试点0),是否使用了 AnimationController 或创建了 Ticker (测试点1、2),而是取决于是否绑定了 TickerMode(测试点3)。

  2. 结论: SingleTickerProviderStateMixin(被 AnimtionController 绑定后) 或 TickerProviderStateMixin 会在 didChangeDependencies 中通过 TickMode.of(context) 绑定 _EffectiveTickerMode(InheritedWidget 类型) 从而获得 Ticker 是否处于 enable 状态。因此,页面 A 跳转页面 B 再回来的场景中,会因为 TickerMode 状态的变化,而重新构建(didChangeDependencies ---> build)。

2. 源码版本

1、ticker_provider.dart 文件看下相关类的目录结构

2、从 AnimationController 看起

 // 测试点-1
 AnimationController _animationController = AnimationController(vsync: this);
    
 AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    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);
  }

第 18 行,vsync.createTicker(_tick) 在构造函数里创建了一个 Ticker。

abstract class TickerProvider {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const TickerProvider();

  /// Creates a ticker with the given callback.
  ///
  /// The kind of ticker provided depends on the kind of ticker provider.
  @factory
  Ticker createTicker(TickerCallback onTick);
}

TickerProvider 是一个抽象类,SingleTickerProviderStateMixinTickerProviderStateMixin分别是具体的实现子类。

3、先看 SingleTickerProviderStateMixin

mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker _ticker;

  @override
  Ticker createTicker(TickerCallback onTick) {
    _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
    return _ticker;
  }

  // ...已剔除无关代码

  @override
  void didChangeDependencies() {
    if (_ticker != null)
      _ticker.muted = !TickerMode.of(context);
    super.didChangeDependencies();
  }

第 5 行,创建了一个 Ticker 对象 。这里对应的就是 AnimationController 初始化过程中的 _ticker = vsync.createTicker(_tick);

第 13 行开始,didChangeDependencies方法里,当 _ticker 不为空,会调用 TickerMode.of(context)方法。两个点:1. 不为空,对应的就是有没调用 createTicker方法或者说是有没有初始化 AnimationController(vsync: this)。 2.TickerMode.of(context)后面一起讲,它是我们这次的核心关键^^。

4、再对比看下 TickerProviderStateMixin

mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Set<Ticker> _tickers;

  @override
  Ticker createTicker(TickerCallback onTick) {
    _tickers ??= <_WidgetTicker>{};
    final _WidgetTicker result = _WidgetTicker(onTick, this, debugLabel: 'created by $this');
    _tickers.add(result);
    return result;
  }
  
  // ...已剔除无关代码
  
  @override
  void didChangeDependencies() {
    final bool muted = !TickerMode.of(context);
    if (_tickers != null) {
      for (final Ticker ticker in _tickers) {
        ticker.muted = muted;
      }
    }
    super.didChangeDependencies();
  }

}

class _WidgetTicker extends Ticker {
  _WidgetTicker(TickerCallback onTick, this._creator, { String debugLabel }) : super(onTick, debugLabel: debugLabel);

  final TickerProviderStateMixin _creator;

  @override
  void dispose() {
    _creator._removeTicker(this);
    super.dispose();
  }
}

第 5 行,同样是创建 Ticker 对象,不过这里创建的是 _WidgetTicker类型的 Ticker 对象并返回,同时塞进了局部变量 _tickers 集合里。这里对应的就是每一个 AnimationController 初始化过程的 _ticker = vsync.createTicker(_tick);,拿到的是 _WidgetTicker (Ticker 子类型),并且被 TickerProviderStateMixin 管理在集合_tickers里了。

第 15 行,这里可以看到 TickerProviderStateMixin 是直接调用了 TickerMode.of(context)

5、来看核心关键点 TickerMode.of(context)

class TickerMode extends StatelessWidget {
  const TickerMode({
    Key key,
    @required this.enabled,
    this.child,
  }) : assert(enabled != null),
       super(key: key);

 // ...已剔除无关代码
 
  static bool of(BuildContext context) {
    final _EffectiveTickerMode widget = context.dependOnInheritedWidgetOfExactType<_EffectiveTickerMode>();
    return widget?.enabled ?? true;
  }

  @override
  Widget build(BuildContext context) {
    return _EffectiveTickerMode(
      enabled: enabled && TickerMode.of(context),
      child: child,
    );
  }
}

class _EffectiveTickerMode extends InheritedWidget {
  const _EffectiveTickerMode({
    Key key,
    @required this.enabled,
    Widget child,
  }) : assert(enabled != null),
        super(key: key, child: child);

  final bool enabled;

 // ...已剔除无关代码
}

第 11 行, TickerMode.of(context) 通过 context.dependOnInheritedWidgetOfExactType 查找控件树中类型是 _EffectiveTickerMode 的控件并建立依赖关系(如何建立依赖本篇不讨论)。而后者是一个 InheritedWidget 类型的控件,含有一个 enable 的局部变量,标示当前 TickerMode 的状态。

真相大白!

页面 PageA with SingleTickerProviderStateMixinwith TickerProviderStateMixin会在 didChangeDependencies 生命周期里调用 TickerMode.of(context)获取 TickerMode 状态,从而建立起依赖关系(实际是对 _EffectiveTickerMode 的包装 Widget)。

6、最后一点 TickerMode 为何可以在页面跳转过程中回调不同状态的?

答案还是要从 Overlay 这个大舞台里找到。

class OverlayState extends State<Overlay> with TickerProviderStateMixin {
@override
  Widget build(BuildContext context) {
    // This list is filled backwards and then reversed below before
    // it is added to the tree.
    final List<Widget> children = <Widget>[];
    bool onstage = true;
    int onstageCount = 0;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageCount += 1;
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
        ));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
          tickerEnabled: false,
        ));
      }
    }
    return _Theatre(
      skipCount: children.length - onstageCount,
      children: children.reversed.toList(growable: false),
    );
  }
  
 // ...已剔除无关代码
}

第 13 行与 20 行,Overlay 最终通过 build 返回的剧场里,将舞台上将可见 & 不可见但需要保持状态的“一幕幕” OverlayEntry 包裹进 _OverlayEntryWidget 再排序。

class _OverlayEntryWidget extends StatefulWidget {
  const _OverlayEntryWidget({
    @required Key key,
    @required this.entry,
    this.tickerEnabled = true,
  }) : assert(key != null),
       assert(entry != null),
       assert(tickerEnabled != null),
       super(key: key);

  final OverlayEntry entry;
  final bool tickerEnabled;

  @override
  _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState();
}

class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
  @override
  Widget build(BuildContext context) {
    return TickerMode(
      enabled: widget.tickerEnabled,
      child: widget.entry.builder(context),
    );
  }

  void _markNeedsBuild() {
    setState(() { /* the state that changed is in the builder */ });
  }
}

第 5 行,入参 this.tickerEnabled 默认 true。

第 21 行,入参 OverlayEntry 最终被包裹进入 TickerMode ,并根据其是否可见(onstage)附带了 Ticker 的状态。

3. 结语

欢迎大家不吝指正,我也会尽量削减此系列的篇幅,保证小而美!

完结撒花,感谢大家观看!