28

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

 3 years ago
source link: https://blog.csdn.net/x359981514/article/details/108289406
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中同Page下跨Widget的数据管理。

第一种方案,我们使用ValueNotifier和ValueListenableBuilder来实现了。

这次,再介绍Flutter中的另一种数据管理方式——Notification。严格来说,Notification并不是一个跨Widget数据管理方案,它只完成了一半的功能,即Notification实现了数据状态修改的通知,但是需要监听的Widget收到通知后的处理,还是需要自己来实现的,这个实现,简单的说,可以是setState来重建UI,复杂了说,可以是配合其它任何一种数据管理/刷新的方案。

Notification

Notification是Flutter中数据传递的一种机制。在Flutter的Widget树上,每个节点都可以发出Notification,Notification会沿着当前节点向上传递,所有的父节点都可以通过NotificationListener来监听Notification的改动。

Flutter中将这种由子节点向父节点传递Notification的机制称为通知冒泡(Notification Bubbling)。

Flutter中的很多地方使用了Notification,如Scrollable Widget在滑动时就会分发ScrollNotification,而Scrollbar正是通过监听ScrollNotification来确定滚动条位置的。

除了ScrollNotification,Flutter中还有KeepAliveNotification、SizeChangedLayoutNotification、LayoutChangedNotification等很多子类。

那么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来进行判断,代码如下。

switch (notification.runtimeType){
  case ScrollStartNotification: print("开始滚动"); break;
  case ScrollUpdateNotification: print("正在滚动"); break;
  case ScrollEndNotification: print("滚动停止"); break;
  case OverscrollNotification: print("滚动到边界"); break;
}

不同的通知类型,实际上封装了不同的状态数据,

EjiiEjq.jpg!mobile

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

Notification的可取消性

由于Notification是沿着父节点向上查找,所以Notification在传递到每个父节点的时候,父节点都可以针对该Notification是否可以继续向上传递做出控制,源码如下所示。

bEnAZnr.jpg!mobile

所以,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返回值', [true, false], [true, false], (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,原因前面已经说了。

jM3Qr2e.jpg!mobile

Flutter Dojo开源至今,受到了很多Flutter学习者和爱好者的喜爱,也有越来越多的人加入到Flutter的学习中来,所以我建了个Flutter修仙群,但是人数太多,所以分成了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指东】三个群,对Flutter感兴趣的朋友,可以留言。

IvAFfuZ.jpg!mobile

项目地址:

https://github.com/xuyisheng/flutter_dojo


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK