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再详细分析
网友评论