关于flutter:Flutter之万物皆Widget一种你没见过的方式来深入Widget

3次阅读

共计 9695 个字符,预计需要花费 25 分钟才能阅读完成。

背景

为什么说 Flutter 万物皆 Widget?首先你要晓得,Flutter 是什么,它是一个古代的响应式框架、一个 2D 渲染引擎、现成的 widget 和开发工具,基于 Skia,一个性能彪悍的 2D 图像绘制引擎,2005 年被 Google 收买,被广泛应用于 Chrome 和 Android 之上,等等吧,说白一点,Flutter 就是一个 UI 框架,所以说,万物皆 Widget,而 Widget 的中文意思是小部件,它为什么不能像 Android 或者 Ios 一样叫做 View 呢?因为 widget 既能够是一个构造元素(如按钮或菜单)、也能够是一个文本款式元素(如字体或色彩计划)、布局的一个方面(如填充)等等,咱们能够兼顾它们为 wiget,而不是 view,依据根本的命名标准,这就是一种正当的命名形象。那么接下来咱们学什么?

  • Widget 是什么
  • Widget 类构造
  • 跟着我实现一个 widget(间接继承 widget 抽象类)
  • Element 类构造
  • 深刻了解 Element

Widget 是什么

其实下面说了,所有皆 Widget,那咱们可不可以认为,在 flutter 的框架中,用到的货色都是 Widget 呢,当然不是哈,因为它是基于 Dart,所以有很多 Dart 的库,还是能够应用的,比方 AES,RSA 加密解密,Json 序列化等等,但你能够这么说,所有构建图形相干的货色都是 Widget,这就是 Widget

Widget 类构造

为什么说下类构造呢?类构造能够很清晰帮忙咱们梳理逻辑,从全局的角度对待整个构造

  • RenderObjectWidget 看名字咱们判断,它是持有 RenderObject 对象的 Widget,而通过其余通道理解到,RenderObject 实际上是实现界面的布局、测量与绘制, 像 Padding,Table,Align 都是它的子类
  • StatefulWidget 多了一个 State 状态的 Widget,子类都是能够动静扭转的如 CheckBox,Switch
  • StatelessWidget 就是一个一般的 Widget, 不可变如 Icon,Text。
  • ProxyWidget InheritedWidget 就是它的子类,咱们暂且认为它是子类能从父类拿数据的要害,当前再钻研,大多数的主题都是继承自 ProxyWidget

跟我一起实现一个 Widget

我不想和他人的教程思路一样,既然万物皆 Widget,那咱们就从实现一个 Widget 开始,而后一步步深刻,看到什么就去理解什么?来上代码

class TestWidget extends Widget{
  @override
  Element createElement() {
    // TODO: implement createElement
    throw UnimplementedError();}
}

创立一个 TestWidget 而后继承 Widget,而后会让你重写函数 createElement,返回一个 Element,通过这个咱们看的出,其实咱们创立的 Widget,最终必定是创立了一个 Element,那 Element 到底是什么呢?同样的思路,咱们继承 Element 看一下

class TestElement extends Element{TestElement(Widget widget) : super(widget);

  @override
  bool get debugDoingBuild => throw UnimplementedError();

  @override
  void performRebuild() {}

}

多了一个构造函数,传递 Widget 对象,get 函数 debugDoingBuild,还有 performRebuild 函数,都是干嘛的呢?

abstract class Element extends DiagnosticableTree implements BuildContext 

abstract class BuildContext {/// Whether the [widget] is currently updating the widget or render tree.
  ///
  /// For [StatefulWidget]s and [StatelessWidget]s this flag is true while
  /// their respective build methods are executing.
  /// [RenderObjectWidget]s set this to true while creating or configuring their
  /// associated [RenderObject]s.
  /// Other [Widget] types may set this to true for conceptually similar phases
  /// of their lifecycle.
  ///
  /// When this is true, it is safe for [widget] to establish a dependency to an
  /// [InheritedWidget] by calling [dependOnInheritedElement] or
  /// [dependOnInheritedWidgetOfExactType].
  ///
  /// Accessing this flag in release mode is not valid.
  bool get debugDoingBuild;
   

通过代码的跟踪咱们发现一些注解:

