背景

为什么说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润饰再看看

/// 改成constconst 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类,这两个下期我再剖析。