• Flutter在同一个Page中,可能存在很多的不同的Widget,这些Widget都在同一个Page层级之下,当某个Widget的状态发生改变之后,需要让其它Widget响应。

    另外一种,就是多页面之间的数据共享。

    那它们的区别是什么呢,在同一个Page下,所有的Widget与Page根Widget是可以形成父子关系的,因为通过PageRoute产生的新页面,其Page根Widget是挂载到App根Widget上的,多个Page之间的Widget,不存在父子关系。

    首先,我们先来看下同页面跨Widget数据管理。

    方案1-1 :StatefulWidget

    这个相信大家都很了解了,StatefulWidget通过State来保存状态,当调用setState函数之后,整个StatefulWidget会重新执行build函数,从而使用全新的数据,生成新的Widget,这样看来,有了StatefulWidget之后,是不是就可以完全实现同页面的数据管理了呢?

    的确可以,但是有个问题,如果页面里面有100个Widget,数据发生改变后,只有一个Widget需要接受这个改变,修改自己的UI,但是在这个StatefulWidget中,由于调用了setState函数,所以这个页面中的100个Widget都将执行重建,这显然是「家里有矿系列」,所以为了避免这个问题,就需要缩小StatefulWidget的范围,让setState函数控制的刷新,尽可能的范围小,这样当100个Widget中只有一个需要重建时,就不需要重新创建那99个不需要的Widget了。

    但是新的问题又来了,StatefulWidget的范围小了,发生在这个StatefulWidget之外的数据改变,如何让这个StatefulWidget进行刷新呢?这时候,就需要利用到Flutter的响应式编程架构了。

    方案1-2:ValueNotifier

    从ValueNotifier的注释就能看明白,ValueNotifier实际上实现了一个观察者模式,ValueNotifier会持有一个Value对象,当Value对象发生改变时,即会通知到所有注册过的观察者。

    A [ChangeNotifier] that holds a single value.

    When [value] is replaced with something that is not equal to the old
    value as evaluated by the equality operator ==, this class notifies its
    listeners.

    那么借助ValueNotifier,就可以实现同Page内跨Widget的数据管理,将需要管理的数据托管给ValueNotifier,所有需要因为该数据而改变的Widget,都会注册监听,那么在数据发生改变时,ValueNotifier将自动通知到所有监听者,从而实现数据的管理。

    下面这个例子,就演示了一个最简单的ValueNotifier的使用。

    class ValueNotifierWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        ValueNotifier<String> valueNotifier = ValueNotifier<String>('Init String Data');

        return Column(
          children: <Widget>[
            MainTitleWidget('ValueNotifier基本使用'),
            SubtitleWidget('在需要响应的Widget中addListener之后,一旦ValueNotifier的值发生改变,就会触发通知'),
            NotifierWidget(data: valueNotifier),
            RaisedButton(
              onPressed: () => valueNotifier.value = 'New Value ${Random().nextInt(100)}',
              child: Text('Change'),
            ),
          ],
        );
      }
    }

    class NotifierWidget extends StatefulWidget {
      final ValueNotifier<String> data;

      NotifierWidget({this.data});

      @override
      _NotifierWidgetState createState() => _NotifierWidgetState();
    }

    class _NotifierWidgetState extends State<NotifierWidget> {
      String info;

      @override
      initState() {
        super.initState();
        widget.data.addListener(changeNotifier);
        info = '${widget.data.value}';
      }

      void changeNotifier() {
        setState(() => info = '${widget.data.value}');
      }

      @override
      Widget build(BuildContext context) {
        return Text(
          info,
          style: TextStyle(fontSize: 30),
        );
      }

      @override
      dispose() {
        widget.data.removeListener(changeNotifier);
        super.dispose();
      }
    }

    NotifierWidget注册了对ValueNotifier的监听,当Demo页面中的其它Widget触发了ValueNotifier的更新的时候(RaisedButton触发),NotifierWidget会自动接受到通知,从而刷新UI。

    自定义ValueNotifier

    ValueNotifier同样可以指定自定义类型,其原理与使用基础类型是一样的。

    class ValueNotifierWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        PersonNotifier customNotifier = PersonNotifier(People('xuyisheng', 18));

        return Column(
          children: <Widget>[
            MainTitleWidget('Custom ValueNotifier'),
            CustomNotifierWidget(data: customNotifier),
            RaisedButton(
              onPressed: () => customNotifier.changePeopleName('zhujia'),
              child: Text('Change'),
            ),
          ],
        );
      }
    }

    class CustomNotifierWidget extends StatefulWidget {
      final ValueNotifier<People> data;

      CustomNotifierWidget({this.data});

      @override
      _CustomNotifierWidgetState createState() => _CustomNotifierWidgetState();
    }

    class _CustomNotifierWidgetState extends State<CustomNotifierWidget> {
      People info;

      @override
      initState() {
        super.initState();
        widget.data.addListener(changeNotifier);
        info = widget.data.value;
      }

      void changeNotifier() {
        setState(() => info = widget.data.value);
      }

      @override
      Widget build(BuildContext context) {
        return Text(
          '${info.name},${info.age}',
          style: TextStyle(fontSize: 30),
        );
      }

      @override
      dispose() {
        widget.data.removeListener(changeNotifier);
        super.dispose();
      }
    }

    class People {
      String name;
      int age;

      People(this.name, this.age);
    }

    class PersonNotifier extends ValueNotifier<People> {
      PersonNotifier(People value) : super(value);

      void changePeopleName(String name) {
        value.name = name;
        notifyListeners();
      }
    }

    同样是点击RaisedButton后,改变ValueNotifier.value的值,从而修改UI。

    通过ValueNotifier,我们将每个可能因为共享数据的变化而改变的Widget,封装起来,从而在数据改变的时候,只更新监听了该数据的Widget。

    但是大家有没有发现,在使用ValueNotifier的时候,是有些冗余的,就好像前面用到的NotifierWidget,实际上大部分的ValueNotifier都需要这样配合使用,所以,Flutter也提供了这样一个类似的Widget——ValueListenableBuilder。

    1-3:ValueListenableBuilder

    ValueListenableBuilder正是这样一个Widget,它封装了对ValueNotifier的使用,简化了其创建过程,Flutter Dojo的首页上,PageView和下面的进度条保存同步的过程,就是通过ValueListenableBuilder来实现的。

    ValueListenableBuilder的使用范式非常简单,即在多个创建修改、监听修改的Widget上,通过ValueNotifier来共享管理数据。

    由于ValueListenableBuilder是一个StatefulWidget,所以它们的父Widget可以直接使用StatelessWidget来组织Widget,一个简单的示例如下所示。

    class ValueListenableBuilderWidget extends StatefulWidget {
      @override
      _ValueListenableBuilderWidgetState createState() => _ValueListenableBuilderWidgetState();
    }

    class _ValueListenableBuilderWidgetState extends State<ValueListenableBuilderWidget> {
      int _counter = 0;

      final ValueNotifier<int> _notifier = ValueNotifier<int>(0);

      void _incrementCounter() {
        _counter++;
        _notifier.value++;
      }

      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            MainTitleWidget('ValueListenableBuilder基本使用'),
            SubtitleWidget('修改数据时未调用setState,所以未通过ValueListenableBuilder管理的数据不会发生改变'),
            SizedBox(height: 20),
            ValueListenableBuilder(
              valueListenable: _notifier,
              builder: (context, value, widget) {
                return Text('Click with ValueListenableBuilder $value');
              },
            ),
            SizedBox(height: 20),
            Text('Click without setState $_counter'),
            SizedBox(height: 20),
            RaisedButton(
              onPressed: () {
                _incrementCounter();
              },
              child: Text('Click me'),
            ),
          ],
        );
      }
    }

    修仙

    Flutter Dojo开源至今,受到了很多Flutter学习者和爱好者的喜爱,也有越来越多的人加入到Flutter的学习中来,所以我建了个Flutter修仙群,但是人数太多,所以分成了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指东】三个群,对Flutter感兴趣的朋友,可以添加我的微信,注明加入Flutter修仙群,或者直接关注我的微信公众号【Android群英传】。

    FlutterDojo设计之道—状态管理之路(一)-LMLPHP

    感兴趣的朋友可以加我微信【Tomcat_xu】,我拉你入群。

    项目地址:

    https://github.com/xuyisheng/flutter_dojo


    本文分享自微信公众号 - Android群英传(android_heroes)。
    如有侵权,请联系 support@oschina.cn 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    09-03 06:40