Flutter学习之构建、布局及绘制三部曲
前言
学习Fullter也有些时间了,写过不少demo,对一些常用的widget使用也比较熟练,但是总觉得对Flutter的框架没有一个大致的了解,碰到有些细节的地方又没有文档可以查询,例如在写UI时总不知道为什么container添加了child就变小了;widget中key的作用,虽然官方有解释但是凭空来讲的话有点难理解。所以觉得深入一点的了解Flutter框架还是很有必要的。
构建
初次构建
flutter的入口main方法直接调用了runApp(Widgetapp)方法,app参数就是我们的根视图的Widget,我们直接跟进runApp方法
voidrunApp(Widgetapp){
WidgetsFlutterBinding.ensureInitialized()//此方法是对flutter的框架做一些必要的初始化
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
runApp方法先调用了WidgetsFlutterBinding.ensureInitialized()方法,这个方法是做一些必要的初始化
classWidgetsFlutterBindingextendsBindingBasewithGestureBinding,ServicesBinding,SchedulerBinding,PaintingBinding,SemanticsBinding,RendererBinding,WidgetsBinding{
staticWidgetsBindingensureInitialized(){
if(WidgetsBinding.instance==null)
WidgetsFlutterBinding();
returnWidgetsBinding.instance;
}
}
WidgetsFlutterBinding混入了不少的其他的Binding
- BindingBase那些单一服务的混入类的基类
- GestureBindingframework手势子系统的绑定,处理用户输入事件
- ServicesBinding接受平台的消息将他们转换成二进制消息,用于平台与flutter的通信
- SchedulerBinding调度系统,用于调用Transientcallbacks(Window.onBeginFrame的回调)、Persistentcallbacks(Window.onDrawFrame的回调)、Post-framecallbacks(在Frame结束时只会被调用一次,调用后会被系统移除,在Persistentcallbacks后Window.onDrawFrame回调返回之前执行)
- PaintingBinding绘制库的绑定,主要处理图片缓存
- SemanticsBinding语义化层与Flutterengine的桥梁,主要是辅助功能的底层支持
- RendererBinding渲染树与Flutterengine的桥梁
- WidgetsBindingWidget层与Flutterengine的桥梁
以上是这些Binding的主要作用,在此不做过多赘述,WidgetsFlutterBinding.ensureInitialized()返回的是WidgetsBinding对象,然后马上调用了WidgetsBinding的attachRootWidget(app)方法,将我们的根视图的Widget对象穿进去,我们继续看attachRootWidget方法
voidattachRootWidget(WidgetrootWidget){
_renderViewElement=RenderObjectToWidgetAdapter(
container:renderView,
debugShortDescription:'[root]',
child:rootWidget
).attachToRenderTree(buildOwner,renderViewElement);
}
创建了一个RenderObjectToWidgetAdapter,让后直接调用它的attachToRenderTree方法,BuildOwner是Widgetframework的管理类
RenderObjectToWidgetElementattachToRenderTree(BuildOwnerowner,[RenderObjectToWidgetElement 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(); } returnelement; }
element为空,owner先锁定状态,然后调用了RenderObjectToWidgetAdapter的createElement()返回了RenderObjectToWidgetElement对象,让后将owner赋值给element(assignOwner方法),让后就是owner调用buildScope方法
voidbuildScope(Elementcontext,[VoidCallbackcallback]){
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
voidmount(Elementparent,dynamicnewSlot){
assert(parent==null);
super.mount(parent,newSlot);
_rebuild();
}
首先super.mount(parent,newSlot)调用了RootRenderObjectElement的mount方法(只是判定parent和newSlot都为null),让后又继续向上调用了RenderObjectElement中的mount方法
@override
voidmount(Elementparent,dynamicnewSlot){
super.mount(parent,newSlot);
_renderObject=widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty=false;
}
RenderObjectElement中的mount方法又调用了Element的mount方法
@mustCallSuper
voidmount(Elementparent,dynamicnewSlot){
_parent=parent;
_slot=newSlot;
_depth=_parent!=null?_parent.depth+1:1;
_active=true;
if(parent!=null)//Onlyassignownershipiftheparentisnon-null
_owner=parent.owner;
if(widget.keyisGlobalKey){
finalGlobalKeykey=widget.key;
key._register(this);
}
_updateInheritance();
}
Element的mount方法其实就是进行了一些赋值,以确认当前Element在整个树种的位置,让后回到RenderObjectElement中的mount方法,调用了widget.createRenderObject(this)方法,widget是RenderObjectToWidgetAdapter的实例,它返回的是RenderObjectWithChildMixin对象,让后调用attachRenderObject方法
@override
voidattachRenderObject(dynamicnewSlot){
assert(_ancestorRenderObjectElement==null);
_slot=newSlot;
_ancestorRenderObjectElement=_findAncestorRenderObjectElement();//获取此RenderObjectElement最近的RenderObjectElement对象
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject,newSlot);//将renderObject插入RenderObjectElement中
finalParentDataElementparentDataElement=_findAncestorParentDataElement();
if(parentDataElement!=null)
_updateParentData(parentDataElement.widget);
}
///RenderObjectToWidgetElement中的insertChildRenderObject方法,简单将子RenderObject赋值给父RenderObject的child字段
@override
voidinsertChildRenderObject(RenderObjectchild,dynamicslot){
assert(slot==_rootChildSlot);
assert(renderObject.debugValidateChild(child));
renderObject.child=child;
}
Element的mount方法确定当前Element在整个树种的位置并插入,RenderObjectElement中的mount方法来创建RenderObject对象并将其插入到渲染树中,让后再回到RenderObjectToWidgetElement方法,mount之后调用_rebuild()方法,_rebuild()方法中主要是调用了Element的updateChild方法
@protected
ElementupdateChild(Elementchild,WidgetnewWidget,dynamicnewSlot){
if(newWidget==null){//当子Widget没有的时候,直接将childdeactivate掉
if(child!=null)
deactivateChild(child);
returnnull;
}
if(child!=null){//有子Element的时候
if(child.widget==newWidget){//Widget没有改变
if(child.slot!=newSlot)//再判断slot有没有改变,没有则不更新slot
updateSlotForChild(child,newSlot);//更新child的slot
returnchild;//返回child
}
if(Widget.canUpdate(child.widget,newWidget)){//Widget没有改变,再判断Widget能否update,如果能还是重复上面的步骤
if(child.slot!=newSlot)
updateSlotForChild(child,newSlot);
child.update(newWidget);
returnchild;
}
deactivateChild(child);//如果不能更新的话,直接将childdeactivate掉,然后在inflateWidget(newWidget,newSlot)创建新的Element
}
returninflateWidget(newWidget,newSlot);//根据Widget对象以及slot创建新的Element
}
由于我们是第一次构建,child是null,所以就直接走到inflateWidget方法创建新的Element对象,跟进inflateWidget方法
@protected
ElementinflatinflateWidgeteWidget(WidgetnewWidget,dynamicnewSlot){
finalKeykey=newWidget.key;
if(keyisGlobalKey){//newWidget的key是GlobalKey
finalElementnewChild=_retakeInactiveElement(key,newWidget);//复用Inactive状态的Element
if(newChild!=null){
newChild._activateWithParent(this,newSlot);//activate此Element(将newChild出入到Element树)
finalElementupdatedChild=updateChild(newChild,newWidget,newSlot);//直接将newChild更新
returnupdatedChild;//返回更新后的Element
}
}
finalElementnewChild=newWidget.createElement();//调用createElement()进行创建
newChild.mount(this,newSlot);//继续调用newChildElement的mount方法(如此就行一直递归下去,当递归完成,整个构建过程也就结束了)
returnnewChild;//返回子Element
}
inflateWidget中其实就是通过Widget得到Element对象,让后继续调用子Element的mount的方将进行递归。
不同的Element,mount的实现会有所不同,我们看一下比较常用的StatelessElement、StatefulElement,他们的mount方法实现在ComponentElement中
@override
voidmount(Elementparent,dynamicnewSlot){
super.mount(parent,newSlot);
_firstBuild();
}
void_firstBuild(){
rebuild();//调用了Element的rebuild()方法
}
//Element的rebuild方法,通常被三处地方调用
//1.当BuildOwner.scheduleBuildFor被调用标记此Element为dirty时
//2.当Element第一次构建由mount方法去调用
//3.当Widget改变时,被update方法调用
voidrebuild(){
if(!_active||!_dirty)
return;
performRebuild();//调用performRebuild方法(抽象方法)
}
//ComponentElement的performRebuild实现
@override
voidperformRebuild(){
Widgetbuilt;
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
voidmount(Elementparent,dynamicnewSlot){
super.mount(parent,newSlot);
_children=List(widget.children.length);
ElementpreviousChild;
for(inti=0;i<_children.length;i+=1){
finalElementnewChild=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还要继续创建childrenElement。
总结:首先是由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
voidbuildScope(Elementcontext,[VoidCallbackcallback]){
if(callback==null&&_dirtyElements.isEmpty)
return;
Timeline.startSync('Build',arguments:timelineWhitelistArguments);
try{
_scheduledFlushDirtyElements=true;
if(callback!=null){
ElementdebugPreviousBuildTarget;
_dirtyElementsNeedsResorting=false;
try{
callback();//调用callback
}finally{}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting=false;
intdirtyCount=_dirtyElements.length;
intindex=0;
while(index0&&_dirtyElements[index-1].dirty){
index-=1;
}
}
}
}finally{
for(Elementelementin_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
voiddrawFrame(){
assert(renderView!=null);
pipelineOwner.flushLayout();//布局
pipelineOwner.flushCompositingBits();//刷新dirty的renderobject的数据
pipelineOwner.flushPaint();//绘制
renderView.compositeFrame();//将二进制数据发送给GPU
pipelineOwner.flushSemantics();//将语义发送给系统
}
flushLayout触发布局,将RenderObject树的dirty节点通过调用performLayout方法进行逐一布局,我们先看一下RenderPadding中的实现
@override
voidperformLayout(){
_resolve();//解析padding参数
if(child==null){//如果没有child,直接将constraints与padding综合计算得出自己的size
size=constraints.constrain(Size(
_resolvedPadding.left+_resolvedPadding.right,
_resolvedPadding.top+_resolvedPadding.bottom
));
return;
}
finalBoxConstraintsinnerConstraints=constraints.deflate(_resolvedPadding);//将padding减去,生成新的约束innerConstraints
child.layout(innerConstraints,parentUsesSize:true);//用新的约束去布局child
finalBoxParentDatachildParentData=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
voidperformLayout(){
size=_getSize(constraints);//直接先确定自己的size
inti=0;
_randomAccessChildren.clear();
RenderBoxchild=firstChild;
while(child!=null){//遍历孩子
_randomAccessChildren.add(child);
finalBoxConstraintsinnerConstraints=_delegate.getConstraintsForChild(i,constraints);//获取child的约束,此方法为抽象
child.layout(innerConstraints,parentUsesSize:true);//布局孩子
finalFlowParentDatachildParentData=child.parentData;
childParentData.offset=Offset.zero;
child=childParentData.nextSibling;
i+=1;
}
}
可以看到RenderFlow的size直接就根据约束来确定了,并没去有先布局孩子,所以RenderFlow的size不依赖与孩子,后面依旧是对每一个child依次进行布局。
还有一种比较典型的树尖类型的RenderBox,LeafRenderObjectWidget子类创建的RenderObject对象都是,他们没有孩子,他们才是最终需要渲染的对象,例如
@override
voidperformLayout(){
size=_sizeForConstraints(constraints);
}
非常简单就通过约束确定自己的大小就结束了。所以performLayout过程就是两点,确定自己的大小以及布局孩子。我们上面提到的都是RenderBox的子类,这些RenderObject约束都是通过BoxConstraints来完成,但是RenderSliver的子类的约束是通过SliverConstraints来完成,虽然他们对child的约束方式不同,但他们在布局过程需要执行的操作都是一致的。
绘制
布局完成了,PipelineOwner就通过flushPaint来进行绘制
voidflushPaint(){
try{
finalListdirtyNodes=_nodesNeedingPaint;
_nodesNeedingPaint=[];
//对dirtynodes列表进行排序,最深的在第一位
for(RenderObjectnodeindirtyNodes..sort((RenderObjecta,RenderObjectb)=>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
voidpaint(PaintingContextcontext,Offsetoffset){
if(child!=null)
context.paintChild(child,offset);//直接绘制child
}
自己没有什么绘制的内容,直接绘制child,再看一下RenderShiftedBox
@override
voidpaint(PaintingContextcontext,Offsetoffset){
if(child!=null){
finalBoxParentDatachildParentData=child.parentData;
context.paintChild(child,childParentData.offset+offset);//直接绘制child
}
}
好像没有绘制内容就直接递归的进行绘制child,那找一个有绘制内容的吧,我们看看RenderDecoratedBox
@override
voidpaint(PaintingContextcontext,Offsetoffset){//Offset由parent去paintChild的时候传入,该值存放在child的parentdata字段中,该字段是BoxParentData或以下实例
_painter??=_decoration.createBoxPainter(markNeedsPaint);//获取painter画笔
finalImageConfigurationfilledConfiguration=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
voidpaint(PaintingContextcontext,Offsetoffset){
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,如果没有孩子就只绘制自己的内容,流程比较简单。
以上是自己学习的一些总结,如有错误之处请指出,大家共同探讨,觉得不错的话,点个赞呗!
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。