数据传递/状态管理 一InheritedWidget

数据传递/状态管理 一InheritedWidget

InheritedWidget是一个特殊的widget,可以存储和获取数据,子组件可以获取到存储的数据,常用的 MediaQueryTheme 就是继承了 InheritedWidget。先通过MediaQuery来学习InheritedWidget。

  • MediaQuery
class MediaQuery extends InheritedWidget {
    const MediaQuery({
      Key key,
      @required this.data,
      @required Widget child,
    }) : assert(child != null),
         assert(data != null),
         super(key: key, child: child);
  
    ///...
    final MediaQueryData data;
    ///...
  
    static MediaQueryData of(BuildContext context, { bool nullOk = false }) {
    ///...
  }
}

通过 MediaQuery.of(context) 获取到MediaQueryData对象,然后获取到设备屏幕相关的信息。

///获取屏幕宽度
MediaQuery.of(context).size.width;

代码查找,下面这些地方创建了MediaQuery

WX20190615-113309@2x

在app.dart里面可以发现 WidgetsApp 如下代码,build 方法返回一个DefaultFocusTraversal组件,而DefaultFocusTraversal 组件的 child 就是 MediaQuery

@override
Widget build(BuildContext context) {
    ///...
  return DefaultFocusTraversal(
    policy: ReadingOrderTraversalPolicy(),
    child: MediaQuery(
      data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
      child: Localizations(
        locale: appLocale,
        delegates: _localizationsDelegates.toList(),
        child: title,
      ),
    ),
  );
}

继续通过代码查找在 MaterialApp 的源码里面找到了 WidgetsApp ,在build方法里面返回了ScrollConfiguration类,ScrollConfigurationchild就是WidgetsApp

@override
Widget build(BuildContext context) {
  Widget result = WidgetsApp(
    key: GlobalObjectKey(this),
    navigatorKey: widget.navigatorKey,
    navigatorObservers: _navigatorObservers,
      pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
          MaterialPageRoute(settings: settings, builder: builder),
    home: widget.home,
  ///...
  );
   
  return ScrollConfiguration(
    behavior: _MaterialScrollBehavior(),
    child: result,
  );
}

通过这些代码,可以推断出 MaterialApphome是属于MediaQuery的子组件,所有home下面所有组件都能够通过 MediaQuery.of(context) 来获取其共享的数据。但是注意并不是任何组件的任何地方,只有当前组件执行完 didChangeDependencies 方法之后才能正确的获取到 InheritedWidget 对象的数据。

  • 示例

    完成下面的示例,页面进来加载用户信息,保存和展示,然后在另外的页面修改编辑后返回,展示的数据也改变了:

Jun-15-2019 13-16-35

完成上面的示例首先创建一个数据实体类,

///共享的数据
class InheritedWidgetData {
  String userName;
  int userAge;

  InheritedWidgetData({this.userName = "", this.userAge = 0});

  void setUser(String userName, int userAge) {
    this.userName = userName;
    this.userAge = userAge;
  }
}

InheritedWidgetDemo 继承 InheritedWidgetdata 存放着用户的数据。提供一个of的静态方法供子组件调用,复写updateShouldNotify 方法判断是否需要更新。

///主Widget 继承InheritedWidget 存放数据
class InheritedWidgetDemo extends InheritedWidget {
  final InheritedWidgetData data;

  InheritedWidgetDemo({@required this.data, @required Widget child})
      : super(child: child);

  @override
  bool updateShouldNotify(InheritedWidgetDemo oldWidget) {
    return data != oldWidget.data;
  }

  static InheritedWidgetData of(BuildContext context) {
    final InheritedWidgetDemo user =
        context.inheritFromWidgetOfExactType(InheritedWidgetDemo);
    return user.data;
  }
}

数据展示Widget,在 didChangeDependencies 方法里面获取用户数据。页面初始化开启模拟网络请求等待3秒后,调用 setUser 方法修改 InheritedWidgetDemo 中的数据,然后重建界面。当用户信息为空的话页面显示加载等待组件。

class CenterWidget extends StatefulWidget {
  @override
  State createState() => _CenterWidgetState();
}

class _CenterWidgetState extends State {
  InheritedWidgetData _userData;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _userData = InheritedWidgetDemo.of(context);
     _getUserData();
  }

  _getUserData() async {
    ///模拟网络请求 等待五秒
    await Future.delayed(Duration(seconds: 3));
    InheritedWidgetDemo.of(context).setUser("李华", 18);
    ///setState 触发页面刷新
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      /// 数据为空显示加载圆圈
      child: _userData.userName == ""
          ? CircularProgressIndicator()
          : new UserInfoWidget(),
    );
  }
}

