注册

Flutter局部刷新三剑客

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


ChangeNotifier


ChangeNotifier作为数据提供方,给出了响应式编程的基础,我们先来看看ChangeNotifier的源码。
image.png
作为一个mixin,它就是实现了Listenable,这又是个什么呢?
image.png
这个抽象类,实际上就是实现了addListener和removeListener两个监听的处理。所以接下来我们看看ChangeNotifier是如何实现者两个方法的。
image.png
源码很简单,就是创建的listener添加到_listeners列表中。
image.png
移除也很简单。最后看下核心的notifyListeners方法。
image.png
这个方法就是遍历_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<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 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,它的源码如下。
image.png
从源码可以看见,ValueNotifier就是在set方法中,帮你调用了下notifyListeners()方法。同时,ValueNotifier封装了一个泛型变量,简化了ChangeNotifier的创建过程,所以大部分时间我们都是直接使用ValueNotifier。


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


class _CountNotifierState extends State<CountNotifierWidget> {
final ValueNotifier<int> _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<CountNotifierWidget> {
final ValueNotifier<int> _countNotify = ValueNotifier(0);

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ValueListenableBuilder<int>(
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,在它的注释中,就有很多教程和示例。
image.png
再看它的源码。
image.png
这里需要接收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<CountNotifierWidget> {
final ValueNotifier<Wrapper> _countNotify = ValueNotifier(Wrapper());

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ValueListenableBuilder<Wrapper>(
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<Wrapper> {
WrapperNotifier(Wrapper value) : super(value);

void increment() {
value.age++;
notifyListeners();
}
}

// 调用处
_countNotify.increment();

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


欢迎大家关注我的公众号——【群英传】,专注于「Android」「Flutter」「Kotlin」
我的语雀知识库——http://www.yuque.com/xuyisheng


作者:xuyisheng
来源:juejin.cn/post/7381767811679502346

0 个评论

要回复文章请先登录注册