  • Element 继承自 DiagnosticableTree,并实现 BuildContext
  • DiagnosticableTree 是个“诊断树”,次要作用是提供调试信息。
  • BuildContext 相似原生零碎的上下文,它定义了 debugDoingBuild,通过注解咱们晓得,它应该就是一个 debug 用的一个标记位。
  • performRebuild 通过源码查看后发现,由 rebuild() 调用如下
  void rebuild() {if (!_active || !_dirty)
      return;
    performRebuild();}
  
    @override
  void update(ProxyWidget newWidget) {rebuild();
  }
  

首先阐明下,这个并不是 Element 的源码,我摘自 StatelessElement,是 Element 的子类,这阐明在 update 函数后,Element 就会间接执行 performRebuild 函数,那咱们欠缺下自定义的 Element 逻辑

class TestElement extends Element {TestElement(Widget widget) : super(widget);

  @override
  bool get debugDoingBuild => throw UnimplementedError();

  @override
  void performRebuild() {}

  @override
  void update(Widget newWidget) {super.update(newWidget);
    print("TestWidget update");
    performRebuild();}

  @override
  TestWidget get widget => super.widget as TestWidget;

  Widget build() => widget.build(this);
}

在 update 的时候执行 performRebuild(), 然而 performRebuild 执行什么呢?咱们联合一下 StatelessElement 的实现,发现,它调用了传递进来的 Widget 参数 build 函数,那么咱们就在 TestWidget 中增加函数,并欠缺下逻辑后是这样的

class TestWidget extends Widget {

  @override
  Element createElement() {
      /// 将本人传递进去,让 Element 调用上面的 build 函数
    return TestElement(this);
  }
   /// 这个 context 其实就是 Element
  Widget build(BuildContext context) {print("TestWidget build");
    return Text("TestWidget");
  }
}

class TestElement extends Element {

  Element _child;

  TestElement(Widget widget) : super(widget);

  @override
  bool get debugDoingBuild => throw UnimplementedError();

  @override
  void performRebuild() {
      /// 调用 build 函数
    var _build = build();
    /// 更新子视图
   _child =  updateChild(_child, _build, slot);
  }

  @override
  void update(Widget newWidget) {super.update(newWidget);
    print("TestWidget update");
    /// 更新
    performRebuild();}

  /// 将 widget 强转成 TestWidget
  @override
  TestWidget get widget => super.widget as TestWidget;
  /// 调用 TestWidget 的 build 函数
  Widget build() => widget.build(this);
}

而后将其放入 main.dart 中如图

最终成果展现,如图

展现进去了,咱们简略总结一下,到目前你学到了什么?

  • Widget 会创立 Element 对象(调用 createElement 并不是 Widget,而是 Framework)
  • Widget 并没有理论的操控 UI
  • Element 是在 update 的时候从新调用 Widget 的 build 函数来构建子 Widget
  • updateChild 会依据传入的 Widget 生成新的 Element
  • Widget 的函数 build,传入的 context 其实就是它创立的 Element 对象,那么为什么这么设计呢?一方面它能够隔离掉一些 Element 的细节,防止 Widget 频繁调用或者误操作带来的不确定问题,一方面 context 上下文能够存储树的构造,来从树种查找元素。

其实能够很简略的了解为,Widget 就是 Element 的配置信息,在 Dart 虚拟机中会频繁的创立和销毁,因为量比拟大,所以形象一层 Element 来读取配置信息,做一层过滤,最终再实在的绘制进去,这样做的益处就是防止不必要的刷新。接下来咱们深刻理解下 Element

Element 类构造

在深刻理解 Element 之前咱们也从全局看下它的构造

能够看到,Element 最次要的两个形象:

