跳至主要內容

Flutter UI 绘制与 InheritedWidget 解析

JI,XIAOYONG...大约 6 分钟

Flutter UI 绘制与 InheritedWidget 解析

Flutter 的Widget分为StatefulWidgetStatelessWidget ,二者都继承自Widget

此外还有一种用来传输数据的Widget——InheritedWidget,与上述两者不太一样的是,他的继承关系是:InheritedWidgetProxyWidgetWidget

Flutter 的渲染流程如图:

flutter_widget_element_renderobject_relationship
flutter_widget_element_renderobject_relationship

可以简单理解为, Widget是配置信息,Element代表在树中详细的位置,而RenderObject则是实际渲染的对象。

StatelessWidgetStatefulWidget在创建之后就不会再变化,而StatefulWidget因为有State,所以可以在State调用setState()方法之后,重新执行Statebuild()方法,从而更新界面。

如果Widgetconst的,那么他就不会被rebuild

Widget Rebuild 的过程

以 StatefulWidget 为例:

flutter_render_flow_chart
flutter_render_flow_chart
  1. 调用setState()方法,会调用对应的ElementmarkNeedsBuild() 方法,通过BuildOwnerscheduleBuildFor(Element element) 方法将当前Element标记为dirty,以便在下次屏幕刷新时安排rebuilt

  2. 下一帧屏幕刷新,调用BuildOwnerbuildScope(Element context, [ VoidCallback? callback ]) 方法。这个方法会遍历_dirtyElements 中所有dirtyelement执行element.rebuild(); 方法,在其内部调用了ElementperformRebuild() 方法。

  3. ElementperformRebuild() 方法因各个Element的实现而异:

    1. StatelessElementInheritedElement:与父类ComponentElement 保持一致

    2. StatefulElement :判断有需要时调用state.didChangeDependencies(); ,其余与父类ComponentElement 保持一致

ComponentElementperformRebuild() 主要做了 2 件事:
(1)built = build(); ;(2)_child = updateChild(_child, built, slot);

在这其中build()

  1. StatelessElement:build() => widget.build(this);
  2. StatefulElement : build() => state.build(this);
  3. InheritedElement :build() => widget.child;

updateChild 会判断以下几种情况:

newWidget == nullnewWidget != null
child == nullreturn nullreturn new Element
child != nullremove old child, return nullOld child updated if possible, returns child or new Element

其中,old child updated 的时候调用的是child.update(newWidget); 方法会触发Widgetrebuild()

这样就完成了一次 Rebuild。

InheritedWidget 的 Rebuild 过程

InheritedWidget是持有状态的Widget,他的子Widget可以通过他来获取这些状态。

一般来说,InheritedWidget持有的状态是final的,如果要更新状态,就需要在其外部包裹一个StatefulWidget,通过StatefulWidgetState.setState()来触发InheritedWidget重建(实际上InheritedElement没有重新创建),从而更新那些依赖了InheritedWidget的子Widget

下图是一个被StatefulWidget包裹的InheritedWidgetsetSate(){}方法执行后的流程图:

flutter_render_flow_chart_with_inheritedwidget
flutter_render_flow_chart_with_inheritedwidget

当外层StatefulWidgetElement执行到updateChild(child,build,solt);会调用InheritedElementupdate() 方法。

这个方法内部会调用updated(oldWidget) 方法,在内部通过notifyClients(oldWidget); 方法,通知原先的InheritedElement_dependents ,将其标记为dirty,准备rebuild

在此之后,update()方法还会将当前Element标记为dirty,通过调用rebuild(); 执行performRebuild();

performRebuild()方法中:

  • built = build(); 中的build方法:build() => widget.child; 实际上取了widgetchild
  • 然后执行_child = updateChild(_child, built, slot); 这个过程与普通Widget一致。

需要注意的是,updateChild 中,如果子Widget不是const (或者被InheritedWidget外层的widget/state之类的持有)就会被认为built!=_child 从而导致InheritedWidget的子Widget重建。导致的结果就是:虽然InheritedWidget的确只标记了那些依赖了他的Widget,但是由于直接子Widget要重建,所以还是所有的非const Widget都重建了。

InheritedWidget 的获取方式

  • T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect }); 获取指定类型的InheritedWidget,并且将自己注册到此Widget,以便当该Widget变化的时候,自己也能rebuilt 。复杂度O(1)
  • T? findAncestorWidgetOfExactType<T extends Widget>(); 只获取指定类型的Widget ,包括InheritedWidget ,仅获取该Widget执行一些操作,通常用在interaction event handlers 之类中。复杂度(O(N)

代码示例

根据上述理论,创建一个InheritedWidget来传递数据:

1、AppColor.dart 一个持有colorInheritedWidget

class AppColor extends InheritedWidget {
  final Color color;

  final Widget child;

  Function(Color)? onColorChanged;

  AppColor({
    required this.color,
    required this.child,
    this.onColorChanged,
  }) : super(child: child);

  
  bool updateShouldNotify(covariant AppColor oldWidget) =>
      color != oldWidget.color;

  static AppColor? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppColor>();
  }
}

2、定义一些类,使用或未使用到InheritedWidget

class NoName extends StatelessWidget {
  const NoName({
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    print("NoName build ${this.hashCode}");
    return Column(
      children: [
        Column(
          children: [
            // 这里 AppColor 的_dependents 会加入 ColorfulContainer(dependencies: [AppColor])
            // 因为他用了 context.dependOnInheritedWidgetOfExactType<AppColor>();
            // 会将自己注册到 AppColor
            ColorfulContainer(),
            ChangeStateButton(),
            Text("This Text Should Not Rebuild"),
          ],
        )
      ],
    );
  }
}

class ColorfulContainer extends StatelessWidget {
  ColorfulContainer({Key? key}) : super(key: key);

