FlutterWidget原理解读(一)

发布网友 发布时间: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

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com