  • ComponentElement
  • RenderObjectElement

都是干嘛的呢?通过看源码,发现 ComponentElement,其实做了一件事件就是在 mount 函数中,判断 Element 是第一次创立,而后调用_firstBuild, 最终通过 rebuild 调用 performRebuild,通过下面咱们也晓得 performRebuild 最终调用 updateChild 来绘制 UI
而 RenderObjectElement 就比较复杂一点,它创立了 RenderObject,通过 RenderObjectWidget 的 createRenderObject 办法,通过以前的学习,咱们也晓得 RenderObject 其实是真正绘制 UI 的对象,所以咱们暂且认为 RenderObjectElement 其实就是能够间接操控 RenderObject,一种更间接的形式来管制 UI。

深刻了解 Element

为什么要深刻了解 Element 呢,因为大多数状况下,咱们开发者并不会间接操作 Element,但对于想要全局理解 FlutterUI 框架至关重要,特地切实一些状态治理的框架中,如 Provider,他们都定制了本人的 Element 实现,那么这么重要,咱们须要从哪方面理解呢?一个很重要的知识点就是生命周期,只有理解了正确的生命周期,你能力在适合的工夫做适合的操作

为了验证该图,咱们退出日志打印下,代码如下:

/// 创立 LifecycleElement 实现生命周期函数
class LifecycleElement extends TestElement{LifecycleElement(Widget widget) : super(widget);

  @override
  void mount(Element parent, newSlot) {print("LifecycleElement mount");
    super.mount(parent, newSlot);
  }

  @override
  void unmount() {print("LifecycleElement unmount");
    super.unmount();}

  @override
  void activate() {print("LifecycleElement activate");
    super.activate();}

  @override
  void rebuild() {print("LifecycleElement rebuild");
    super.rebuild();}

  @override
  void deactivate() {print("LifecycleElement deactivate");
    super.deactivate();}

  @override
  void didChangeDependencies() {print("LifecycleElement didChangeDependencies");
    super.didChangeDependencies();}

  @override
  void update(Widget newWidget) {print("LifecycleElement update");
    super.update(newWidget);
  }

  @override
  Element updateChild(Element child, Widget newWidget, newSlot) {print("LifecycleElement updateChild");
    return super.updateChild(child, newWidget, newSlot);
  }

  @override
  void deactivateChild(Element child) {print("LifecycleElement deactivateChild");
    super.deactivateChild(child);
  }

}

class TestWidget extends Widget {

  @override
  Element createElement() {
    /// 将本人传递进去,让 Element 调用上面的 build 函数
    /// 更新 TestElement 为 LifecycleElement
    return LifecycleElement(this);
  }
  /// 这个 context 其实就是 Element
  Widget build(BuildContext context) {return Text("TestWidget");
  }
}

而后革新下 main.dart, 如下

/// 增加变量
  bool isShow = true;
/// 退出变量管制
  isShow ? TestWidget() : Container(),
/// 将 floatingActionButton 改为这样的实现
 onPressed: () {setState(() {isShow = !isShow;});
        },

运行一下我的项目查看日志

  • 调用 element.mount(parentElement,newSlot)
  • 调用 update(Widget newWidget)
  • 调用 updateChild(Element child, Widget newWidget, newSlot)

而后咱们点击下按钮

