欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Flutter学习之构建、布局及绘制三部曲

程序员文章站 2023-09-04 00:00:00
前言 学习fullter也有些时间了,写过不少demo,对一些常用的widget使用也比较熟练,但是总觉得对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,如果没有孩子就只绘制自己的内容,流程比较简单。

以上是自己学习的一些总结,如有错误之处请指出,大家共同探讨,觉得不错的话,点个赞呗!

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。