Flutter学习之构建、布局及绘制三部曲
前言
学习fullter也有些时间了,写过不少demo,对一些常用的widget使用也比较熟练,但是总觉得对flutter的框架没有一个大致的了解,碰到有些细节的地方又没有文档可以查询,例如在写ui时总不知道为什么container添加了child就变小了;widget中key的作用,虽然官方有解释但是凭空来讲的话有点难理解。所以觉得深入一点的了解flutter框架还是很有必要的。
构建
初次构建
flutter的入口main方法直接调用了runapp(widget app)
方法,app参数就是我们的根视图的widget,我们直接跟进runapp方法
void runapp(widget app) { widgetsflutterbinding.ensureinitialized()//此方法是对flutter的框架做一些必要的初始化 ..attachrootwidget(app) ..schedulewarmupframe(); }
runapp方法先调用了widgetsflutterbinding.ensureinitialized()
方法,这个方法是做一些必要的初始化
class widgetsflutterbinding extends bindingbase with gesturebinding, servicesbinding, schedulerbinding, paintingbinding, semanticsbinding, rendererbinding, widgetsbinding { static widgetsbinding ensureinitialized() { if (widgetsbinding.instance == null) widgetsflutterbinding(); return widgetsbinding.instance; } }
widgetsflutterbinding混入了不少的其他的binding
- bindingbase 那些单一服务的混入类的基类
- gesturebinding framework手势子系统的绑定,处理用户输入事件
- servicesbinding 接受平台的消息将他们转换成二进制消息,用于平台与flutter的通信
- schedulerbinding 调度系统,用于调用transient callbacks(
window.onbeginframe
的回调)、persistent callbacks(window.ondrawframe
的回调)、post-frame callbacks(在frame结束时只会被调用一次,调用后会被系统移除,在persistent callbacks后window.ondrawframe
回调返回之前执行) - paintingbinding 绘制库的绑定,主要处理图片缓存
- semanticsbinding 语义化层与flutter engine的桥梁,主要是辅助功能的底层支持
- rendererbinding 渲染树与flutter engine的桥梁
- widgetsbinding widget层与flutter engine的桥梁
以上是这些binding的主要作用,在此不做过多赘述,widgetsflutterbinding.ensureinitialized()
返回的是widgetsbinding对象,然后马上调用了widgetsbinding的attachrootwidget(app)
方法,将我们的根视图的widget对象穿进去,我们继续看attachrootwidget方法
void attachrootwidget(widget rootwidget) { _renderviewelement = renderobjecttowidgetadapter<renderbox>( container: renderview, debugshortdescription: '[root]', child: rootwidget ).attachtorendertree(buildowner, renderviewelement); }
创建了一个renderobjecttowidgetadapter,让后直接调用它的attachtorendertree方法,buildowner是widget framework的管理类
renderobjecttowidgetelement<t> attachtorendertree(buildowner owner, [renderobjecttowidgetelement<t> element]) { if (element == null) { owner.lockstate(() { element = createelement(); assert(element != null); element.assignowner(owner); }); owner.buildscope(element, () { element.mount(null, null); }); } else { element._newwidget = this; element.markneedsbuild(); } return element; }
element为空,owner先锁定状态,然后调用了renderobjecttowidgetadapter的createelement()
返回了renderobjecttowidgetelement对象,让后将owner赋值给element(assignowner方法),让后就是owner调用buildscope方法
void buildscope(element context, [voidcallback callback]) { if (callback == null && _dirtyelements.isempty) return; timeline.startsync('build', arguments: timelinewhitelistarguments); try { _scheduledflushdirtyelements = true; if (callback != null) { _dirtyelementsneedsresorting = false; try { callback(); } finally {} } ... }
省略了部分以及后续代码,可以看到buildscope方法首先就调用了callback(就是element.mount(null, null)
方法),回到renderobjecttowidgetelement的mount方法
@override void mount(element parent, dynamic newslot) { assert(parent == null); super.mount(parent, newslot); _rebuild(); }
首先super.mount(parent, newslot)
调用了rootrenderobjectelement的mount方法(只是判定parent和newslot都为null),让后又继续向上调用了renderobjectelement中的mount方法
@override void mount(element parent, dynamic newslot) { super.mount(parent, newslot); _renderobject = widget.createrenderobject(this); attachrenderobject(newslot); _dirty = false; }
renderobjectelement中的mount方法又调用了element的mount方法
@mustcallsuper void mount(element parent, dynamic newslot) { _parent = parent; _slot = newslot; _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); } _updateinheritance(); }
element的mount方法其实就是进行了一些赋值,以确认当前element在整个树种的位置,让后回到renderobjectelement中的mount方法,调用了widget.createrenderobject(this)
方法,widget是renderobjecttowidgetadapter的实例,它返回的是renderobjectwithchildmixin对象,让后调用attachrenderobject方法
@override void attachrenderobject(dynamic newslot) { assert(_ancestorrenderobjectelement == null); _slot = newslot; _ancestorrenderobjectelement = _findancestorrenderobjectelement();//获取此renderobjectelement最近的renderobjectelement对象 _ancestorrenderobjectelement?.insertchildrenderobject(renderobject, newslot);//将renderobject插入renderobjectelement中 final parentdataelement<renderobjectwidget> parentdataelement = _findancestorparentdataelement(); if (parentdataelement != null) _updateparentdata(parentdataelement.widget); } ///renderobjecttowidgetelement中的insertchildrenderobject方法,简单将子renderobject赋值给父renderobject的child字段 @override void insertchildrenderobject(renderobject child, dynamic slot) { assert(slot == _rootchildslot); assert(renderobject.debugvalidatechild(child)); renderobject.child = child; }
element的mount方法确定当前element在整个树种的位置并插入,renderobjectelement中的mount方法来创建renderobject对象并将其插入到渲染树中,让后再回到renderobjecttowidgetelement方法,mount之后调用_rebuild()方法, _rebuild()
方法中主要是调用了element的updatechild方法
@protected element updatechild(element child, widget newwidget, dynamic newslot) { if (newwidget == null) {//当子widget没有的时候,直接将child deactivate掉 if (child != null) deactivatechild(child); return null; } if (child != null) {//有子element的时候 if (child.widget == newwidget) {//widget没有改变 if (child.slot != newslot)//再判断slot有没有改变,没有则不更新slot updateslotforchild(child, newslot);//更新child的slot return child;//返回child } if (widget.canupdate(child.widget, newwidget)) {//widget没有改变,再判断widget能否update,如果能还是重复上面的步骤 if (child.slot != newslot) updateslotforchild(child, newslot); child.update(newwidget); return child; } deactivatechild(child);//如果不能更新的话,直接将child deactivate掉,然后在inflatewidget(newwidget, newslot)创建新的element } return inflatewidget(newwidget, newslot);//根据widget对象以及slot创建新的element }
由于我们是第一次构建,child是null,所以就直接走到inflatewidget方法创建新的element对象,跟进inflatewidget方法
@protected element inflatinflatewidgetewidget(widget newwidget, dynamic newslot) { final key key = newwidget.key; if (key is globalkey) {//newwidget的key是globalkey final element newchild = _retakeinactiveelement(key, newwidget);//复用inactive状态的element if (newchild != null) { newchild._activatewithparent(this, newslot);//activate 此element(将newchild出入到element树) final element updatedchild = updatechild(newchild, newwidget, newslot);//直接将newchild更新 return updatedchild;//返回更新后的element } } final element newchild = newwidget.createelement();//调用createelement()进行创建 newchild.mount(this, newslot);//继续调用newchild element的mount方法(如此就行一直递归下去,当递归完成,整个构建过程也就结束了) return newchild;//返回子element }
inflatewidget中其实就是通过widget得到element对象,让后继续调用子element的mount的方将进行递归。
不同的element,mount的实现会有所不同,我们看一下比较常用的statelesselement、statefulelement,他们的mount方法实现在componentelement中
@override void mount(element parent, dynamic newslot) { super.mount(parent, newslot); _firstbuild(); } void _firstbuild() { rebuild();//调用了element的rebuild()方法 } //element的rebuild方法,通常被三处地方调用 //1.当buildowner.schedulebuildfor被调用标记此element为dirty时 //2.当element第一次构建由mount方法去调用 //3.当widget改变时,被update方法调用 void rebuild() { if (!_active || !_dirty) return; performrebuild();//调用performrebuild方法(抽象方法) } //componentelement的performrebuild实现 @override void performrebuild() { widget built; try { built = build();//构建widget(statelesselement直接调用build方法,statefulelement直接调用state.build方法) } catch (e, stack) { built = errorwidget.builder(_debugreportexception('building $this', e, stack));//有错误的化就创建一个errorwidget } finally { _dirty = false; } try { _child = updatechild(_child, built, slot);//让后还是根据wdiget来更新子element } catch (e, stack) { built = errorwidget.builder(_debugreportexception('building $this', e, stack)); _child = updatechild(null, built, slot); } }
再看一看multichildrenderobjectelement的mount方法
@override void mount(element parent, dynamic newslot) { super.mount(parent, newslot); _children = list<element>(widget.children.length); element previouschild; for (int i = 0; i < _children.length; i += 1) { final element newchild = inflatewidget(widget.children[i], previouschild);//遍历children直接inflate根据widget创建新的element _children[i] = newchild; previouschild = newchild; } }
可以看到不同的element构建方式会有些不同,element(第一层element)的mount方法主要是确定当前element在整个树种的位置并插入;componentelement(第二层)的mount方法先构建widget树,让后再递归更新(包括重用,更新,直接创建inflate)其element树;renderobjectelement(第二层)中的mount方法来创建renderobject对象并将其插入到渲染树中。multichildrenderobjectelement(renderobjectelement的子类)在renderobjectelement还要继续创建children element。
总结:首先是由widgetbinding创建renderobjecttowidgetadapter然后调用它的attachtorendertree方法,创建了renderobjecttowidgetelement对象,让后将它mount(调用mount方法),mount方法中调用的_rebuild,继而调用updatechild方法,updatechild会进行递归的更新element树,若child没有则需要重新创建新的element,让后将其mount进element树中(如果是renderobjectelement的化,mount的过程中会去创建renderobject对象,并插入到rendertree)。
通过setstate触发构建
通常我们在应用中要更新状态都是通过state中的setstate方法来触发界面重绘,setstate方法就是先调用了callback让后调用该state的element对象的markneedsbuild方法,markneedsbuild中将element标记为dirty并通过buildowner将其添加到dirty列表中并调用onbuildscheduled回调(在widgetsbinding初始化时设置的,它回去调用window.scheduleframe
方法),让后window的onbeginframe,ondrawframe回调(在schedulerbinding初始化时设置的,这两个回调会执行一些callback)会被调用,schedulerbinding通过persistercallbacks来调用到buildowner中buildscope方法。上面我们只看了buildscope的一部分,当通过setstate方法来触发界面重绘时,buildscope的callback为null
void buildscope(element context, [voidcallback callback]) { if (callback == null && _dirtyelements.isempty) return; timeline.startsync('build', arguments: timelinewhitelistarguments); try { _scheduledflushdirtyelements = true; if (callback != null) { element debugpreviousbuildtarget; _dirtyelementsneedsresorting = false; try { callback();//调用callback } finally {} } _dirtyelements.sort(element._sort); _dirtyelementsneedsresorting = false; int dirtycount = _dirtyelements.length; int index = 0; while (index < dirtycount) { try { _dirtyelements[index].rebuild();//遍历dirtyelements并执行他们的rebuild方法来使这些element进行rebuild } catch (e, stack) {} index += 1; if (dirtycount < _dirtyelements.length || _dirtyelementsneedsresorting) { _dirtyelements.sort(element._sort); _dirtyelementsneedsresorting = false; dirtycount = _dirtyelements.length; while (index > 0 && _dirtyelements[index - 1].dirty) { index -= 1; } } } } finally { for (element element in _dirtyelements) {//最后解除element的dirty标记,以及清空dirtyelements assert(element._indirtylist); element._indirtylist = false; } _dirtyelements.clear(); _scheduledflushdirtyelements = false; _dirtyelementsneedsresorting = null; timeline.finishsync(); } }
很明显就是对dirtyelements中的元素进行遍历并且对他们进行rebuild。
布局
window通过scheduleframe方法会让schedulerbinding来执行handlebeginframe方法(执行transientcallbacks)和handledrawframe方法(执行persistentcallbacks,postframecallbacks),在rendererbinding初始化时添加了_handlepersistentframecallback,它调用了核心的绘制方法drawframe。
@protected void drawframe() { assert(renderview != null); pipelineowner.flushlayout();//布局 pipelineowner.flushcompositingbits();//刷新dirty的renderobject的数据 pipelineowner.flushpaint();//绘制 renderview.compositeframe(); // 将二进制数据发送给gpu pipelineowner.flushsemantics(); // 将语义发送给系统 }
flushlayout触发布局,将renderobject树的dirty节点通过调用performlayout方法进行逐一布局,我们先看一下renderpadding中的实现
@override void performlayout() { _resolve();//解析padding参数 if (child == null) {//如果没有child,直接将constraints与padding综合计算得出自己的size size = constraints.constrain(size( _resolvedpadding.left + _resolvedpadding.right, _resolvedpadding.top + _resolvedpadding.bottom )); return; } final boxconstraints innerconstraints = constraints.deflate(_resolvedpadding);//将padding减去,生成新的约束innerconstraints child.layout(innerconstraints, parentusessize: true);//用新的约束去布局child final boxparentdata childparentdata = child.parentdata; childparentdata.offset = offset(_resolvedpadding.left, _resolvedpadding.top);//设置childparentdata的offset值(这个值是相对于parent的绘制偏移值,在paint的时候传入这个偏移值) size = constraints.constrain(size(//将constraints与padding以及child的sieze综合计算得出自己的size _resolvedpadding.left + child.size.width + _resolvedpadding.right, _resolvedpadding.top + child.size.height + _resolvedpadding.bottom )); }
可以看到renderpadding中的布局分两种情况。如果没有child,那么就直接拿parent传过来的约束以及padding来确定自己的大小;否则就先去布局child,让后再拿parent传过来的约束和padding以及child的size来确定自己的大小。
renderpadding是典型的单child的renderbox,我们看一下多个child的renderbox。例如renderflow
@override void performlayout() { size = _getsize(constraints);//直接先确定自己的size int i = 0; _randomaccesschildren.clear(); renderbox child = firstchild; while (child != null) {//遍历孩子 _randomaccesschildren.add(child); final boxconstraints innerconstraints = _delegate.getconstraintsforchild(i, constraints);//获取child的约束,此方法为抽象 child.layout(innerconstraints, parentusessize: true);//布局孩子 final flowparentdata childparentdata = child.parentdata; childparentdata.offset = offset.zero; child = childparentdata.nextsibling; i += 1; } }
可以看到renderflow的size直接就根据约束来确定了,并没去有先布局孩子,所以renderflow的size不依赖与孩子,后面依旧是对每一个child依次进行布局。
还有一种比较典型的树尖类型的renderbox,leafrenderobjectwidget子类创建的renderobject对象都是,他们没有孩子,他们才是最终需要渲染的对象,例如
@override void performlayout() { size = _sizeforconstraints(constraints); }
非常简单就通过约束确定自己的大小就结束了。所以performlayout过程就是两点,确定自己的大小以及布局孩子。我们上面提到的都是renderbox的子类,这些renderobject约束都是通过boxconstraints来完成,但是rendersliver的子类的约束是通过sliverconstraints来完成,虽然他们对child的约束方式不同,但他们在布局过程需要执行的操作都是一致的。
绘制
布局完成了,pipelineowner就通过flushpaint来进行绘制
void flushpaint() { try { final list<renderobject> dirtynodes = _nodesneedingpaint; _nodesneedingpaint = <renderobject>[]; // 对dirty nodes列表进行排序,最深的在第一位 for (renderobject node in dirtynodes..sort((renderobject a, renderobject b) => b.depth - a.depth)) { assert(node._layer != null); if (node._needspaint && node.owner == this) { if (node._layer.attached) { paintingcontext.repaintcompositedchild(node); } else { node._skippedpaintingonlayer(); } } } } finally {} }
paintingcontext.repaintcompositedchild(node)
会调用到child._paintwithcontext(childcontext, offset.zero)
方法,进而调用到child的paint方法,我们来看一下第一次绘制的情况,dirty的node就应该是renderview,跟进renderview的paint方法
@override void paint(paintingcontext context, offset offset) { if (child != null) context.paintchild(child, offset);//直接绘制child }
自己没有什么绘制的内容,直接绘制child,再看一下rendershiftedbox
@override void paint(paintingcontext context, offset offset) { if (child != null) { final boxparentdata childparentdata = child.parentdata; context.paintchild(child, childparentdata.offset + offset);//直接绘制child } }
好像没有绘制内容就直接递归的进行绘制child,那找一个有绘制内容的吧,我们看看renderdecoratedbox
@override void paint(paintingcontext context, offset offset) {//offset由parent去paintchild的时候传入,该值存放在child的parentdata字段中,该字段是boxparentdata或以下实例 _painter ??= _decoration.createboxpainter(markneedspaint);//获取painter画笔 final imageconfiguration filledconfiguration = configuration.copywith(size: size); if (position == decorationposition.background) {//画背景 _painter.paint(context.canvas, offset, filledconfiguration);//绘制过程,具体细节再painter中 if (decoration.iscomplex) context.setiscomplexhint(); } super.paint(context, offset);//画child,里面直接调用了paintchild if (position == decorationposition.foreground) {//画前景 _painter.paint(context.canvas, offset, filledconfiguration); if (decoration.iscomplex) context.setiscomplexhint(); } }
如果自己有绘制内容,paint方法中的实现就应该包括绘制自己以及绘制child,如果没有孩子就只绘制自己的内容,看一下renderimage
@override void paint(paintingcontext context, offset offset) { if (_image == null) return; _resolve(); paintimage(//直接绘制image,具体细节再此方法中 canvas: context.canvas, rect: offset & size, image: _image, scale: _scale, colorfilter: _colorfilter, fit: _fit, alignment: _resolvedalignment, centerslice: _centerslice, repeat: _repeat, fliphorizontally: _fliphorizontally, invertcolors: invertcolors, filterquality: _filterquality ); }
所以基本上绘制需要完成的流程就是,如果自己有绘制内容,paint方法中的实现就应该包括绘制自己以及绘制child,如果没有孩子就只绘制自己的内容,流程比较简单。
以上是自己学习的一些总结,如有错误之处请指出,大家共同探讨,觉得不错的话,点个赞呗!
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。