Flutter 3.19.3 • channel stable • https://github.com/flutter/flutter.git

Framework • revision ba39319843 (8 days ago) • 2024-03-07 15:22:21 -0600

Engine • revision 2e4ba9c6fb

Tools • Dart 3.3.1 • DevTools 2.31.1

本篇为Flutter基建的第十二篇文章💪🏻💪🏻💪🏻,本篇文章主要围绕着ChangeNotifier来介绍,ChangeNotifier我们先从字面意思来理解下,大体就是当某个状态发生改变时可以及时的通知到需要监听此状态的对象,也就是我们通常所说的观察者模式,Flutter中ChangeNotifier相关知识应用点也是比较广泛的,下面我们一起进入文章感受下吧~

flutter EasyRefresh 自定义 header flutter changenotifier_前端

ChangeNotifier简单使用

我们先简单看下ChangeNotifier的定义,了解下内部的几个重要方法,后面这几个方法我们都会接触。

mixin class ChangeNotifier implements Listenable

abstract class Listenable {

  const Listenable();

  factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;

  void addListener(VoidCallback listener);

  void removeListener(VoidCallback listener);
}

ChangeNotifier是Listenable的一个子类,而Listenable内部也就两个主要的方法,addListener()removeListener()就是添加和移除监听者的入口,下面我们就使用这两个方法来感受下ChangeNotifier的最基本使用。

首先定义一个ChangeNotifier的子类,内部维护一个count状态值:

class CountNotifier with ChangeNotifier {
  int count = 0;

  void increase() {
    ++count;
    notifyListeners();
  }
}

