发布网友 发布时间:2024-09-28 12:05
共1个回答
热心网友 时间:2024-09-30 20:20
前言使用过Flutter的同学,应该都听过一句话“everythingisawidget——在Flutter中万物皆是Widget”。
虽然不能说在Flutter开发中所有代码模块都是一个Widget,但足以说明Widget在Flutter中的重要性,本篇文章就重点关于FlutterWidget的原理进行解读。
Widget简介什么是Widget?我们先看一下官方的描述
“==Describestheconfigurationforan[Element]==”
在Flutter中,Widget的功能是“描述一个UI元素的配置数据”。
这句话很简单,如何理解呢?暂时可以简单的理解,FLutter最终绘制在设备上的显示元素,都是通过Widget配置出来的。
在web前端开发中,我们知道浏览器页面由HTML+CSS+JS配置而成,其中HTML负责配置UI结构,CSS负责配置UI样式,JS负责UI的交互。
而在Flutter中,无论是UI结构,还是UI样式,再到UI交互都是通过Widget完成。例如:
Widget树结构配置UI结构
样式Widget,Padding、Color等
交互Widget,GestureDetector等
Widget分类在Flutter中,官方提供的原生Widget多达300+,这么多Widget,在基础原理层面是如何分类的呢?
使用过Flutter的同学,最熟悉的应该是StatelessWidget和StatefulWidget两种Widget,除了这两种还要其他的吗?
我们来看一下FlutterWidget组件继承图。
从上图中,我们知道继承Widget基类四个子类分别是
StatelessWidget
StatefulWidget
RenderObjectWidget
ProxyWidget
其中前三类StatelessWidget、StatefulWidget、RenderObjectWidget负责UI渲染配置,而ProxyWidget继承的子类InheritedWidget负责Widget树向下传递数据。
如果按照功能来分类,则可分成两大类:
UI渲染配置Widget:StatelessWidget、StatefulWidget、RenderObjectWidget
UI树数据状态管理Widget:InheritedWidget
StatelessWidget、StatefulWidget、RenderObjectWidget又可依据UI配置类型Widget,分成两类:
组合Widget:StatelessWidget、StatefulWidget
自定义渲染Widget:RenderObjectWidget
接下来,本篇文章主要讲解UI配置类型Widget,UI树数据状态管理Widget——InheritedWidget,将在下一篇文章中讲解。
组合Widget自定义渲染Widget区别?在日常业务开发中,开发者只需要使用组合Widget就能满足99%的业务功能,所以对于初学Flutter的同学来说,学会StatelessWidget与StatefulWidget的使用就能满足业务开发需求。
组合Widget与自定义渲染Widget有什么区别呢?
站在前端的角度,我们开发一个HTML页面,只需要使用W3C定义的标准的div、span等标签和css样式position、color等即可搭建一个完整的页面。
至于div、color浏览器最终是如何渲染的,无需开发者定义实现,全权由浏览器引擎原生实现。开发者基于div+css开发的组件都属于组合组件,等同于组合Widget。
那什么是自定义渲染Widget呢?就好比,浏览器未支持css3之前,如果要实现边框圆角样式“border-radius”使用css是做不到的。假如浏览器提供前端开发者自定义css样式渲染的接口,由前端开发者实现边框圆角的css渲染,则属于自定义渲染组件,等同于与自定义渲染Widget。
组合Widget,StatelessWidget与StatefulWidget我们先看看,源码抽象类的定义
StatelessWidget源码
abstractclassStatelessWidgetextendsWidget{constStatelessWidget({Key?key}):super(key:key);@overrideStatelessElementcreateElement()=>StatelessElement(this);@protectedWidgetbuild(BuildContextcontext);}StatefulWidget
abstractclassStatefulWidgetextendsWidget{constStatefulWidget({Key?key}):super(key:key);@overrideStatefulElementcreateElement()=>StatefulElement(this);@protected@factoryStatecreateState();//ignore:}从源代码我们可以看出,StatelessWidget是一个无状态组件,提供一个组件构建函数build。StatefulWidget是一个有状态组件,提供一个状态创建函数createState。
接下来看看StatefulWidget类中依赖State类的源码
abstractclassState<TextendsStatefulWidget>withDiagnosticable{Tgetwidget=>_widget!;T?_widget;BuildContextgetcontext{assert((){if(_element==null){throwFlutterError('Thiswidgethasbeenunmounted,sotheStatenolongerhasacontext(andshouldbeconsidereddefunct).\n''Considercancelinganyactiveworkring"dispose"orusingthe"mounted"gettertodetermineiftheStateisstillactive.',);}returntrue;}());return_element!;}StatefulElement?_element;boolgetmounted=>_element!=null;@protected@mustCallSupervoidinitState(){}@mustCallSuper@protectedvoiddidUpdateWidget(covariantToldWidget){}@protected@mustCallSupervoidreassemble(){}@protectedvoidsetState(VoidCallbackfn){_element!.markNeedsBuild();}@protected@mustCallSupervoiddeactivate(){}@protected@mustCallSupervoidactivate(){}@protected@mustCallSupervoiddispose(){}从上面代码中可以看出,State是一个有状态的组件,有生命周期钩子函数initState、dispose等和状态改变函数setState。
@protectedvoidsetState(VoidCallbackfn){_element!.markNeedsBuild();}从从setState源码定义可以知道,setState会触发组件重渲染函数markNeedsBuild。
从源码对比来看StatelessWidget实现非常简单,连组件生命周期的钩子函数都没有,而StatefullWidget则相对复杂许多。
有不少生命周期钩子函数
有状态存储对象
有修改状态对象的函数setState
如果用React组件类比,则StatelessWidget相当于纯函数组件,而StatefullWidget则是类组件。
且StatelessWidget和StatefullWidget使用场景也跟React纯函数组件和类组件使用场景相同,在此不做赘述。
StatefulWidget生命周期流程图
重点关注如下生命周期钩子:
initState():widget第一次插入widget树调用,此时还没有触发build函数,且整个state生命周期只调用一次
didUpdateWidget():当State对象的状态发生变化时,重新build之前调用,一般在这里判断哪些状态变化是需要触发哪些业务函数时调用。
dispose():当State对象从树中被永久移除时调用,通常在此回调中释放资源。
自定义渲染Widget——RenderObjectWidget我们先看看RenderObjectWidget子类继承关系图。
从上图可以得知RenderObjectWidget分成三类:
LeafRenderObjectWidget
SingleChildRenderObjectWidget
MultiChildRenderObjectWidget
Flutter原生基础布局组件都是通过继承SingleChildRenderObjectWidget或MultiChildRenderObjectWidget实现。
接下来我们看看源码实现:
RenderObjectWidget
abstractclassRenderObjectWidgetextendsWidget{provideconstRenderObjectWidget({Key?key}):super(key:key);@override@factoryRenderObjectElementcreateElement();@protected@factoryRenderObjectcreateRenderObject(BuildContextcontext);@protectedvoipdateRenderObject(BuildContextcontext,covariantRenderObjectrenderObject){}@protectedvoiddidUnmountRenderObject(covariantRenderObjectrenderObject){}}LeafRenderObjectWidget
abstractclassLeafRenderObjectWidgetextendsRenderObjectWidget{provideconstLeafRenderObjectWidget({Key?key}):super(key:key);@overrideLeafRenderObjectElementcreateElement()=>LeafRenderObjectElement(this);}SingleChildRenderObjectWidget
abstractclassSingleChildRenderObjectWidgetextendsRenderObjectWidget{provideconstSingleChildRenderObjectWidget({Key?key,this.child}):super(key:key);finalWidget?child;@overrideSingleChildRenderObjectElementcreateElement()=>SingleChildRenderObjectElement(this);}MultiChildRenderObjectWidget
abstractclassMultiChildRenderObjectWidgetextendsRenderObjectWidget{MultiChildRenderObjectWidget({Key?key,this.children=const<Widget>[]})}finalList<Widget>children;@overrideMultiChildRenderObjectElementcreateElement()=>MultiChildRenderObjectElement(this);}从源码可以看出LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget处理RenderObjectWidget个数有差异。
SingleChildRenderObjectWidget:处理单个RenderObjectWidget。
MultiChildRenderObjectWidget:处理多个RenderObjectWidget。
LeafRenderObjectWidget:叶子渲染Widget,处理没有children的RenderObjectWidget。
而继承RenderObjectWidget的自定义子类最重要是需要实现抽象函数createRenderObject、updateRenderObject,对应创建、更新
拿Padding原生Widget源码实现距离。
classPaddingextendsSingleChildRenderObjectWidget{///Createsawidgetthatinsetsitschild.//////The[padding]argumentmustnotbenull.constPadding({Key?key,requiredthis.padding,Widget?child,}):assert(padding!=null),super(key:key,child:child);///Theamountofspacebywhichtoinsetthechild.finalEdgeInsetsGeometrypadding;@overrideRenderPaddingcreateRenderObject(BuildContextcontext){returnRenderPadding(padding:padding,textDirection:Directionality.maybeOf(context),);}@overridevoipdateRenderObject(BuildContextcontext,RenderPaddingrenderObject){renderObject..padding=padding..textDirection=Directionality.maybeOf(context);}@overridevoiddebugFillProperties(DiagnosticPropertiesBuilderproperties){super.debugFillProperties(properties);properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding',padding));}}从源码实现来看,传入PaddingWidget的子Widget直接传递到父SingleChildRenderObjectWidgetchild,而Padding只是实现Widget容器布局RenderPaddingcreateRenderObject(BuildContextcontext),具体实现需要看RenderPadding实现源码,如下:
classRenderPaddingextendsRenderShiftedBox{///Createsarenderobjectthatinsetsitschild.//////The[padding]argumentmustnotbenullandmusthavenon-negativeinsets.RenderPadding({requiredEdgeInsetsGeometrypadding,TextDirection?textDirection,RenderBox?child,}):assert(padding!=null),assert(padding.isNonNegative),_textDirection=textDirection,_padding=padding,super(child);EdgeInsets?_resolvedPadding;void_resolve(){if(_resolvedPadding!=null)return;_resolvedPadding=padding.resolve(textDirection);assert(_resolvedPadding!.isNonNegative);}void_markNeedResolution(){_resolvedPadding=null;markNeedsLayout();}///Theamounttopadthechildineachdimension.//////Ifthisissettoan[EdgeInsetsDirectional]object,then[textDirection]///mustnotbenull.EdgeInsetsGeometrygetpadding=>_padding;EdgeInsetsGeometry_padding;setpadding(EdgeInsetsGeometryvalue){assert(value!=null);assert(value.isNonNegative);if(_padding==value)return;_padding=value;_markNeedResolution();}///Thetextdirectionwithwhichtoresolve[padding].//////Thismaybechangedtonull,butonlyafterthe[padding]hasbeenchanged///toavaluethatdoesnotdependonthedirection.TextDirection?gettextDirection=>_textDirection;TextDirection?_textDirection;settextDirection(TextDirection?value){if(_textDirection==value)return;_textDirection=value;_markNeedResolution();}@overridedoublecomputeMinIntrinsicWidth(doubleheight){_resolve();finaldoubletotalHorizontalPadding=_resolvedPadding!.left+_resolvedPadding!.right;finaldoubletotalVerticalPadding=_resolvedPadding!.top+_resolvedPadding!.bottom;if(child!=null)//nextlinereliesondouble.infinityabsorptionreturnchild!.getMinIntrinsicWidth(math.max(0.0,height-totalVerticalPadding))+totalHorizontalPadding;returntotalHorizontalPadding;}@overridedoublecomputeMaxIntrinsicWidth(doubleheight){_resolve();finaldoubletotalHorizontalPadding=_resolvedPadding!.left+_resolvedPadding!.right;finaldoubletotalVerticalPadding=_resolvedPadding!.top+_resolvedPadding!.bottom;if(child!=null)//nextlinereliesondouble.infinityabsorptionreturnchild!.getMaxIntrinsicWidth(math.max(0.0,height-totalVerticalPadding))+totalHorizontalPadding;returntotalHorizontalPadding;}@overridedoublecomputeMinIntrinsicHeight(doublewidth){_resolve();finaldoubletotalHorizontalPadding=_resolvedPadding!.left+_resolvedPadding!.right;finaldoubletotalVerticalPadding=_resolvedPadding!.top+_resolvedPadding!.bottom;if(child!=null)//nextlinereliesondouble.infinityabsorptionreturnchild!.getMinIntrinsicHeight(math.max(0.0,width-totalHorizontalPadding))+totalVerticalPadding;returntotalVerticalPadding;}@overridedoublecomputeMaxIntrinsicHeight(doublewidth){_resolve();finaldoubletotalHorizontalPadding=_resolvedPadding!.left+_resolvedPadding!.right;finaldoubletotalVerticalPadding=_resolvedPadding!.top+_resolvedPadding!.bottom;if(child!=null)//nextlinereliesondouble.infinityabsorptionreturnchild!.getMaxIntrinsicHeight(math.max(0.0,width-totalHorizontalPadding))+totalVerticalP