  Widget build(BuildContext context) {
    var appColor = AppColor.of(context);
    print(
        "_ColorfulContainerState appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");

    return Container(
      color: appColor?.color,
      height: 100,
      child: Text("hello color ${appColor?.color}"),
    );
  }
}

class ChangeStateButton extends StatefulWidget {
  
  State<ChangeStateButton> createState() => _ChangeStateButtonState();
}

class _ChangeStateButtonState extends State<ChangeStateButton> {
  
  Widget build(BuildContext context) {
    return MaterialButton(
      onPressed: () {
        // 注意下面这个方法,只是查找到 InheritedWidget 的引用,并没有注册依赖
        // 所以当 InheritedWidget 变化的时候并不会触发此控件重建
        // 因为每次 onColorChanged 时 AppColor 都会重建,所以需要在这里获取最新的
        var appColor = context.findAncestorWidgetOfExactType<AppColor>();
        print(
            "_ChangeStateButtonState appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");

        var color = appColor?.color;
        var newColor = color == Colors.teal ? Colors.blueAccent : Colors.teal;
        appColor?.onColorChanged?.call(newColor);
        print(
            "_ChangeStateButtonState onPressed appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");
      },
      child: Text("Change State Button, Shlould NOT Rebuild"),
    );
  }
}

3、接下来实现一种基础的使用InheritedWidget的方法,这种方法会在InheritedWidget更新的时候,rebuilt InheritedWidget下面的所有子类,无论他们是否使用到了InheritedWidget(原因是上面说到的 Flutter rebuild 的机制导致的,实际上InheritedWidget本身只标记了ColorfulContainerdirty)。

class AlwaysRebuildWidget extends StatefulWidget {
  final Color color;

  AlwaysRebuildWidget({Key? key, required this.color}) : super(key: key);

  
  State<AlwaysRebuildWidget> createState() => _AlwaysRebuildWidgetState();
}

class _AlwaysRebuildWidgetState extends State<AlwaysRebuildWidget> {
  late Color _color;
  var child = NoName();

  
  void initState() {
    super.initState();
    _color = widget.color;
  }

  
  Widget build(BuildContext context) {
    // 这种写法,AppColor 的_dependents 也只有 1 个。ColorfulContainer(dependencies: [AppColor])
    // 所以每次 setState 引起 AlwaysRebuildWidget 重新绘制,引起 AppColor 重新创建,本应该会重建 ColorfulContainer
    // 但是因为 build 方法重新执行了一次,所以 AppColor 和整个 NoName 都被重建,
    print("AlwaysRebuildWidget build${widget.hashCode}");

    return AppColor(
        color: _color,
        onColorChanged: (color) {
          setState(() {
            _color = color;
          });
        },
        // 这种写法,AppColor 在 updateChild 的时候会判断 widget.child 与_child.widget 的 NoName 不一致
        // (这是因为,AppColor 在 notifyClients 的时候修改了 NoName 的 child 之一 ColorfulContainer 为 dirty)
        // 从而会更新 NoName,导致 NoName 下面所有的子 Widget 全部重新绘制
         child: NoName());
    // 按照上面分析的逻辑,在这里加上 const,那么依旧用的是之前的 NoName,就不会 repaint 整个的 NoName 了
    // child: const NoName());
  }
}

4、接下来实现一种使用InheritedWidget的方法,当InheritedWidget更新的时候,只会更新那些在InheritedWidget这里注册依赖了的Widget

class SelectiveRebuildWidget extends StatefulWidget {
  final Widget child;

  final Color color;

  SelectiveRebuildWidget({Key? key, required this.child, required this.color})
      : super(key: key) {}

  
  State<SelectiveRebuildWidget> createState() => _SelectiveRebuildWidgetState();
}

class _SelectiveRebuildWidgetState extends State<SelectiveRebuildWidget> {
  late Color _color;

  
  void initState() {
    super.initState();
    _color = widget.color;
  }

  
  Widget build(BuildContext context) {
    print(
        "SelectiveRebuildWidget build${widget.child.hashCode}  ${widget.hashCode}");
    return AppColor(
      color: _color,
      onColorChanged: (color) {
        setState(() {
          _color = color;
        });
      },
      // 这里的 AppColor 的_dependents 只有 1 个。ColorfulContainer(dependencies: [AppColor])
      // 因为 setState 不会重新创建 SelectiveRebuildWidget,所以 widget.child 也没有被重新
      // 创建(但是重新绘制了,导致 AppColor 也重新绘制)
      // 所以 AppColor 的 child 还是之前的,按照 InheritedWidget 的规则,只有 ColorfulContainer 重新绘制了
      child: widget.child,
    );
  }
}

参考资料

Using Inherited Widget In Flutteropen in new window

【Flutter 学习】之 Widget 数据共享之 InheritedWidget 梁飞宇open in new window

InheritedWidget confusionopen in new window

Managing Flutter Application State With InheritedWidgetsopen in new window

Does using const in the widget tree improve performance?open in new window

StatefulWidgetopen in new window

文章标题:《Flutter UI 绘制与 InheritedWidget 解析》
本文作者: JI,XIAOYONG
发布时间: 2022/05/15 19:02:13 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/4c220635.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8