FlutterDojo設計之道—狀態管理之路(一)

Flutter萬物皆Widget的理念很容易搭建出這樣一個WidgetTree。android

在這個Widget Tree中,一般會存在不少組件之間的相互依賴,時間一長,就很容易變成下面這樣。git

這是申明式編程的通病,由於Widget用於展現數據,而數據可能來源於不少其它的Widget,這時候跨Widget共享數據、傳遞數據,就變得很麻煩,並且不容易管理。github

因此,Flutter在StatelessWidget、StatefulWidget的基礎之上,還有一個InheritedWidget,專門用於進行數據、狀態的共享與傳遞,除此以外,申明式編程獨特的響應式架構,也經過觀察者模式,讓數據狀態改變的監聽變得比較容易,這些都是Flutter處理數據的優點。web

下面的文章,將帶領你們梳理Flutter中的數據流向,掌握Flutter的狀態管理方案。編程

開篇

要管理Widget的數據、狀態,首先要了解下,在Flutter中有哪些須要管理數據的場景。微信

通常來講,數據管理有兩個場景:架構

  • 同頁面跨Widget數據管理
  • 跨頁面數據管理

Flutter在同一個Page中,可能存在不少的不一樣的Widget,這些Widget都在同一個Page層級之下,當某個Widget的狀態發生改變以後,須要讓其它Widget響應。less

另一種,就是多頁面之間的數據共享。dom

那它們的區別是什麼呢,在同一個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來實現的。

代碼位置: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羣英傳】。

感興趣的朋友能夠加我微信【Tomcat_xu】,我拉你入羣。

項目地址:

https://github.com/xuyisheng/flutter_dojo


本文分享自微信公衆號 - Android羣英傳(android_heroes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索