12

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

 3 years ago
source link: https://blog.csdn.net/x359981514/article/details/108211976
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Flutter万物皆Widget的理念很容易搭建出这样一个WidgetTree。

32qQFbB.jpg!mobile

在这个Widget Tree中,通常会存在很多组件之间的相互依赖,时间一长,就很容易变成下面这样。

AVriymB.jpg!mobile

这是申明式编程的通病,因为Widget用于展示数据,而数据可能来源于很多其它的Widget,这时候跨Widget共享数据、传递数据,就变得很麻烦,而且不容易管理。

所以,Flutter在StatelessWidget、StatefulWidget的基础之上,还有一个InheritedWidget,专门用于进行数据、状态的共享与传递,除此之外,申明式编程独特的响应式架构,也通过观察者模式,让数据状态改变的监听变得比较容易,这些都是Flutter处理数据的优势。

下面的文章,将带领大家梳理Flutter中的数据流向,掌握Flutter的状态管理方案。

要管理Widget的数据、状态,首先要了解下,在Flutter中有哪些需要管理数据的场景。

一般来说,数据管理有两个场景:

  • 同页面跨Widget数据管理

  • 跨页面数据管理

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

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

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

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

为了保证文章的完整性,本文会由浅入深,依次讲解Flutter中状态管理的方方面面,所以有些冗余的地方,请不要介意。

方案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。

代码位置:Flutter Dojo-Widget-Async-ValueNotifier

自定义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。

代码位置:Flutter Dojo-Widget-Async-ValueNotifier

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

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

1-3:ValueListenableBuilder

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

IRJza2n.jpg!mobile

代码位置:Flutter Dojo-/pages/main/mainpage_scroll_container.dart

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-Widget-Async-ValueListenableBuilder

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

ZjaMZv.jpg!mobile

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

项目地址:

https://github.com/xuyisheng/flutter_dojo


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK