真的一切都是widget么

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', {});
      _needToReportFirstFrame = false;
    }
  }
}

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

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

然后我们又回到_inactiveElements了

void _unmountAll() {
  _locked = true;
  final List 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么)