点击上方蓝字关注我,知识会给你力量

局部刷新作为提高Flutter页面性能的重要手段,是每一个Flutter老手都必须掌握的技巧。当然,我们不用非得使用Riverpod、Provider、Bloc这些状态管理工具来实现局部刷新,Flutter框架本身也给我们提供了很多方便快捷的刷新方案,今天要提的就是Notifier三剑客,用它来处理局部刷新,代码优雅又方便,可谓是居家必备之良器。

ChangeNotifier

ChangeNotifier作为数据提供方,给出了响应式编程的基础,我们先来看看ChangeNotifier的源码。

打开网易新闻 查看精彩图片

作为一个mixin,它就是实现了Listenable,这又是个什么呢?

打开网易新闻 查看精彩图片

这个抽象类,实际上就是实现了addListener和removeListener两个监听的处理。所以接下来我们看看ChangeNotifier是如何实现者两个方法的。

打开网易新闻 查看精彩图片

源码很简单,就是创建的listener添加到_listeners列表中。

打开网易新闻 查看精彩图片

移除也很简单。最后看下核心的notifyListeners方法。

打开网易新闻 查看精彩图片

这个方法就是遍历_listeners,来触发监听Callback。整体就是一个标准的「订阅-发布」流程。

作为Notifier家族的长辈,它的使用会略复杂一些,我们来看一个例子。首先,需要mixin一个ChangeNotifier。

class CountNotifier with ChangeNotifier {
 int count = 0;

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

然后再创建一个TestWidget来调用这个ChangeNotifier。

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

 @override
 State createState() { 
 return _CountNotifierState();
 }
}

class _CountNotifierState extends State

 { 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 Scaffold( body: Center( child: Text("Test: $_count"), ), floatingActionButton: FloatingActionButton( onPressed: () => _countNotify.increase(), child: const Icon(Icons.add), ), ); } @override void dispose() { super.dispose(); _countNotify.removeListener(updateCount); } }

这样当我们修改ChangeNotifier的value的时候,就会Callback到updateCount实现刷新。

这样就形成了一个响应式的基础模型,数据修改,监听者刷新UI,完成了响应式的同时,也实现了局部刷新的功能,提高了性能。

ValueNotifier

在使用ChangeNotifier的时候,每次在修改变量时,都需要手动调用notifyListeners()方法,所以,Flutter创建了一个新的组件——ValueNotifier,它的源码如下。

打开网易新闻 查看精彩图片

从源码可以看见,ValueNotifier就是在set方法中,帮你调用了下notifyListeners()方法。同时,ValueNotifier封装了一个泛型变量,简化了ChangeNotifier的创建过程,所以大部分时间我们都是直接使用ValueNotifier。

那么有了它之后,我们就可以省去新建类的步骤,对于单一的基础类型变量,直接创建ValueNotifier即可,就像上面的例子,我们可以直接改造成下面这样。

class _CountNotifierState extends State

 { final ValueNotifier

 _countNotify = ValueNotifier(0); @override void initState() { super.initState(); _countNotify.addListener(updateCount); } void updateCount() { setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text("Test: ${_countNotify.value}"), ), floatingActionButton: FloatingActionButton( onPressed: () => _countNotify.value++, child: const Icon(Icons.add), ), ); } @override void dispose() { super.dispose(); _countNotify.removeListener(updateCount); } }

这样我们就简化了不少的模板代码。

ValueListenableBuilder

我们从ChangeNotifier到ValueNotifier,逐步减少了模板代码的创建,但是依然还有很多问题,比如我们还是需要手动addListener、removeListener或者是dispose,同时,还需要使用setState来刷新页面,如果Context控制不好,很容易造成整个页面的刷新。因此,Flutter在它们的基础之上,又提供了ValueListenableBuilder来解决上面这些问题。

我们继续改造上面的例子。

class _CountNotifierState extends State

 { final ValueNotifier

 _countNotify = ValueNotifier(0); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ValueListenableBuilder

 ( valueListenable: _countNotify, builder: (context, value, child) { return Text('Value: $value'); }, ), ), floatingActionButton: FloatingActionButton( onPressed: () => _countNotify.value++, child: const Icon(Icons.add), ), ); } @override void dispose() { super.dispose(); _countNotify.dispose(); } }


可以发现,我们使用ValueListenableBuilder来根据ValueNotifier的改变而刷新Widget。这样不仅简化了代码模板,而且不再使用setState来进行页面刷新。

ValueListenableBuilder作为一个非常经典的Widget,在它的注释中,就有很多教程和示例。

打开网易新闻 查看精彩图片

再看它的源码。

打开网易新闻 查看精彩图片

这里需要接收3个参数,其中valueListenable用来接收ValueNotifier,builder用来构建Widget,而child,用来创建不依赖ValueNotifier构建的Widget(这是一个很经典的性能优化的例子,如果子构建成本高,并且不依赖于通知符的值,我们将使用它进行优化)。

这个优化方案非常经典,在Flutter的很多地方都有使用这个技巧,特别是动画这块的处理。通常来说ValueNotifier对应ValueListenableBuilder,Listenable、ChangeNotifier对应AnimatedBuilder。

自定义类型

在使用自定义类型时,例如一个包装类,那么当你改变它的某个属性值时,ValueListenableBuilder是不会刷新的,我们来看下面这个例子。

class Wrapper {
 int age;
 String name;

 Wrapper({this.age = 0, this.name = ''});
}

class _CountNotifierState extends State

 { final ValueNotifier _countNotify = ValueNotifier(Wrapper()); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ValueListenableBuilder ( valueListenable: _countNotify, builder: (context, value, child) { return Text('Value: ${value.age}'); }, ), ), floatingActionButton: FloatingActionButton( onPressed: () => _countNotify.value.age = _countNotify.value.age + 1, child: const Icon(Icons.add), ), ); } @override void dispose() { super.dispose(); _countNotify.dispose(); } }

这样的话,ValueListenableBuilder就失去作用了,其原因也很简单,ValueNotifier所监听的数据其实并未发生改变,实例的内存地址没发生改变,所以,直接创建一个新的对象,就可以触发更新了,就像下面这样。

onPressed: () => _countNotify.value = Wrapper(age: 10),

自定义类型局部刷新

上面这种自定义模型的刷新方法还是略显复杂了一点,每次更新的时候,都要copy一下数据来实现更新,实际上,ValueNotifier继承自ChangeNotifier,所以可以通过手动调用notifyListeners的方式来进行刷新,我们改造下上面的例子。

class WrapperNotifier extends ValueNotifier

 { WrapperNotifier(Wrapper value) : super(value); void increment() { value.age++; notifyListeners(); } } // 调用处 _countNotify.increment();

通过这种方式,我们可以实现当模型内部变量更新时,局部进行刷新了。

向大家推荐下我的网站 https://www.yuque.com/xuyisheng 点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

作者:徐宜生

更文不易,点个“三连”支持一下