点击上方蓝字关注我,知识会给你力量
局部刷新作为提高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小时后转载。
作者:徐宜生
更文不易,点个“三连”支持一下
热门跟贴