理解InheritedWidget及应用

9,879 阅读4分钟

本篇文章主要介绍Flutter中的InheritedWidget小部件,以及其官方小部件库中的应用。

官方介绍

关于InheritedWidget,在官方文档中是这样描述的:

  • 1,可以沿着树向下传递信息。
  • 2,可以使用 BuildContext.dependOnInheritedWidgetOfExactType方法,获取最近的指定类型的inherited widget
  • 3,当inherited widget状态发生改变时,所有依赖其状态的子部件都会进行rebuild

我们该如何理解InheritedWidget呢,接下来,通过几个示例来阐述InheritedWidget

ThemeData

我们经常使用的ThemeData就是一个inheritedWidget,通常我们Approot MaterialApp,这样我们的ThemeDataWidget树的最顶端。

我们可以使用

final themeData = Theme.of(context);

来获取当前的主题,当主题改变时,会从Widget树的最顶端,沿着树,从上到下,依次刷新每个依赖themeData的子部件。

static ThemeData of(BuildContext context) {
  final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
  final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
  final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike;
  final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme;
  return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
}

static ThemeData of(BuildContext context)的实现我们可以看出其是一个InheritedWidget

示例

无状态InheritedWidget

对于无状态的InheritedWidget,我们以读取其共享的数据为例展开,接下来,我们新建Counter

class Counter extends InheritedWidget { // 1
    const Counter( {Key? key,  required this.child, required this.counter})
      : super(key: key, child: child); // 2

  final int counter;

  final Widget child;

  static Counter? of(BuildContext context) { // 3
    return context.dependOnInheritedWidgetOfExactType<Counter>();
  }

  @override
  bool updateShouldNotify(covariant Counter oldWidget) { // 4
    return oldWidget.counter != counter;
  }
}
  • 1,Counter继承InheritedWidget
  • 2,在构建时,需要提供counter值和子Widget
  • 3,向子部件提供获取状态的接口。
  • 4,是否通知子部件进行状态刷新。

我们可以这样使用该InheritedWidget

Widget buildInheritedWidget(BuildContext context) {
  return Counter(
      counter: 5,
      child: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              WidgetE(),
            ],
          ),
        ),
      ));

}

@override
Widget build(BuildContext context) {

  return buildInheritedWidget(context);
}

运行结果如下

Simulator Screen Shot - iPhone 13 - 2022-05-14 at 11.49.04.png

接下来,我们创建一个有状态的InheritedWidget

有状态的InheritedWidget

1,创建一个持有状态的InheritedWidget

class CounterWidget extends InheritedWidget {
  const CounterWidget(
      {Key? key,
        required this.counter,
        required this.child,
        required this.data})
      : super(child: child, key: key);

  final int counter;

  final Widget child;

  final CounterWrapperState data; // 1

  /// 获取 CounterWidget 实例
  static CounterWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterWidget>();
  }

  @override
  bool updateShouldNotify(covariant CounterWidget oldWidget) {
    // TODO: implement updateShouldNotify
    return oldWidget.counter != counter;
  }
}
  • 1,相比于无状态的,这里新增了一个CounterWrapperState

解下来,创建一个StatefulWidget,并将CounterWidget作为该部件的子部件。

class CounterWrapper extends StatefulWidget {
  final Widget child; // 1
  const CounterWrapper({Key? key, required this.child}) : super(key: key);

  static CounterWrapperState of(BuildContext context, {bool build = true}) { // 2
    return build
        ? (context.dependOnInheritedWidgetOfExactType<CounterWidget>())!.data
        : context.findAncestorWidgetOfExactType<CounterWidget>()!.data;
  }

  @override
  CounterWrapperState createState() => CounterWrapperState();
}

class CounterWrapperState extends State<CounterWrapper> {
  int counter = 0;
  void incrementCounter() { // 3
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) { // 4
    return CounterWidget(data: this, counter: counter, child: widget.child);
  }
}
  • 1,在构建时,需提供child,并传递给CounterWidget

  • 2,提供获取状态的接口,有两种方式 : 当使用dependOnInheritedWidgetOfExactType时,当状态改变时,会重新rebuild子部件。 使用findAncestorWidgetOfExactType时,当状态改变时,子部件不会刷新。

  • 3,计数器加1,刷新Widget。

  • 4,将CounterWidget作为CounterWrapper的子部件。

子Widget刷新、局部刷新、不刷新

接下来,创建5个不同的Widget:

/// 使计数状态改变
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  @override
  Widget build(BuildContext context) {
    print("A refresh");
    return ElevatedButton(onPressed: onPressed, child: Text("Increment"));
  }
  onPressed() {
    CounterWrapperState wrapper = CounterWrapper.of(context, build: false);
    wrapper.incrementCounter();
  }
}

/// 局部刷新
class WidgetB extends StatelessWidget {
  const WidgetB({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    print("widget B 整个 刷新");
    return Builder(builder: (contextTwo) {
      print("widget B Text 局部刷新");
      final CounterWrapperState state =
      CounterWrapper.of(contextTwo, build: true);
      return Text('${state.counter}');
    });
  }
}

/// inheritedWidget刷新时,也刷新
class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterWrapperState state =
    CounterWrapper.of(context, build: true);
    print("widget C 刷新");
    return new Text('I am Widget C ${state.counter}');
  }
}
/// inheritedWidget刷新时,不刷新
class WidgetC1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterWrapperState state =
    CounterWrapper.of(context, build: false);
    print("widget C1 刷新");
    return new Text('I am Widget C1 ${state.counter}');
  }
}
/// 不依赖inheriteWidget状态的子部件
class WidgetD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("widget D 刷新");
    return new Text('I am Widget D');
  }
}
  • 1,WidgetA: 使 CounterWrapper 的状态改变。
  • 2,WidgetB: 不刷新整个页面,对依赖部分进行局部刷新
  • 3,WidgetC: 依赖inheritedWidget的状态,状态改变时,也随之刷新。
  • 4,WidgetC1: 只在初始化时,获取inheritedWidget的状态,不随之刷新。
  • 5,WidgetD: inheritedWidget的子部件,但不依赖其状态

我们将这些Widget加载到视图中

Widget buildStatefulInheritedWidget(BuildContext context) {
  return CounterWrapper(
      child: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              WidgetB(),
              WidgetA(),
              WidgetC(),
              WidgetC1(),
              WidgetD(),
            ],
          ),
        ),
      ));
}

界面如下

Simulator Screen Shot - iPhone 13 - 2022-05-14 at 13.04.59.png

点击increment,只会有 WidgetCWidgetB 进行刷新。

flutter: widget C 刷新
flutter: widget B Text 局部刷新

Simulator Screen Shot - iPhone 13 - 2022-05-14 at 13.08.43.png

这样我们就可以定义我们自己的inheritedWidget,并通知其子部件进行数据刷新。

总结

我们可以使用InheritedWidget共享全局状态,子部件可以获取其状态,并控制小部件刷新

最后附上本文涉及的示例代码 inherited_widget_demo

如果觉得有收获请按如下方式给个 爱心三连:👍:点个赞鼓励一下。🌟:收藏文章,方便回看哦!。💬:评论交流,互相进步!