这里住一下increase()方法内部先自增了count值,然后还调用了下notifyListeners()方法,这个方法就是为了通知监听者有状态改变了,监听者就可以感应到状态改变,然后获取下最新的count值即可。 (PS:千万别忘记手动调用下notifyListeners()

class CountNotifierWidget extends StatefulWidget {
  const CountNotifierWidget({super.key});

  @override
  State<StatefulWidget> createState() {
    return _CountNotifierState();
  }
}

class _CountNotifierState extends State<CountNotifierWidget> {
  final CountNotifier _countNotify = CountNotifier();
  int _count = 0;

  @override
  void initState() {
    super.initState();
    _countNotify.addListener(updateCount);
  }

  void updateCount() {
    setState(() {
      _count = _countNotify.count;
    });
  }

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      Center(
        child: Text("Count: $_count"),
      ),
      actionButton: FloatingActionButton(
        onPressed: () {
          _countNotify.increase();
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _countNotify.removeListener(updateCount);
  }
}

这段代码逻辑还是比较简单的,我们在initState()中通过_countNotify添加了一个监听,然后在dispose()中移除了此监听,监听的方法中仅仅通过setState()方法改变了当前Widget中的_count值,这样我们在FloatingActionButton的点击事件中调用了_countNotify.increase()方法,当ChangeNotifier内部的count发生改变时,我们Widget中就会收到通知,从而重建界面,达到界面文本更新的效果。

其实刚使用ChangeNotifier的此种方式时,我还是有点纳闷的,此方式有点繁琐,明明ChangeNotifier已经定义过count值了,我们还是需要再Widget中再定义一遍,这样维护了两种状态不利用统一管理,这样显得ChangeNotifier并没有我们想象的那么完美,是否有种别的方式来解决这种问题呢?下面我们来感受下ChangeNotifier+ListenableBuilder的方式。

结合ListenableBuilder使用

我们在写具体的代码之前先看下ListenableBuilder的构造方法,这样可以更好的理解它的具体使用方式。

class ListenableBuilder extends AnimatedWidget {
  /// Creates a builder that responds to changes in [listenable].
  const ListenableBuilder({
    super.key,
    required super.listenable,
    required this.builder,
    this.child,
  });
}

final Listenable listenable;
final TransitionBuilder builder;
typedef TransitionBuilder = Widget Function(BuildContext context, Widget? child);

ListenableBuilder的构造方法我们使用的主要有listenable和builder两个参数,listenable参数就是我们传入的ChangeNotifier对象,builder参数可以帮助我们构建子Widget,并且其子Widget会随着ChangeNotifier内部值的变化而进行界面更新,如果你对ListenableBuilder的内部机制比较感兴趣的话可以进入源码一探究竟,内部也是比较简单的,当我们定义好ListenableBuilder之后,它内部会帮助我们进行ChangeNotifier的addListener()注册,然后在其监听的方法内部调用builder()方法进行界面的重建。这个就是它内部的一个实现原理,其实就是它自身完成了监听和刷新界面的动作,无须我们自己再调用addListener()方法进行监听。

接下来我们再来具体体验下ListenableBuilder的具体使用。

class ListenableBuilderWidget extends StatelessWidget {
  ListenableBuilderWidget({super.key});

  final _countNotify = CountNotifier();

  @override
  Widget build(BuildContext context) {
    debugPrint("Widget build");
    return buildScaffold(
      context,
      Center(
        child: ListenableBuilder(
          listenable: _countNotify,
          builder: (context, child) {
            debugPrint("ListenableBuild build");
            return Text("ListenableBuilder Text: ${_countNotify.count}");
          },
        ),
      ),
      actionButton: FloatingActionButton(
        onPressed: () {
          _countNotify.increase();
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

在使用ListenableBuilder的时候我们就可以直接继承StatelessWidget了,因为我们在Widget内部不会直接拥有ChangeNotifier的状态值,我们直接在ListenableBuilder的builder()参数中直接获取_countNotify.count即可。

这里我们在Widget的build()方法和ListenableBuilder的builder()方法中都加入了debugPrint日志来明确下从进入界面和刷新界面时调用的逻辑。

当我们第一次进入页面时,此时Log会输出:

Widget build
ListenableBuild build

表示Widget的build()方法和ListenableBuilder的builder()方法都会进行一次调用。

然后我们再点击按钮更新文本看看Log会如何输出:

ListenableBuild build
ListenableBuild build
ListenableBuild build
ListenableBuild build
ListenableBuild build
ListenableBuild build

此时无论我们点击多少次按钮,只会有ListenableBuilder的builder()方法进行调用了,也就是说明此时当ChangeNotifier内部的count值发生改变时,不会影响到我们这个页面的重建,只有ListenableBuilder内部的builder()方法被调用,重建的也只有Text组件了。

ChangeNotifier的方式可以帮助我们省略掉内部定义状态值和主动添加、移除监听的操作,这样可以更方便我们去管理组件!

ValueNotifier使用

从上面的内容我们已经了解了ChangeNotifier和ListenableBuilder的具体使用逻辑和简单的实现原理,下面我们再来接触下ChangeNotifier的升级版ValueNotifier。

老样子,在写具体的使用代码之前,还是先看下ValueNotifier的源码,看看它内部是如何升级了ChangeNotifier的。

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  /// Creates a [ChangeNotifier] that wraps this value.
  ValueNotifier(this._value) {
    ...
  }

  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }
}

这里删除了一些多余的代码和注释,我们重点看下它的构造方法和value这个成员方法:

  • 构造方法需要传入一个值,类型为泛型,它是内部状态值的默认值;
  • value这个成员方法的set()方法很有趣,当我们每次给value赋值时,如何和旧的值是相同的那么就什么都不做直接返回,如何值改变了,它会给旧的值赋值上新值,并且这里调用了下notifyListeners()方法,小伙伴们应该还记得这个方法吧,文章的第一部分ChangeNotifier的简单使用中我们还特意PS了下,这个方法就是通知监听者状态改变了,监听者可以获取新值进行界面的刷新等其他操作。

经过源码的分析之后我们是否觉得ValueNotifier也并没有想象中的那么巨大升级,它只是帮助我们维护了一个状态值,并且在状态值发生改变时主动帮助我们通知监听者,简化了我们的一些操作,哈哈,能少写代码尽量少些😏

下面我们就直接使用下ValueNotifier

class CountValueNotifier extends ValueNotifier<int> {
  CountValueNotifier(super.value);

  void increase() {
    ++value;
  }
}

class _CountNotifierState extends State<CountNotifierWidget> {
  int _count = 0;
  final CountValueNotifier _countNotify = CountValueNotifier(0);

  @override
  void initState() {
    super.initState();
    _countNotify.addListener(updateCount);
  }

  void updateCount() {
    setState(() {
      _count = _countNotify.value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      Center(
        child: Text("Count: $_count"),
      ),
      actionButton: FloatingActionButton(
        onPressed: () {
          _countNotify.increase();
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _countNotify.removeListener(updateCount);
  }
}

对比ChangeNotifier的用法来说,在Widget中我们并没有省去任何代码,该定义内部状态还是需要定义,该注册、移除监听的地方还是需要调用,这是在定义ValueNotifier的类时少些了一些代码而已,想要简洁的写法可以将ValueNotifier结合ListenableBuilder,这样可以最大化的简化我们的代码。

这里的具体用法我们就不过多解释了,小伙伴们运行下代码就可以直观的感受效果了,好了,今天的文章分享就到此结束了,ChangeNotifier的用法还是偏向于简单的,但是我们还是需要掌握它内部的一些实现原理,这样可以帮助我们最深入、更彻底的理解监听与通知的基本原理。

写在最后

本篇文章主要介绍了Flutter中ChangeNotifier相关知识的具体使用和基本的实现原理