Flutter

Flutter小知识--划重点之InheritedWidget

2019-07-15  本文已影响6人  RidingWind2023
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)]

上一篇下一篇

猜你喜欢

热点阅读