用户信息展示Widget,和上面的Widget一样,在 didChangeDependencies 方法里面获取用户数据并且展示。

///用户信息Widget
class UserInfoWidget extends StatefulWidget {
  @override
  State createState() => _UserInfoState();
}

class _UserInfoState extends State {
  InheritedWidgetData _userData;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _userData = InheritedWidgetDemo.of(context);
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.max,
        children: [
          new Text(_userData.userName),
          new Text(_userData.userAge.toString())
        ],
      ),
    );
  }
}

用户资料修改界面,和上面一样在 didChangeDependencies 方法里面拿到数据,点击保存按钮的时候 InheritedWidgetDemo.of(context).setUser(_userName, userAge) 修改数据,当返回展示界面的时候,界面会拿到新数据重建。

class EditUserPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("修改数据"),
      ),
      body: new EditUserWidget(),
    );
  }
}

class EditUserWidget extends StatefulWidget {
  @override
  State createState() => _EditUserState();
}

class _EditUserState extends State {
  InheritedWidgetData _userData;
  String _userName;
  String _userAge;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _userData = InheritedWidgetDemo.of(context);
    _userName = _userData.userName;
    if (_userData.userAge > 0) {
      _userAge = _userData.userAge.toString();
    } else {
      _userAge = "";
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Column(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          new TextField(
            ///文本内容控制器
            controller: TextEditingController.fromValue(new TextEditingValue(
              text: _userName,
              ///光标移动到最后面
              selection: TextSelection.fromPosition(new TextPosition(
                  affinity: TextAffinity.downstream, offset: _userName.length)),
            )),

            decoration: new InputDecoration(labelText: "输入姓名"),

            ///内容变化监听
            onChanged: (value) {
              _userName = value;
            },
          ),
          new TextField(
            ///文本内容控制器
            controller: TextEditingController.fromValue(new TextEditingValue(
              text: _userAge,
              ///光标移动到最后面
              selection: TextSelection.fromPosition(new TextPosition(
                  affinity: TextAffinity.downstream, offset: _userAge.length)),
            )),

            decoration: new InputDecoration(labelText: "输入年龄"),

            ///内容变化监听
            onChanged: (value) {
              _userAge = value;
            },
          ),
          new FlatButton(
              onPressed: () {
                int userAge;
                try {
                  ///保存当前修改的值
                  userAge = int.parse(_userAge);
                  InheritedWidgetDemo.of(context).setUser(_userName, userAge);
                  ///关闭当前界面
                  Navigator.pop(context);
                } catch (e) {}
              },
              child: new Text("保存"))
        ],
      ),
    );
  }
}

由于用户数据需要在全局共享,所以 InheritedWidgetDemo 要在顶层入口MaterialApp之上,当然在日常开发并不是所有的数据都是全局保存的,每个子页面都会管理自己所属的数据,只需要把 InheritedWidget 放在子页面的最外层。

class AppPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new InheritedWidgetDemo(
        data: new InheritedWidgetData(),
        child: new MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text("数据传递"),
            ),
            floatingActionButton: new Builder(builder: (context) {
              return new FloatingActionButton(
                  child: new Icon(Icons.edit),
                  onPressed: () {
                    ///push到编辑页面
                    Navigator.of(context).push(
                        new MaterialPageRoute(builder: (BuildContext context) {
                      return EditUserPageWidget();
                    }));
                  });
            }),
            body: new CenterWidget(),
          ),
        ));
  }
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  ///使用
    return AppPage();
  }
}
  • 关于 didChangeDependencies 的回调,在生命周期提到过,解释是在 State 对象的依赖发生变化时会被调用,结合 InheritedWidget 看下面这个demo。

    Jun-15-2019 14-44-43

    两个Text,一个依赖了 InheritedWidget,一个没有。点击按钮修改 InheritedWidget 的值,上面的text会根据值的变化刷新重建,并回调了 didChangeDependencies ,下面的text由于没有数据依赖,所以只有初始化的时候回调了一次 didChangeDependencies

    class InheritedRely extends StatefulWidget {
      @override
      State createState() {
        return new _InheritedRelyState();
      }
    }
    
    class _InheritedRelyState extends State {
      int _state = 0;
    
      @override
      Widget build(BuildContext context) {
        return new InheritedTestWidget(
            data: _state,
            child: new MaterialApp(
              title: "Inherited数据依赖",
              home: new Scaffold(
                body: new Center(
                  child: new Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      new TextWidget(),
                      new Text1Widget()
                    ],
                  ),
                ),
                floatingActionButton: new FloatingActionButton(
                  onPressed: () {
                    setState(() {
                      ///修改state
                      _state++;
                    });
                  },
                  child: new Icon(Icons.add),
                ),
              ),
            ));
      }
    }
    
    class Text1Widget extends StatefulWidget {
    
      @override
      State createState() {
        return new _Text1WidgetState();
      }
    }
    
    ///这个text没有使用InheritedWidget的数据
    class _Text1WidgetState extends State {
      @override
      Widget build(BuildContext context) {
        return Text("这个text没有使用InheritedWidget的数据");
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print("_Text1------>>>>>>didChangeDependencies");
      }
    }
    
    
    
    ///这个text使用了InheritedWidget的数据
    class TextWidget extends StatefulWidget {
      @override
      State createState() {
        return _TextWidgetState();
      }
    }
    
    class _TextWidgetState extends State {
      @override
      Widget build(BuildContext context) {
        ///InheritedTestWidget.of(context) 依赖了InheritedWidget的数据
        return Text("这个text使用了InheritedWidget的数据" +
            InheritedTestWidget.of(context).toString());
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print("_Text------>>>>>>didChangeDependencies");
      }
    }
    
    class InheritedTestWidget extends InheritedWidget {
      final int data;
    
      InheritedTestWidget({@required this.data, @required Widget child})
          : super(child: child);
    
      @override
      bool updateShouldNotify(InheritedTestWidget oldWidget) {
        return data != oldWidget.data;
      }
    
      static int of(BuildContext context) {
        final InheritedTestWidget user =
            context.inheritFromWidgetOfExactType(InheritedTestWidget);
        return user.data;
      }
    }
    
    

    代码和演示清楚的解释了,当 InheritedWidgetState 对象发生变化时,它下面子组件只要是对 InheritedWidget 有依赖的都会调 didChangeDependenciesbuild 进行刷新重建。

  • scoped_model

    上面的例子展示了如何使用 InheritedWidget ,有个问题就是在数据变化之后每次都需要手动调动 setState 方法才会进行重建。如何让组件自动刷新不需要我们自己手动调用 setState 方法呢。

    这需要使用到 scoped_model 库 , scoped_model 是基于 InheritedWidget 实现的,源码稍后分析,先通过刚刚用户资料编辑的demo来学习如何使用这个库,展示效果和上面的demo一样。

    因为是第三方库第一步肯定是添加依赖 scoped_model: ^1.0.1 。文档在这里(需要梯子)。

    用户model,继承 Model 提供一个 of 方法供组件调用获取model,注意 rebuildOnChange参数是可选的,默认为fales,为false的时候获取的model数据变化之后不会刷新界面

    class UserMode extends Model {
      String userName;
      int userAge;
    
      UserMode({this.userName, this.userAge});
    
      void setUserInfo(String userName, int userAge) {
        this.userName = userName;
        this.userAge = userAge;
        ///修改完数据 调用刷新方法
        notifyListeners();
      }
    
      ///rebuildOnChange 此方法拿到的model对象修改后是否需要刷新
      static UserMode of(BuildContext context) =>
          ScopedModel.of(context, rebuildOnChange: true);
    
    }
    

    展示组件,逻辑基本和 InheritedWidget 的一样,初始化的时候模拟请求,延迟3秒修改数据,当没有数据的时候加载等待小圆圈,不同的是获取数据的方法,这里变成通过model的 of 方法。

    class CenterWidget extends StatefulWidget {
      @override
      State createState() => _CenterWidgetState();
    }
    
    class _CenterWidgetState extends State {
      @override
      void initState() {
        super.initState();
        _getUserData();
      }
    
      _getUserData() async {
        ///模拟网络请求 等待五秒
        await Future.delayed(Duration(seconds: 3));
        UserMode.of(context).setUserInfo("李华", 22);
      }
    
      @override
      Widget build(BuildContext context) {
        return new Center(
            /// 数据为空显示加载圆圈
            child: UserMode.of(context).userName == null
                ? CircularProgressIndicator()
                : new UserInfoWidget());
      }
    }
    

    除了 ScopedModel.of 方法还可以通过 ScopedModelDescendant 来获取,在需要获取数据的组件外加一层ScopedModelDescendant ,用户信息使用此方法。

    
    ///用户信息Widget
    class UserInfoWidget extends StatefulWidget {
      @override
      State createState() => _UserInfoState();
    }
    
    class _UserInfoState extends State {
      @override
      Widget build(BuildContext context) {
        return new ScopedModelDescendant(builder: (context, child, model) {
          return new Center(
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: [
                ///可以直接通过mode拿到数据
                new Text(model.userName),
                new Text(model.userAge.toString())
              ],
            ),
          );
        });
      }
    }
    

    信息编辑页面逻辑也一样,获取和修改数据的方式不一样。

    class EditUserPageWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text("修改数据"),
          ),
          body: new EditUserWidget(),
        );
      }
    }
    
    class EditUserWidget extends StatefulWidget {
      @override
      State createState() => _EditUserState();
    }
    
    class _EditUserState extends State {
      UserMode _userMode;
    
      String _userName;
      String _userAge;
    
      @override
      Widget build(BuildContext context) {
        _userMode = UserMode.of(context);
        _userName = _userMode.userName;
        _userAge = _userMode.userAge.toString();
    
        return new Center(
          child: new Column(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              new TextField(
                ///文本内容控制器
                controller: TextEditingController.fromValue(new TextEditingValue(
                  text: _userName,
                  ///光标移动到最后面
                  selection: TextSelection.fromPosition(new TextPosition(
                      affinity: TextAffinity.downstream, offset: _userName.length)),
                )),
    
                decoration: new InputDecoration(labelText: "输入姓名"),
    
                ///内容变化监听
                onChanged: (value) {
                  _userName = value;
                },
              ),
              new TextField(
                ///文本内容控制器
                controller: TextEditingController.fromValue(new TextEditingValue(
                  text: _userAge,
                  selection: TextSelection.fromPosition(new TextPosition(
                      affinity: TextAffinity.downstream, offset: _userAge.length)),
                )),
    
                decoration: new InputDecoration(labelText: "输入年龄"),
    
                ///内容变化监听
                onChanged: (value) {
                  _userAge = value;
                },
              ),
              new FlatButton(
                  onPressed: () {
                    int userAge;
                    try {
                      ///保存当前修改的值
                      userAge = int.parse(_userAge);
                      _userMode.setUserInfo(_userName, userAge);
    
                      ///关闭当前界面
                      Navigator.pop(context);
                    } catch (e) {}
                  },
                  child: new Text("保存"))
            ],
          ),
        );
      }
    }
    
    

    主Widget,和 InheritedWidget 一样需要在最外层,使用 ScopedModel

    class ScopedModelDemo extends StatefulWidget {
      @override
      State createState() {
        return new _ScopedModelDemoState();
      }
    }
    
    class _ScopedModelDemoState extends State {
    
      @override
      Widget build(BuildContext context) {
        return new ScopedModel(
            model: UserMode(),
            child: new MaterialApp(
              theme: ThemeData(
                primarySwatch: Colors.blue,
              ),
              home: new Scaffold(
                appBar: new AppBar(
                  title: new Text("ScopedModelDemo"),
                ),
                floatingActionButton: new Builder(builder: (context) {
                  return new FloatingActionButton(
                      child: new Icon(Icons.edit),
                      onPressed: () {
                        ///push到编辑页面
                        Navigator.of(context).push(
                            new MaterialPageRoute(builder: (BuildContext context) {
                          return EditUserPageWidget();
                        }));
                      });
                }),
                body: new CenterWidget(),
              ),
            ));
      }
    }
    

    效果和上面一样,不上图了。代码中没有任何地方使用到 setState 数据更新之后,界面自动刷新重建。上面说到了这个库也是基于 InheritedWidget ,具体如何实现的,通过源码来分析。

    首先看下Model的源码,

    abstract class Model extends Listenable {
      final Set _listeners = Set();
      int _version = 0;
      int _microtaskVersion = 0;
    
      /// [listener] will be invoked when the model changes.
      @override
      void addListener(VoidCallback listener) {
        _listeners.add(listener);
      }
    
      /// [listener] will no longer be invoked when the model changes.
      @override
      void removeListener(VoidCallback listener) {
        _listeners.remove(listener);
      }
    
      /// Returns the number of listeners listening to this model.
      int get listenerCount => _listeners.length;
    
      /// Should be called only by [Model] when the model has changed.
      @protected
      void notifyListeners() {
        // We schedule a microtask to debounce multiple changes that can occur
        // all at once.
        if (_microtaskVersion == _version) {
          _microtaskVersion++;
          scheduleMicrotask(() {
            _version++;
            _microtaskVersion = _version;
    
            // Convert the Set to a List before executing each listener. This
            // prevents errors that can arise if a listener removes itself during
            // invocation!
            _listeners.toList().forEach((VoidCallback listener) => listener());
          });
        }
      }
    }
    

    Model继承至Listenable,维护着一个VoidCallback类型的集合,

    /// Signature of callbacks that have no arguments and return no data.
    typedef VoidCallback = void Function();
    

    VoidCallback 就是一个没有返回值的方法,notifyListeners 方法里面遍历调用了所有集合里面的方法。

    再看入口 ScopedModel,

    class ScopedModel extends StatelessWidget {
      /// The [Model] to provide to [child] and its descendants.
      final T model;
    
      /// The [Widget] the [model] will be available to.
      final Widget child;
    
      ScopedModel({@required this.model, @required this.child})
          : assert(model != null),
            assert(child != null);
    
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: model,
          builder: (context, _) => _InheritedModel(model: model, child: child),
        );
      }
    
      static T of(
        BuildContext context, {
        bool rebuildOnChange = false,
      }) {
        final Type type = _type<_InheritedModel>();
    
        Widget widget = rebuildOnChange
            ? context.inheritFromWidgetOfExactType(type)
            : context.ancestorWidgetOfExactType(type);
    
        if (widget == null) {
          throw new ScopedModelError();
        } else {
          return (widget as _InheritedModel).model;
        }
      }
      
    }
    

    代码很简单of 方法为Model方法提供获取值的方法,而且是通过 inheritFromWidgetOfExactType 和上面 InheritedWidgetof差不多,这里也可以解释了上面Model里面为什么rebuildOnChange 为fals的时候不会更新界面的原因。

    然后查看 bulid方法返回的是一个 AnimatedBuilder ,两个参数,model和 _InheritedModel,查看 _InheritedModel,果然,继承了 InheritedWidget。并且存放了model和一个int类型的 version通过version来判断是否更新。

    class _InheritedModel extends InheritedWidget {
      final T model;
      final int version;
    
      _InheritedModel({Key key, Widget child, T model})
          : this.model = model,
            this.version = model._version,
            super(key: key, child: child);
    
      @override
      bool updateShouldNotify(_InheritedModel oldWidget) =>
          (oldWidget.version != version);
    }
    

    回到 AnimatedBuilder 查看它的代码

    class AnimatedBuilder extends AnimatedWidget {
      /// Creates an animated builder.
      ///
      /// The [animation] and [builder] arguments must not be null.
      const AnimatedBuilder({
        Key key,
        @required Listenable animation,
        @required this.builder,
        this.child,
      }) : assert(animation != null),
           assert(builder != null),
           super(key: key, listenable: animation);
    
      /// Called every time the animation changes value.
      final TransitionBuilder builder;
    
      final Widget child;
    
      @override
      Widget build(BuildContext context) {
        return builder(context, child);
      }
    }
    

    看不出有什么重要的代码,点击super往上走看AnimatedWidget的代码,

    abstract class AnimatedWidget extends StatefulWidget {
     ///...
    }
    
    class _AnimatedState extends State {
      @override
      void initState() {
        super.initState();
        widget.listenable.addListener(_handleChange);
      }
    
      @override
      void didUpdateWidget(AnimatedWidget oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (widget.listenable != oldWidget.listenable) {
          oldWidget.listenable.removeListener(_handleChange);
          widget.listenable.addListener(_handleChange);
        }
      }
    
      @override
      void dispose() {
        widget.listenable.removeListener(_handleChange);
        super.dispose();
      }
    
      void _handleChange() {
        setState(() {
          // The listenable's state is our build state, and it changed already.
        });
      }
    
      @override
      Widget build(BuildContext context) => widget.build(context);
    }
    

    看到这里就很清晰了,通过观察者模式,_InheritedModel 里的数据也就是Model是被观察者,在 _AnimatedStateinitStatedispose方法来 订阅和取消,Model发生变化时,去调用notifyListeners 来通知所有观察者执行 setState 刷新重构造界面。

    还有一个ScopedModelDescendant

    class ScopedModelDescendant extends StatelessWidget {
      /// Called whenever the [Model] changes.
      final ScopedModelDescendantBuilder builder;
    
      /// An optional constant child that does not depend on the model.  This will
      /// be passed as the child of [builder].
      final Widget child;
    
      /// An optional constant that determines whether the
      final bool rebuildOnChange;
    
      /// Constructor.
      ScopedModelDescendant({
        @required this.builder,
        this.child,
        this.rebuildOnChange = true,
      });
    
      @override
      Widget build(BuildContext context) {
        return builder(
          context,
          child,
          ScopedModel.of(context, rebuildOnChange: rebuildOnChange),
        );
      }
    }
    

本质也是ScopedModel.of 方法拿到数据的。

初学小白的个人学习笔记,欢迎大佬检查,如有不对请指出。

你可能感兴趣的:(数据传递/状态管理 一InheritedWidget)