美文网首页Android开发经验谈Android开发Android补给站
从零开始的Flutter之旅: InheritedWidget

从零开始的Flutter之旅: InheritedWidget

作者: 午后一小憩 | 来源:发表于2020-06-09 19:01 被阅读0次

    往期回顾

    从零开始的Flutter之旅: StatelessWidget

    从零开始的Flutter之旅: StatefulWidget

    在之前的文章中,介绍了StatelessWidget与StatefulWidget的特性与它们的呈现原理。

    这期要聊的是它们的另一个兄弟InheritedWidget。

    特性

    InheritedWidget是Flutter中的一个非常重要的功能组件,它能够提供数据在widget树中从上到下进行传递。保证数据在不同子widget中进行共享。这对于一些需要使用共享数据的场景非常有效,例如,在Flutter SDK中就是通过InheritedWidget来共享应用的主题与语言信息。

    可能你还有点模糊,别急,下面我们通过一个简单的示例来了解InheritedWidget。

    示例

    相信开始学Flutter时都看过官方的计数器示例。我们将官方提供的计数器示例使用InheritedWidget进行改造。

    首先我们需要一个CountInheritedWidget,它继承于InheritedWidget。

    class CountInheritedWidget extends InheritedWidget {
      CountInheritedWidget({@required this.count, Widget child})
          : super(child: child);
     
      // 共享数据,计数的数量
      final int count;
     
      // 统一的获取CountInheritedWidget实例, 方便树中子widget的获取共享数据
      // 必须在State中调用才会有效
      static CountInheritedWidget of(BuildContext context) {
        // 调用共享数据的子widget将不会回调didChangeDependencies方法,即子widget将不会更新
        // return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
        return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
      }
     
      // true -> 通知树中依赖改共享数据的子widget
      @override
      bool updateShouldNotify(CountInheritedWidget oldWidget) {
        return oldWidget.count != count;
      }
    }
    
    1. 在CountInheritedWidget中提供共享计数的数量count
    2. 同时为外部提供统一的获取CountInheritedWidget实例的of方法
    3. 最后再重写updateShouldNotify方法,来通知依赖该共享count的子widget进行更新

    现在已经有了共享数据count的提供,接下来是在具体的子widget中进行使用。

    我们抽离出一个CountText子widget

    class CountText extends StatefulWidget {
      @override
      _CountTextState createState() {
        return _CountTextState();
      }
    }
     
    class _CountTextState extends State<CountText> {
      @override
      Widget build(BuildContext context) {
        return Text("count: ${CountInheritedWidget.of(context).count.toString()}");
      }
     
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print("didChangeDependencies");
      }
    }
    
    1. 内部引用了CountInheritedWidget中的共享数据count,通过of方法获取CountInheritedWidget实例
    2. didChangeDependencies可以用来监听子widget依赖是否反生改变

    最后,我们再将CountInheritedWidget与CountText结合起来,通过简单的点击自增事件来看下效果

    class CountWidget extends StatefulWidget {
      @override
      _CountState createState() {
        return _CountState();
      }
    }
     
    class _CountState extends State<CountWidget> {
      int count = 0;
     
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Count App',
          theme: new ThemeData(primarySwatch: Colors.blue),
          home: Scaffold(
            appBar: AppBar(
              title: Text("Count"),
            ),
            body: Center(
              child: CountInheritedWidget(
                count: count,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    CountText(),
                    RaisedButton(
                      onPressed: () => setState(() => count++),
                      child: Text("Increment"),
                    )
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    

    上面的层级关系是CountText刚好是CountInheritedWidget的子widget。

    现在我们通过点击事件直接改变外部的count值,如果InheritedWidget从上到下数据传到的效果能够生效,那么在CountText中引用的count将会与外部count同步,程序呈现的效果将会是自增的,同时由于依赖的count发生改变CountText中的didChangeDependencies也会回调。

    我们直接运行一下

    点击后的输出日志

    I/flutter: didChangeDependencies
    

    说明InheritedWidget的效果已经生效,通过InheritedWidget的使用,我们可以很方便的在嵌套下层的子widget中拿到上层的数据,或者说整个widget的共享数据。

    分析

    在依赖方式改变时子widget的didChangeDependencies会回调,但由于你可能会在该方法中做一些特殊的操作,例如网络请求。只是需要一次就可以。如果是套用我们上面的示例,将会在count子增时反复调用。

    为了防止didChangeDependencies的调用,我们再来看CountInheritedWidget的of方法中注释的那部分

      static CountInheritedWidget of(BuildContext context) {
        // 调用共享数据的子widget将不会回调didChangeDependencies方法,即子widget将不会更新
        // return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
        return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
      }
    

    我们使用的是dependOnInheritedWidgetOfExactType方法,依赖的共享数据发生改变时会回调子widget中的didChangeDependencies方法,如果我们不想要子widget调用该方法,可以使用注释的代码,通过getElementForInheritedWidgetOfExactType方法来获取共享数据。

    如果此时我们再运行一下项目,点击count自增,控制台将不会再输出日志。这样就可以解决didChangeDependencies的反复调用。

    而这两个方法的主要区别是在dependOnInheritedWidgetOfExactType调用的过程中会进行注册依赖关系

      @override
      InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
        assert(ancestor != null);
        _dependencies ??= HashSet<InheritedElement>();
        _dependencies.add(ancestor);
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
      }
    

    所以dependOnInheritedWidgetOfExactType更新依赖的子widget中的didChangeDependencies方法。

    思考下一个问题,虽然现在didChangeDependencies方法不会调用,但是CountText的build方法还是会执行。原因是在CountWidget中通过setState来改变count值,会重新build所用的子widget。但我们真正想要的只是更新子widget中依赖的CountInheritedWidget的组件值。

    那么如何解决呢?这里提供一个解决方案是为子widget提供缓存。可以通过封装一个简单的StatefulWidget,将子widget缓存起来。如果对这块感兴趣的,可以期待我的后续文章。

    推荐项目

    下面介绍一个完整的Flutter项目,对于新手来说是个不错的入门。

    flutter_github,这是一个基于Flutter的Github客户端同时支持Android与IOS,支持账户密码与认证登陆。使用dart语言进行开发,项目架构是基于Model/State/ViewModel的MSVM;使用Navigator进行页面的跳转;网络框架使用了dio。项目正在持续更新中,感兴趣的可以关注一下。

    当然如果你想了解Android原生,相信flutter_github的纯Android版本AwesomeGithub是一个不错的选择。

    如果你喜欢我的文章模式,或者对我接下来的文章感兴趣,建议您关注我的微信公众号:【Android补给站】

    或者扫描下方二维码,与我建立有效的沟通,同时更快更准的收到我的更新推送。

    相关文章

      网友评论

        本文标题:从零开始的Flutter之旅: InheritedWidget

        本文链接:https://www.haomeiwen.com/subject/sfuetktx.html