  • 调用 deactivate()
  • 调用 unmount()

咱们再点击下按钮

这次只有 mount,为什么?因为 Widget 自身不可变,我判断是因为这个导致的,那如何判断呢?上面介绍一个小技巧,其实 flutter 的 framework 层是能够退出调试代码的,咱们退出日志看下,如下:

/// widget 基类其实有一个 canUpdate 函数,咱们猜想必定是这里导致的,退出日志如下
  static bool canUpdate(Widget oldWidget, Widget newWidget) {if(oldWidget.toString()=="TestWidget") {
      print("canUpdate${oldWidget.runtimeType == newWidget.runtimeType
          && oldWidget.key == newWidget.key}");
    }

    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

是个动态函数,必定是在 Element 中被调用的,咱们找下

@mustCallSuper
  void update(covariant Widget newWidget) {if (newWidget.toString() == "TestWidget") {print("TestWidget update start");
    }
  
    assert(_debugLifecycleState == _ElementLifecycle.active
        && widget != null
        && newWidget != null
        && newWidget != widget
        && depth != null
        && _active
        && Widget.canUpdate(widget, newWidget));

    assert(() {_debugForgottenChildrenWithGlobalKey.forEach(_debugRemoveGlobalKeyReservation);
      _debugForgottenChildrenWithGlobalKey.clear();
      return true;
    }());
      if (newWidget.toString() == "TestWidget") {print("TestWidget:${newWidget.hashCode}");
    }
    _widget = newWidget;
  }

如上代码是 Element 的源码,这里调用了 canUpdate 函数,如果不须要更新的话,就间接中断了执行,咱们从新运行下 demo, 并在加一个 print 来验证一下 newWidget 是什么样子的,这里退出 newWidget.toString() == “TestWidget”,次要是为了过滤垃圾日志,从新运行我的项目。如图

点击后按钮

再点击

发现并没有调用 canUpdate,那咱们如何让它从新加载回来呢?咱们查查材料,革新下例子

  @override
  void mount(Element parent, newSlot) {print("LifecycleElement mount");
    super.mount(parent, newSlot);
    assert(_child == null);
    print("LifecycleElement firstBuild");
    performRebuild();}

mount 函数退出 performRebuild() 函数,最终会触发 updateChild,加 assert 断言是避免前面再加载进来的时候屡次触发 updateChild,而后革新下 main.dart

@override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: isShow ? TestWidget() : Container(),
      ),
      floatingActionButton: FloatingActionButton(onPressed: () {setState(() {isShow = !isShow;});
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

去掉 Column,这里是因为咱们没有解决 widget 的 index 逻辑,导致在 Column 里不失常,后续咱们再钻研为什么,先来看下生命周期的回调

第一次运行

点击按钮

又发现一个问题,为什么咱们的断言没失效呢?怎么又呈现了 firstBuild?哈哈,这里不要纠结,因为 TestWidget 并非 const,导致 setState 后,又从新被创立了,而对应的 Element 也同样是创立了新的值,最终导致被从新执行。其实这个 TestWidget 曾经不是上一个了, 那咱们退出 const 润饰再看看

/// 改成 const
const TestWidget()

/// 退出以后 widget hashcode 输入,用来判断两次是否统一
  @override
  void mount(Element parent, newSlot) {print("LifecycleElement  widget hashcode${widget.hashCode}");
    print("LifecycleElement hashcode${this.hashCode}");
    print("LifecycleElement mount");
    super.mount(parent, newSlot);
    assert(_child == null);
    print("LifecycleElement firstBuild");
    performRebuild();}

最终(启动,点击按钮两次的成果)运行成果如下:

两次运行 Widget 保持一致,这就防止了 Widget 的重建

小结

通过测试咱们发现:

  • Widget 的创立能够做到复用,通过 const 润饰
  • Element 并没有复用, 其实起因应该是在于 isShow 为 false 的时候导致其被 deactivate 而后 unmount,从 Element 树种被移除掉。
  • 有的人必定有些疑难,怎么全程没看到 activate 呢?它不应该属于生命周期的一部分吗?这个就须要用到 Key 了,在接下来的课程里,讲到 Key 的时候,咱们再具体的学习。

总结

本期咱们对 Widget,Element 有了一个具体的认知,但其实它还有一个 State 类(StatefulWidget 的外围实现)和 RenderObject 类,这两个下期我再剖析。

正文完
 0