Flutter局部刷新三剑客

b89aefdee273085e0aca6805d846964d.png

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

ccd923bead64ea04b85ab3574aaabc15.png

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

ChangeNotifier

ChangeNotifier作为数据提供方,给出了响应式编程的基础,我们先来看看ChangeNotifier的源码。
6729b726acd7501c8810f2afa8973309.png
作为一个mixin,它就是实现了Listenable,这又是个什么呢?
5a9c5f8714d8f7009fbd087861f7fb2c.png
这个抽象类,实际上就是实现了addListener和removeListener两个监听的处理。所以接下来我们看看ChangeNotifier是如何实现者两个方法的。
e11e32ea4e09aba79a573e16998a954c.png
源码很简单,就是创建的listener添加到_listeners列表中。
79a174f6027143363c1a92c4e27f16bd.png
移除也很简单。最后看下核心的notifyListeners方法。
0bea1e73899d6ab81961fd32226599b9.png
这个方法就是遍历_listeners,来触发监听Callback。整体就是一个标准的「订阅-发布」流程。

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

dart
class CountNotifier with ChangeNotifier {
  int count = 0;

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

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

dart
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,它的源码如下。
55d94fdaa0f181d9e247ea1e55b74402.png
从源码可以看见,ValueNotifier就是在set方法中,帮你调用了下notifyListeners()方法。同时,ValueNotifier封装了一个泛型变量,简化了ChangeNotifier的创建过程,所以大部分时间我们都是直接使用ValueNotifier。

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

dart
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来解决上面这些问题。

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

dart
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,在它的注释中,就有很多教程和示例。
9ca68a131cef3f4bea3efad677fd24e8.png
再看它的源码。
1ab557c614c3957c9ac9215ebf4d3267.png
这里需要接收3个参数,其中valueListenable用来接收ValueNotifier,builder用来构建Widget,而child,用来创建不依赖ValueNotifier构建的Widget(这是一个很经典的性能优化的例子,如果子构建成本高,并且不依赖于通知符的值,我们将使用它进行优化)。

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

自定义类型

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

dart
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所监听的数据其实并未发生改变,实例的内存地址没发生改变,所以,直接创建一个新的对象,就可以触发更新了,就像下面这样。

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

自定义类型局部刷新

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

dart
class WrapperNotifier extends ValueNotifier<Wrapper> {
  WrapperNotifier(Wrapper value) : super(value);

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

// 调用处
_countNotify.increment();

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

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

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

往期推荐

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

< END >

作者:徐宜生

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

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值