美文网首页Flutter
Flutter小知识--划重点之InheritedWidget

Flutter小知识--划重点之InheritedWidget

作者: RidingWind2023 | 来源:发表于2019-07-15 23:00 被阅读6次
    InheritedWidget.png

    InheritedWidget 初识

    InheritedWidget是Flutter中非常重要的一个功能型Widget,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。

    InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在Widget树中数据传递方向是从上到下的,这和Notification的传递方向正好相反。

    可以高效的在widget tree中传递信息的widgets基类。

    可以使用BuildContext.inheritFromWidgetOfExactType获取指定类型的 inherited widget 的"最近"的实例。
    此处的"最近"的意思是从当前widget(调用BuildContext.inheritFromWidgetOfExactType的widget)向上溯源,找到第一个指定类型的inherited widget的位置。

    当使用这种方式引用Inherited widgets时,inherited widget 变化状态时 会通知引用者rebuild。

    样例代码:

    class FrogColor extends InheritedWidget {
      const FrogColor({
        Key key,
        @required this.color,
        @required Widget child,
      }) : assert(color != null),
           assert(child != null),
           super(key: key, child: child);
      final Color color;
      static FrogColor of(BuildContext context) {
        return context.inheritFromWidgetOfExactType(FrogColor) as FrogColor;
      }
      @override
      bool updateShouldNotify(FrogColor old) => color != old.color;
    }
    

    为InheritedWidget提供了静态方法 of,用以调用BuildContext.inheritFromWidgetOfExactType
    这允许类去定义自己的回溯逻辑 如果在scope内为发现widget。在上述的例子中,这种情况下会返回null,但是也可以定义一个默认值。
    某些时候,of方法返回的是数据而不是 inherited widget,比如 在这个例子中可以返回一个Color来替代FrogColor widget。
    偶尔,inherited widget也会是另一个类的具体实现,是private的, 这种情况下of方法需要放在public class上。比如, Theme就是实现了StatelessWidget来构建
    一个私有的inherited widget。Theme.of使用BuildContext.inheritFromWidgetOfExactType来查找inherited widget 并返回 ThemeData

    class Theme extends StatelessWidget {
      ...
      static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
        final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme);
        if (shadowThemeOnly) {
          if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
            return null;
          return inheritedTheme.theme.data;
        }
        ...
      }
    }
    
    class _InheritedTheme extends InheritedWidget {
      ...
    }
    
    

    didChangeDependencies

    继承StatefulWidget时State对象有一个回调didChangeDependencies,它会在“依赖”发生变化时被Flutter Framework调用。
    而这个“依赖”指的就是是否使用了父widget中InheritedWidget的数据,如果使用了,则代表有依赖,如果没有使用则代表没有依赖。
    这种机制可以使子组件在所依赖的主题、locale等发生变化时有机会来做一些事情。

    接下来我们看一下InheritedWidget版本的计数器。

    首先,我们通过继承InheritedWidget,将当前计数器点击次数保存在ShareDataWidget的data属性中:

    class ShareDataWidget extends InheritedWidget {
      ShareDataWidget({
        @required this.data,
        Widget child
      }) :super(child: child);
    
      final int data; //需要在子树中共享的数据,保存点击次数
    
      //定义一个便捷方法,方便子树中的widget获取共享数据
      static ShareDataWidget of(BuildContext context) {
        return context.inheritFromWidgetOfExactType(ShareDataWidget);
      }
    
      //该回调决定当data发生变化时,是否通知子树中依赖data的Widget
      @override
      bool updateShouldNotify(ShareDataWidget old) {
        //如果返回true,则子树中依赖(build函数中有调用)本widget
        //的子widget的`state.didChangeDependencies`会被调用
        return old.data != data;
      }
    }
    

    然后我们实现一个子widget _TestWidget,在其build方法中引用ShareDataWidget中的数据;同时,在其didChangeDependencies() 回调中打印日志:

    class __TestWidgetState extends State<_TestWidget> {
      @override
      Widget build(BuildContext context) {
        //使用InheritedWidget中的共享数据
        return Text(ShareDataWidget
            .of(context)
            .data
            .toString());
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
        //如果build中没有依赖InheritedWidget,则此回调不会被调用。
        print("Dependencies change");
      }
    }
    

    最后,我们创建一个按钮,每点击一次,就将ShareDataWidget的值自增:

    class _InheritedWidgetTestState extends State<InheritedWidgetTest> {
      int count = 0;
    
      @override
      Widget build(BuildContext context) {
        return  Center(
          child: ShareDataWidget( //使用ShareDataWidget
            data: count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(bottom: 20.0),
                  child: _TestWidget(),//子widget中依赖ShareDataWidget
                ),
                RaisedButton(
                  child: Text("Increment"),
                  //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
                  onPressed: () => setState(() => ++count),
                )
              ],
            ),
          ),
        );
      }
    }
    

    运行后界面如下:
    [图片上传失败...(image-689242-1563202790002)]

    每点击一次按钮,计数器就会自增,控制台就会打印一句日志:

    I/flutter (16850): Dependencies change
    

    可见依赖发生变化后,其didChangeDependencies()会被调用。
    但是,如果_TestWidget的build方法中没有使用ShareDataWidget的数据,那么它的didChangeDependencies()将不会被调用,因为它并没有依赖ShareDataWidget。

    Flutter framework是怎么知道子widget有没有依赖InheritedWidget的?

    下一篇文章会从源码的角度分析并解释这个问题,敬请期待。


    如果你觉得这篇文章对你有益,还请帮忙转发和点赞,万分感谢。
    [图片上传失败...(image-630a2a-1563202790002)]

    相关文章

      网友评论

        本文标题:Flutter小知识--划重点之InheritedWidget

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