美文网首页Flutter小笔记
真的一切都是widget么

真的一切都是widget么

作者: 李发糕 | 来源:发表于2019-07-20 15:06 被阅读0次

    flutter第一篇。本来这片应该先放出来,但总感觉自己理解有些偏差,改来改去只好作为第二篇了

    既然所谓一切都是widget,那我们就从widget看起

    widget

    先截取了一段官方注释,相信我,这真的不是机翻(其实我觉得机翻翻的比我好=。=),老哥们我尽力了T T 英语好的同学可以自己去看一下英文注释

    /// 用于描述Element的配置 /这里我们看到一个类 Element 是干啥的呢,后面再说
    /// widget是Flutter框架中的中心结构. widget是对用户界面的一部分的不可变的描述
    ///
    /// widgets 能够生成完成底层渲染树的的elements
    ///
    /// widgets 本身没有可变状态,他的所有属性必须是final 如果你想要widget关联一个可变的状态,可以
    /// 考虑使用StatefulWidget 当它(考虑使用StatefulWidget)被生成element并添加到tree中时会通过
    /// StatefulWidget.createState 创建一个State
    ///
    /// 一个给定的widget可以被0次或多次包含在tree中 特别是一个给定的widget可以被多次放置到tree中,每
    /// 被添加一次,他就会生成一个Element
    ///
    /// key这个属性用于控制tree中widget的替换方式 如果两个widget的runtimeType和key这两个属性各自
    /// 相等(operator==)则新的widget会通过更新底层element的方式(通过调用新widget的
    /// Element.call)来更新,反之,则会先在tree中移除旧的Element,在使用新的widget生成element然
    /// 后插入tree中
    ///
    /// 另外
    /// StatefulWidget和State,适用于多次构建不同的widget。
    /// InheritedWidget 用于引入可以被后代widget读取的周围的状态 /对叭起,这里我翻译的实在是不咋
    /// 地,我们看到这里的时候再详细说啦
    /// StatelessWidget 用于构建方式和状态等都不变的widget
    

    这里我们再看看widget的代码 不是很多 我省略了一部分

    @immutable//这个注解代表了这个类是不可变的,我们上面提到过
    abstract class Widget extends DiagnosticableTree {
      /// Initializes [key] for subclasses.
      const Widget({ this.key });//构造函数,需要一个key
    
      /// 这里的注释提及了使用globalkey 暂时先不看
      final Key key;
    
      Element createElement();//创建填充元素
      
      @override
      String toStringShort() {//用runtimetype和key生成字段
        return key == null ? '$runtimeType' : '$runtimeType-$key';
      }
      ···
      static bool canUpdate(Widget oldWidget, Widget newWidget) {//能否直接更新
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
    }
    

    可以看到,我们平时使用最多的widget,既没有测量的代码,也没有绘制的代码。因为他根本就不是想象中android的View。注释中也说明了,widget只是对element的描述,可以通过widget来创建element。

    那么element是不是想象中的view呢?我们看一下

    Element

    老规矩,先看我人肉翻译的晦涩难懂的类注释

    /// widget在tree中特定位置的实例
    ///
    /// widget描述如何配置一个子树,同一个widget可以多次配置多个子树,因为widget是不可变的。
    /// 一个Element相当于widget在tree中特定位置的使用 一个与提供的elemengt所关联的widget是可以被更
    /// 换的,比如父widget rebuilds并且给这个位置创建了新的widget
    /// 
    /// Element是树形的 大部分element有唯一的孩子,但是一些例如继承RenderObjectElement的widget
    /// 可以有多个孩子
    /// 
    /// 下面是elements的生命周期:
    ///  * 框架通过调用用于元素初始配置的widget的createElement方法创建一个element
    ///  * 框架调用mount方法将新创建的元素添加到树中给定父级的给定位置。mount方法负责子widget填充以
    ///      及必要时调用attachRenderObject把相关的渲染对象添加到渲染树中
    ///  * 此时该元素被视为“活跃的(active)”,有可能显示在屏幕上
    ///  * 在某些时候,父级可能决定更改用于配置此element的widget,比如父级使用新状态重构。新的widget
    ///      总是和旧的有相同的key和runtimeType 所以此时框架会调用新widget的update方法来更新。
    ///      如果父级希望更改此处的widget的runtimeType或key,可以先移除这个element然后再填充一个新的
    ///      widget到这里来
    ///  * 在某些时候,祖先要从树中删除这个元素(或者是中间祖先),这通过调用自己的deactivateChild来
    ///      实现。对中间祖先的Deactivating会从渲染树中移除元素的渲染对象并把这个元素添加到其owner的
    ///      “不活跃(inactive)”元素列表中,因为框架会调用此元素的deactivate方法
    ///  * 此时此元素是”不活跃”的并且不会出现在屏幕上,这个状态可以一直持续到这一帧动画结束,当动画结束
    ///      时所有“不活跃”的元素将会被移除
    ///  * 如果元素被重新整合到树中(例如,因为它或一个它的祖先有一个重用的globalkey),框架会从将其从
    ///      它的owner的非活跃元素列表中删除,调用它的activate激活元素并重新把它的渲染对象添加到渲染树
    ///      上,此时,该元素再次被视为“活跃”并可能出现在屏幕上。)
    ///  * 如果此元素在当前帧结束时还没有被重新整合进去,则最后会通过调用unmount将其移除
    ///  * 此时此元素时是“不存在”的,并且以后也不会再被添加到树中了
    

    下面看一下Element的代码,比较多,我们挑重要的看

    首先看看element持有的属性及构造方法和部分重写方法

    abstract class Element extends DiagnosticableTree implements BuildContext {//这里第一次见到了这个BuildContext 可见flutter中的context与android中的context是完全不一样的。
      Element(Widget widget)//构造方法 必须要传入非空widget
        : assert(widget != null),
          _widget = widget;
      Element _parent;//持有他的父节点
      @override
      bool operator ==(Object other) => identical(this, other);//重写了相等判断方法
    
      @override
      int get hashCode => _cachedHash;//重写hashcode 递增
      final int _cachedHash = _nextHashCode = (_nextHashCode + 1) % 0xffffff;
      static int _nextHashCode = 1;
    
      dynamic get slot => _slot;//在父节点中的位置信息
      dynamic _slot;
    
      int get depth => _depth;//在树中的深度,根节点为0
      int _depth;
    
      static int _sort(Element a, Element b) {//排序,先比较深度,在比较是不是dirty,这个dirty为true代表了被标记了需要rebuild
        if (a.depth < b.depth)
          return -1;
        if (b.depth < a.depth)
          return 1;
        if (b.dirty && !a.dirty)
          return -1;
        if (a.dirty && !b.dirty)
          return 1;
        return 0;
      }
    
      @override
      Widget get widget => _widget;//widget ,一个element对应一个widget,是element的配置信息
      Widget _widget;
      
      /// 管理元素生命周期的对象BuildOwner
      @override
      BuildOwner get owner => _owner;
      BuildOwner _owner;
    
      bool _active = false; //类注释中提到的字段 是否是活跃的
    }
    

    element的代码比较多,简单看一下类注释中生命周期内提及的相关方法

    首先就是mount 挂载方法

    ///当一个新创建的element第一次被添加到tree时调用此方法,使用此方法根据父节点来初始化自己的状态
    ///这个方法会把元素的生命周期从initial转换到active
       @mustCallSuper
      void mount(Element parent, dynamic newSlot) {
        assert(_debugLifecycleState == _ElementLifecycle.initial);
        assert(widget != null);
        assert(_parent == null);
        assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
        assert(slot == null);
        assert(depth == null);
        assert(!_active);//做一些状态判断断言
        _parent = parent;
        _slot = newSlot;//赋值parent及在parent中的位置
        _depth = _parent != null ? _parent.depth + 1 : 1;
        _active = true;//进入活跃状态
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner;
        if (widget.key is GlobalKey) {
          final GlobalKey key = widget.key;
          key._register(this);//如果是GlobalKey,则需要注册 这个方法我们等下再看
        }
        _updateInheritance();//更新继承关系
        assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
      }
    

    这里就可以看出来,这个element实际是一棵树。然后就是注释中提到的渲染用的方法

    attachRenderObject 实际还有一个方法detachRenderObject 我们一起来看一下

      void detachRenderObject() {
        visitChildren((Element child) {
          child.detachRenderObject();
        });
        _slot = null;
      }
    
      void attachRenderObject(dynamic newSlot) {
        assert(_slot == null);
        visitChildren((Element child) {
          child.attachRenderObject(newSlot);
        });
        _slot = newSlot;
      }
    

    可以看到这两个方法都是对自己的子树递归调用此方法,并没有具体的实现。查找发现具体的实现是由子类RenderObjectElement完成的。另一方面按照注释所说mount中有必要的时候会调用attachRenderObject,element中也并没有调用,实际上也是在RenderObjectElement的mount中调用的。由此应该能感觉的出来,并不是所有的element都要经过渲染绘制。具体和randerObject相关的部分一会儿再讲,我们还是先了解一下整体结构。

    再下一个就是deactivateChild 移除某一个子element 我们看一下代码

    @protected
      void deactivateChild(Element child) {
        assert(child != null);
        assert(child._parent == this);
        child._parent = null; //断开父节点
        child.detachRenderObject();//移除渲染对象
        owner._inactiveElements.add(child); // 放入非活跃list
        assert(() {
          if (debugPrintGlobalKeyedWidgetLifecycle) {
            if (child.widget.key is GlobalKey)
              debugPrint('Deactivated $child (keyed child of $this)');
          }
          return true;
        }());
      }
    
    //这里注意的是,非活跃list实际上是一个_InactiveElements对象,我们看一下他的add方法
    void add(Element element) {
        assert(!_locked);
        assert(!_elements.contains(element));
        assert(element._parent == null);
        if (element._active)
          _deactivateRecursively(element);
        _elements.add(element);
      }
    void _deactivateRecursively(Element element) {//对其及其子树的所有节点element调用deactivate方法
        assert(element._debugLifecycleState == _ElementLifecycle.active);
        element.deactivate();
        assert(element._debugLifecycleState == _ElementLifecycle.inactive);
        element.visitChildren(_deactivateRecursively);
        assert(() { element.debugDeactivated(); return true; }());
     }
    

    下面再看看如果二次激活的情况

    @mustCallSuper
      void activate() {
        assert(_debugLifecycleState == _ElementLifecycle.inactive);
        assert(widget != null);
        assert(owner != null);
        assert(depth != null);
        assert(!_active);//判断各种状态
        final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
        _active = true;
        // We unregistered our dependencies in deactivate, but never cleared the list.
        // Since we're going to be reused, let's clear our list now.
        _dependencies?.clear();//清除依赖列表
        _hadUnsatisfiedDependencies = false;
        _updateInheritance();//更新继承
        assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
        if (_dirty)
          owner.scheduleBuildFor(this);//提交构建
        if (hadDependencies)
          didChangeDependencies();//更改依赖
      }
    

    上面这个方法涉及了依赖和Inheritance等,暂时并不清楚,我们先放一放~

    最后看一下类注释所说的,当这一帧动画结束的时候干了啥呢

    那首先我们来到WidgetBinding类中~ 先看看这个类注释:The glue between the widgets layer and the Flutter engine./小部件层和Flutter引擎之间的粘合剂。

    @override
    void drawFrame() {//这里是绘制一震的方法,我们暂时跳过别的方法,直接看最后~
      assert(!debugBuildingDirtyElements);
      assert(() {
        debugBuildingDirtyElements = true;
        return true;
      }());
      try {
        if (renderViewElement != null)
          buildOwner.buildScope(renderViewElement);
        super.drawFrame();
        buildOwner.finalizeTree();//这里这一帧就绘制完毕了,它调用了BuildOwner的finalizeTree方法 我们直接去看这个方法,其余的代码暂时先跳过
      } finally {
        assert(() {
          debugBuildingDirtyElements = false;
          return true;
        }());
      }
      if (!kReleaseMode) {
        if (_needToReportFirstFrame && _reportFirstFrame) {
          developer.Timeline.instantSync('Widgets completed first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
          _needToReportFirstFrame = false;
        }
      }
    }
    

    buildowner中的方法也比较长,我们只看前面就够了

    void finalizeTree() {
      Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
      try {
        lockState(() {//这个先不看,暂时只要知道他会会掉这个参数方法就好了
          _inactiveElements._unmountAll(); //这里调用了非活跃列表的卸载所有方法
        });
    

    然后我们又回到_inactiveElements了

    void _unmountAll() {
      _locked = true;
      final List<Element> elements = _elements.toList()..sort(Element._sort);//调用排序,我们之前看到过,跟节点排在前面,没有标记需要rebuild的排在前面
      _elements.clear();//清除
      try {
        elements.reversed.forEach(_unmount);//依次循环调用ummount
      } finally {
        assert(_elements.isEmpty);
        _locked = false;
      }
    }
    void _unmount(Element element) {
        assert(element._debugLifecycleState == _ElementLifecycle.inactive);//当前element生命周期断言,必须是不活跃的
        assert(() {
          if (debugPrintGlobalKeyedWidgetLifecycle) {
            if (element.widget.key is GlobalKey)
              debugPrint('Discarding $element from inactive elements list.');
          }
          return true;
        }());
        element.visitChildren((Element child) {//递归调用element及其子树所有节点element的umount方法
          assert(child._parent == element);
          _unmount(child);
        });
        element.unmount();
        assert(element._debugLifecycleState == _ElementLifecycle.defunct);
      }
    

    然后就是element的unmount 因为本身就是抽象类,同样在这个方法中没有做太多的事

    @mustCallSuper
    void unmount() {
      assert(_debugLifecycleState == _ElementLifecycle.inactive);
      assert(widget != null);
      assert(depth != null);
      assert(!_active);//一些状态的断言
      if (widget.key is GlobalKey) {//如果是全局key,需要解除注册
        final GlobalKey key = widget.key;
        key._unregister(this);
      }
      assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; }());
    }
    

    最后还有一个方法要看一下updateChild

    还是先看一下注释

    /// 根据给的新配置更新子节点
    /// 此方法是widget系统的核心。每次我们根据更新的配置去添加更新或删除子节点都要调用此方法
    /// ···中间省略一段描述,总结下来就是下面这个表
    ///
    /// |                     | **newWidget == null**  | **newWidget != null**   |
    /// | :-----------------: | :--------------------- | :---------------------- |
    /// |  **child == null**  |  Returns null.         |  Returns new [Element]. |
    /// |  **child != null**  |  Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
    

    然后看一下代码

    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
        ···
        if (newWidget == null) {
          if (child != null)
            deactivateChild(child);//新的为空说明需要移除旧的
          return null;
        }
        if (child != null) {
          if (child.widget == newWidget) {//如果配置相同
            if (child.slot != newSlot)//位置不同
              updateSlotForChild(child, newSlot);//更新位置
            return child;
          }
          if (Widget.canUpdate(child.widget, newWidget)) {//可以直接更新
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            child.update(newWidget);//直接调用update方法
            ···
            return child;
          }
          deactivateChild(child);//如果不能直接更新,就先移除旧的
          assert(child._parent == null);
        }
        return inflateWidget(newWidget, newSlot);//插入新的
      }
    

    下面再看看如何给当前element填充一个按照给定配置的element子节点

    @protected
    Element inflateWidget(Widget newWidget, dynamic newSlot) {
      assert(newWidget != null);
      final Key key = newWidget.key;
      if (key is GlobalKey) {//关于GlobalKey的部分暂时先不了解,下一节再讲
        final Element newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
          assert(newChild._parent == null);
          assert(() { _debugCheckForCycles(newChild); return true; }());
          newChild._activateWithParent(this, newSlot);
          final Element updatedChild = updateChild(newChild, newWidget, newSlot);
          assert(newChild == updatedChild);
          return updatedChild;
        }
      }
      final Element newChild = newWidget.createElement();//这里可以看到,通过widget的createelement方法获取elemenet实例
      assert(() { _debugCheckForCycles(newChild); return true; }());
      newChild.mount(this, newSlot);//再挂载到此节点下
      assert(newChild._debugLifecycleState == _ElementLifecycle.active);
      return newChild;
    }
    

    可以看到,顺序和之前看到的生命周期中说明的一样

    到此为止,widget层面的两个比较重要的抽象类就先看到这里了,当然还有一个,就是element继承的buildconetxt,实际上在在我刚开始学习flutter代码时,并没有见到element,而比较多见的是buildcontext,我们先简单的看一下

    BuildContext

    老规矩,先看注释

    /// widget在widget tree中的位置的"把手"?/意思到了就行,我也不知道咋翻译好了,,,
    ///
    /// 这个类提出了能够被StatelessWidget.build和State使用的方法的集合/说白了实际上就是个接口,只不
    /// 过dart没有接口
    /// 
    /// buildobject对象被传递给WidgetBuilder中的方法(例如StatelessWidget.build),并可以通过
    /// State.context获取到。一些静态方法例如showDialog,Theme.of等一些静态函数还采用构建上下文,
    /// 以便它们可以代表调用窗口小部件,或者专门为给定上下文获取数据
    ///
    /// 每个widget都有它自己的通过父节点的StatelessWidget.build或State.build方法返回的
    /// buildContext
    ///
    /// 特别的,特别是,这意味着在构建方法中,构建方法的widget的构建context与该构建方法返回的widget
    /// 的构建context不同。这可能导致一些棘手的情况。例如,Theme.of(context)方法用于查找给定的
    /// context的最近包裹的主题。如果一个widget Q 的构建方法包含了一个Theme在他返回的widgettree里
    /// 面,并且试图使用对他自己的context调用Themeof,那么这个Q的构造方法是找不到这个Theme的,他会找
    /// Q的祖先的Theme。如果需要使用返回的tree的子部分的context,那么需要使用回调的方式
    /// 
    /// 例如下面的代码
    ///
    /// ```dart
    ///   @override
    ///   Widget build(BuildContext context) {
    ///     // here, Scaffold.of(context) returns null
    ///     return Scaffold(
    ///       appBar: AppBar(title: Text('Demo')),
    ///       body: Builder(
    ///         builder: (BuildContext context) {
    ///           return FlatButton(
    ///             child: Text('BUTTON'),
    ///             onPressed: () {
    ///               // here, Scaffold.of(context) returns the locally created Scaffold
    ///               Scaffold.of(context).showSnackBar(SnackBar(
    ///                 content: Text('Hello.')
    ///               ));
    ///             }
    ///           );
    ///         }
    ///       )
    ///     );
    ///   }
    /// ```
    /// 特定widget的buildcontext当widget在树中移动时可以多次改变位置 因此,除了执行单个同步函数之
    /// 外,不应缓存从此类上的方法返回的值。
    ///
    /// BuildContext对象就是Element对象. BuildContext接口用于阻止对element的直接操作
    

    到此为止,我们还是不清楚widget究竟是怎么显示出来的,下一片关于RenderObject再详细分析

    相关文章

      网友评论

        本文标题:真的一切都是widget么

        本文链接:https://www.haomeiwen.com/subject/jthulctx.html