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

書接上回,咱們講到Flutter中同Page下跨Widget的數據管理。android

第一種方案,咱們使用ValueNotifier和ValueListenableBuilder來實現了。git

此次,再介紹Flutter中的另外一種數據管理方式——Notification。嚴格來講,Notification並非一個跨Widget數據管理方案,它只完成了一半的功能,即Notification實現了數據狀態修改的通知,可是須要監聽的Widget收到通知後的處理,仍是須要本身來實現的,這個實現,簡單的說,能夠是setState來重建UI,複雜了說,能夠是配合其它任何一種數據管理/刷新的方案。github

Notification

Notification是Flutter中數據傳遞的一種機制。在Flutter的Widget樹上,每一個節點均可以發出Notification,Notification會沿着當前節點向上傳遞,全部的父節點均可以經過NotificationListener來監聽Notification的改動。web

Flutter中將這種由子節點向父節點傳遞Notification的機制稱爲通知冒泡(Notification Bubbling)。微信

Flutter中的不少地方使用了Notification,如Scrollable Widget在滑動時就會分發ScrollNotification,而Scrollbar正是經過監聽ScrollNotification來肯定滾動條位置的。編輯器

除了ScrollNotification,Flutter中還有KeepAliveNotification、SizeChangedLayoutNotification、LayoutChangedNotification等不少子類。ide

那麼Notification爲何能夠實現跨Widget的數據管理呢,首先,經過Notification機制有個使用條件,那就是父子關係,前面說了,父節點能夠經過NotificationListener來監聽子節點的Notification信息。因此藉助Notification,能夠很方便的從下至上傳遞數據的改變信息。函數

下面就經過一個系統的例子來演示下如何經過ScrollNotification,從滾動Widget拿到滾動數據。學習

class NotificationListenerWidget extends StatefulWidget {
  @override
  _NotificationListenerWidgetState createState() => _NotificationListenerWidgetState();
}

class _NotificationListenerWidgetState extends State<NotificationListenerWidget> {
  final StreamController<String> _controller = StreamController();
  var state = '';

  @override
  void dispose() {
    _controller.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        MainTitleWidget('NotificationListener基本使用'),
        Expanded(
          child: StreamBuilder(
            initialData: '',
            stream: _controller.stream,
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              return Text(snapshot.data);
            },
          ),
        ),
        Expanded(
          child: NotificationListener<ScrollNotification>(
            child: ListView.builder(
              itemBuilder: (BuildContext context, int position) {
                return ListTile(
                  title: Text('ListTile:$position'),
                );
              },
              itemCount: 30,
            ),
            onNotification: (notification) {
              switch (notification.runtimeType) {
                case ScrollStartNotification:
                  state = '開始滾動';
                  break;
                case ScrollUpdateNotification:
                  state = '正在滾動';
                  break;
                case ScrollEndNotification:
                  state = '滾動中止';
                  break;
                case OverscrollNotification:
                  state = '滾動到邊界';
                  break;
              }
              _controller.add('depth:${notification.depth}\n'
                  'state:$state\n'
                  'metrics\n'
                  '-axisDirection:${notification.metrics.axisDirection}\n'
                  '-axis:${notification.metrics.axis}\n'
                  '-extentAfter:${notification.metrics.extentAfter}\n'
                  '-extentBefore:${notification.metrics.extentBefore}\n'
                  '-extentInside:${notification.metrics.extentInside}\n'
                  '-minScrollExtent:${notification.metrics.minScrollExtent}\n'
                  '-maxScrollExtent:${notification.metrics.maxScrollExtent}\n'
                  '-atEdge:${notification.metrics.atEdge}\n'
                  '-outOfRange:${notification.metrics.outOfRange}\n'
                  '-pixels:${notification.metrics.pixels}\n'
                  '-viewportDimension:${notification.metrics.viewportDimension}\n');
              return false;
            },
          ),
        ),
      ],
    );
  }
}

要獲取滾動Widget的滾動數據,實際上就是在其父節點,經過NotificationListener來拿到其Notification改變的通知,NotificationListener能夠指定接收Notification的具體類型,也能夠在其內部經過runtimeType來進行判斷,代碼以下。flex

switch (notification.runtimeType){
  case ScrollStartNotification: print("開始滾動"); break;
  case ScrollUpdateNotification: print("正在滾動"); break;
  case ScrollEndNotification: print("滾動中止"); break;
  case OverscrollNotification: print("滾動到邊界"); break;
}

不一樣的通知類型,實際上封裝了不一樣的狀態數據,

代碼地址:Flutter Dojo-Widget-Scrolling-NotificationListener

Notification的可取消性

因爲Notification是沿着父節點向上查找,因此Notification在傳遞到每一個父節點的時候,父節點均可以針對該Notification是否能夠繼續向上傳遞作出控制,源碼以下所示。

因此,NotificationListener的onNotification回調是一個帶bool返回值的函數,當返回false的時候,該Notification能夠繼續向上傳遞,不然則被該父節點攔截。

自定義Notification

Flutter封裝了不少Notification,一樣也支持自定義Notification,這也是使用Notification來進行數據管理的核心原理。

自定義Notification很是簡單,只須要繼承Notification便可,代碼以下所示。

class MyNotification extends Notification {
  MyNotification(this.msg);

  final String msg;
}

接下來,就是本身實現通知的分發。

class NotificationListenerWidget extends StatefulWidget {
  @override
  _NotificationListenerWidgetState createState() => _NotificationListenerWidgetState();
}

class _NotificationListenerWidgetState extends State<NotificationListenerWidget> {
  String msg = '';
  String msgParent = '';
  bool returnValue = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        MainTitleWidget('自定義Notification'),
        SubtitleWidget('onNotification返回值控制通知的傳遞'),
        MultiSelectionWidget('onNotification返回值', [truefalse], [truefalse], (value) {
          setState(() => returnValue = value);
        }),
        NotificationListener<MyNotification>(
          onNotification: (notification) {
            setState(() => msgParent = notification.msg);
            return true;
          },
          child: NotificationListener<MyNotification>(
            onNotification: (notification) {
              setState(() => msg = notification.msg);
              return returnValue;
            },
            child: Column(
              children: [
                Text('自定義Msg: $msg, 父Widget是否收到消息: ${msgParent.isEmpty ? false : true}'),
                Builder(
                  builder: (BuildContext context) {
                    return RaisedButton(
                      onPressed: () {
                        msg = '';
                        msgParent = '';
                        MyNotification('MyNotification').dispatch(context);
                      },
                      child: Text('Send'),
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

代碼地址:Flutter Dojo-Widget-Scrolling-NotificationListener

在使用自定義Notification的時候,須要注意幾個地方。

  • 繼承Notification後,直接使用dispatch函數便可實現Notification的分發。
  • NotificationListener監聽的是子節點,因此dispatch函數傳入的context必須是子節點的Context,因此這裏須要使用Builder來建立子節點的Context(建立新的Widget一樣能夠實現這個功能)。
  • 要監聽的Notification必定要是NotificationListener的child,緣由前面已經說了。

修仙

Flutter Dojo開源至今,受到了不少Flutter學習者和愛好者的喜好,也有愈來愈多的人加入到Flutter的學習中來,因此我建了個Flutter修仙羣,可是人數太多,因此分紅了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指東】三個羣,對Flutter感興趣的朋友,能夠留言。

項目地址:

https://github.com/xuyisheng/flutter_dojo


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

相關文章
相關標籤/搜索