ValueListenableBuilder组件基本用法

375 阅读3分钟

刚开始上手flutter,最近在做需求的时候,发现库里的代码到处都在用这个组件,简单总结一下用法。

什么时候用?

  • 局部刷新statufulWidget可以通过setState来监听更新,但往往是在全局整体都有变化的情况下使用,因为setState后会重构整个build方法,造成一些不必要的刷新,此时考虑引入ValueListenableBuilder。使用时尽可能让ValueListenableBuilder只构建依赖数据源的widget,这样的话可以缩小重新构建的范围,也就是说ValueListenableBuilder的拆分粒度应该尽可能细。
  • 数据流向并非从上至下InheritedWidget提供一种在 widget 树中从上到下共享数据的方式,但是也有很多场景数据流向并非从上到下,比如从下到上或者横向等,这时也需要用到ValueListenableBuilder

基本用法

构造函数如下:

const ValueListenableBuilder({
  Key? key,
  required this.valueListenable, // 数据源,类型为ValueListenable<T>
  required this.builder, // builder
  this.child,
}
  • valueListenable:类型为 ValueListenable<T>,表示一个可监听的数据源。ValueListenable<T> 是一个抽象类,不能直接使用, ValueNotifier 是其实现类之一,接收一个泛型。
  • builder:对应的类型为 ValueWidgetBuilder<T>,本质是一个方法。数据源发生变化通知时,会重新调用 builder 重新 build 子组件树。
  • child: 如果builder子组件树中有一些不变的部分,可以传递给child,child 会作为builder的第三个参数传递给 builder,通过这种方式就可以实现组件缓存,原理和AnimatedBuilder 第三个 child 相同。

一般在写法上,分为以下几步:

  1. 先在需要监听的类中声明一个 ValueNotifier变量。
  2. 在接收该变量变化的位置用Notifier.value = newValue的形式接收变化的值。
  3. 在需要局部刷新的位置构造一个ValueListenableBuilder实例,其参数如下:
    • Notifier赋给参数ValueListenable<T>
    • builder参数传入一个回调函数,该函数的参数分别为(BuildContext context, int value, Widget child),这里的value为Notifier收到的值,child则是ValueListenableBuilder实例中传入的child参数的值。函数体中具体写要重构的内容。
    • 写局部刷新中不刷新的部分,传入child参数。

实例

以最基本的计数器为例,来看看具体怎么用。

class ValueListenableRoute extends StatefulWidget {
  const ValueListenableRoute({Key? key}) : super(key: key);

  @override
  State<ValueListenableRoute> createState() => _ValueListenableState();
}

class _ValueListenableState extends State<ValueListenableRoute> {
  // 定义一个ValueNotifier,当数字变化时会通知 ValueListenableBuilder
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);
  static const double textScaleFactor = 1.5;

  @override
  Widget build(BuildContext context) {
    // 添加 + 按钮不会触发整个 ValueListenableRoute 组件的 build
    print('build');
    return Scaffold(
      appBar: AppBar(title: Text('ValueListenableBuilder 测试')),
      body: Center(
        child: ValueListenableBuilder<int>(
          builder: (BuildContext context, int value, Widget? child) {
            // builder 方法只会在 _counter 变化时被调用
            return Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                child!,
                Text('$value 次',textScaleFactor: textScaleFactor),
              ],
            );
          },
          valueListenable: _counter,
          // 当子组件不依赖变化的数据,且子组件收件开销比较大时,指定 child 属性来缓存子组件非常有用
          child: const Text('点击了 ', textScaleFactor: textScaleFactor),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        // 点击后值 +1,触发 ValueListenableBuilder 重新构建
        onPressed: () => _counter.value += 1,
      ),
    );
  }
}

参考:

  1. 《Flutter实战第二版》
  2. juejin.cn/post/691220…