简书 flutter(flutter开发框架)

1.引言上一篇文章提到过,Widget是描述一个UI元素的配置数据,元素真正代表的是屏幕显示元素,是某个位置的Widget生成的实例。本文主要介绍元素的主要功能。通过上一篇文章介绍的Widget Tree,Flutter框架会生成一系列元素,这些元素构成了元素树,其主要功能如下:
维护这个元素树,根据Widget树的变化更新元素树,包括插入、更新、删除、移动节点;小部件和RenderObject与元素树相关联。2.元素分类
如上图所示,元素按照功能可以分为两类:
ComponentElement组合类元素。这类元素主要用于组合其他更基本的元素,得到更复杂的元素。开发中经常用到的StatelessWidget和StatefulWidget: Stateless元素和StatefulElement对应的元素都属于ComponentElement。
RenderObjectElement渲染类元素,对应RenderWidget,是框架的核心元素。RenderObjectElement主要包括LeafRenderObjectElement、SingleChildRenderObjectElement和MultiChildRenderObjectElement。其中,LeafRenderObjectElement对应的Widget是LeafRenderObjectWidget,没有子节点;SingleChildRenderObjectElement对应的小部件是SingleChildRenderObjectWidget,它有一个子节点;与childrenrobjectelement对应的小部件是childrenrobjectget,它有多个子节点。3.元素生命周期元素有四种状态:初始、活动、非活动和失效。其对应的含义如下:
Initial:初始状态,即首次创建元素时的状态。活动:活动状态。此时,元素的父元素已经通过mount将元素插入到元素树的指定槽中,元素可以随时显示在屏幕上。非活动:非活动状态。当widget树发生变化时,元素对应的Widget发生变化,由于新Widget和旧Widget的Key或RunTimeType不匹配,该元素也被移除,因此该元素变为非活动状态并从屏幕中移除。并且从元素树中移除该元素,并且如果该元素具有相应的RenderObject,则相应的RenderObject也将从呈现树中移除。然而,这个元素仍然有机会被重用,例如,通过GlobalKey。不存在:无效状态。如果一个非活动元素在当前帧动画结束时没有被重用,那么将调用该元素的unmount函数,该元素的状态将被更改为defunct,并且其中的资源将被清理。元素4的四种状态之间的转换关系如下图所示:
4.4之间的关系。ComponentElement4.1及核心元素如上所述,ComponentElement可分为StatefulElement和StatefulElement。下图显示了这两个元素与核心元素小部件和状态之间的关系。
如图所示:
ComponentElement保存父元素和子元素,从而构成元素树。ComponentElement持有其对应的Widget,对于StatefulElement也持有对应的状态,从而实现元素与Widget的绑定。状态由StatefulElement保存,而不是StatefulWidget,这有利于状态的重用。实际上,State和StatefulElement是一一对应的,只有在StatefulElement初始化的时候,对应的State才会被初始化并绑定到StatefulElement。4.2核心流程元素的核心操作流程包括创建、更新和销毁。下面将分别介绍这三个过程。
ComponentElement的创建起源和父Widget调用inflateWidget,然后通过mount把元素挂载到元素树上,递归创建子节点。
更新父元素执行更新子节点的操作(updateChild)。因为新旧小部件的类型和键都没有改变,所以元素的更新操作被触发,更新操作通过performRebuild传递。其核心功能updateChild将在后面详细介绍。
销毁由父元素或更上级的节点执行更新子节点的操作(updateChild),由于新旧小部件的类型或者钥匙发生变化,或者新小部件被移除,因此导致该元素被转为未激活状态,并被加入未激活列表,并在下一帧被失效。
4.3 核心函数下面对组件元素中的核心方法进行介绍。
inflateWidgetElement inflateWidget(Widget new Widget,dynamic newSlot){ final Key Key=new Widget。关键;//复用GlobalKey对应的元素if(键为global key){ final Element new child=_ retreateinactiveelement(key,new widget);如果(newChild!=null) { newChild ._ activatewithpparent(this,newSlot);最后的元素updated child=update child(new child,newWidget,newSlot);返回updatedChild} }//创建元素,并挂载至元素树最终元素新的子组件=新的小部件。createelement();newChild.mount(this,newSlot);返回newChild}通货膨胀宽度的主要职责如下:
判断新小部件是否有GlobalKey,如果有GlobalKey,则从非活动元素列表中找到对应的元素并进行复用。无可复用元素,则根据新小部件创建对应的元素,并将其挂载至元素树。mountvoid安装(元素父级,动态新闻槽){//更新_parent等属性,将元素加入元素tree _ parent=parent _ slot=newSlot _ depth=_ parent!=null _ parent . depth 1 : 1 _ active=true如果(家长!=null) //仅在父级为非null _owner=父代.所有者时分配所有权;//注册全局键最终键Key=widget。关键;如果(键是GlobalKey) { key ._注册(此);} _更新继承();}当元素第一次被插入元素树的时候,该方法被调用。其主要职责如下:
将给元素加入元素树,更新_父,_插槽等树相关的属性。如果新小部件有GlobalKey,将该元素注册进GlobalKey中,其作用下文会详细分析组件元素的增加函数会调用_firstBuild函数,触发子小部件的创建和更新perform rebuild @ override void perform rebuild(){//调用体格函数,生成子Widget小部件内置;build=build();//根据新的子小部件更新子element _ child=update child(_ child,built,slot);}执行重建的主要职责如下:
调用体格函数,生成子小工具。根据新的子小部件更新子元素。update@mustCallSupervoid更新(协变Widget新Widget){ _ Widget=新Widget;}此函数主要职责为:
将对应的小部件更新为新的小工具。在组件元素的各种子类中,还会调用重建函数触发对子小部件的重建。update child @ protected Element update child(Element child,Widget newWidget,dynamic newSlot){ if(new Widget==null){//新的子部件为空,则返回空如果旧子部件,使其未激活如果(孩子!=null)停用子级(子级);返回null}元素newChild如果(孩子!=null) {//新的子部件不为空,旧的子部件也不为null bool hassameperclass=true if(hassameperclass子级。widget==new widget){ if(child。槽!=newSlot)updateSlotForChild(child,newSlot);new child=child } else if(hassameperclass小部件。可以更新(子。widget,newWidget)){//Key和运行时间类型相同,使用更新更新if (child.slot!=newSlot)updateSlotForChild(child,newSlot);孩子。更新(新的widget);newChild=child} else {//Key或运行时间类型不相同,使旧的子部件未激活,并对新的子部件使用膨胀小部件停用子部件(子部件);new child=inflate widget(新widget,newSlot);} } else {//新的子部件不为空,旧的子部件为空,对新的子部件使用inflate widget new child=inflate widget(新widget,newSlot);}返回newChild}该方法的主要职责为:根据新的子小部件,更新旧的子元素,或者得到新的子元素。其核心逻辑可以用表格表示:
newWidget==null
newWidget!=空
Child==null
返回空
返回新元素
孩子!=空
移除旧的子元素并返回null。
如果Widget可以更新,更新旧的子元素并返回;否则,创建一个新的子元素并返回。
逻辑总结如下:
如果newWidget为null,则返回null,如果有旧的子元素,则将其删除。如果newWidget不为null,旧的子元素为null,则创建一个新的子元素并返回它。如果newWidget不为null,old Child不为null,并且新旧子Widget的Key和RuntimeType相同,则调用update方法更新子元素并返回它。如果newWidget不为null,old Child不为null,并且新旧子Widget的Key和RuntimeType不完全相同,则意味着Widget树发生了变化。此时,移除旧的子元素,创建新的子元素,并返回它。5.5之间的关系。RenderObjectElement5.1和核心元素RenderObjectElement和核心元素小部件与RenderObject的关系如下图所示:
如图所示:
RenderObjectElement保存父元素,但不一定保存子元素。它可能没有子元素、一个子元素(子元素)或多个子元素(子元素)。RenderObjectElement持有对应的Widget和RenderObject,将它们串联起来,实现Widget、Element和RenderObject之间的绑定。5.2核心流程和ComponentElement一样,RenderObjectElement的核心操作流程包括三种:创建、更新和销毁。接下来将详细介绍这三个过程。
创建RenderObjectElement的过程与ComponentElement基本相同。最大的区别是ComponentElement挂载后调用build创建子Widget,而RenderObjectElement是create并附加其RenderObject。
更新RenderObjectElement的更新过程与ComponentElement基本相同。最大的区别是ComponentElement的update函数调用build函数再次触发子Widget的构造,RenderObjectElement调用updateRenderObject更新绑定的RenderObject。
销毁RenderObjectElement的过程与ComponentElement基本相同。也是由父元素或更高节点执行的updateChild导致该元素被停用,被添加到非活动列表,并在下一帧中失效。不同的是,在卸载Element时,会调用didUnmountRenderObject失效对应的RenderObject。
5.3核心函数下面介绍RenderObjectElement中的核心方法。
InflateWidget该函数与ComponentElement的inflateWidget函数完全一致,此处不再赘述。
mountvoid mount(Element parent,dynamic newSlot){ super . mount(parent,newSlot);_ render object=widget . create renderObject(this);attachRenderObject(newSlot);_ dirty=false}该函数的调用时机与ComponentElement一致。当元素第一次插入到元素树中时,调用此方法。其主要职责与ComponentElement相同,这里只列出不同的职责。责任如下:
调用createRenderObject创建RenderObject,并使用attachRenderObject将RenderObject与元素相关联。SingleChildRenderObjectElement调用updateChild更新子节点,MultiChildRenderObjectElement调用每个子节点的inflateWidget重新构建所有子小部件。perform rebuild @ override void perform rebuild(){//Update renderobject widget . updaterenderobject(this,render object);_ dirty=false}performRebuild的主要职责如下:调用updateRenderObject更新对应的RenderObject。
update@overridevoid update(协变RenderObjectWidget new widget){ super . update(new widget);widget.updateRenderObject(this,render object);_ dirty=false}更新的主要职责如下:
将相应的小部件更新为新的小部件。2)调用updateRenderObject更新相应的RenderObject。
UpdateChild函数与ComponentElement的inflateWidget函数完全相同,此处不再赘述。
update children @ protected List update children(List old children,List newWidgets,{ Set forgotten children }){ int new children top=0;int oldChildrenTop=0;int newChildrenBottom=新的小部件。长度-1;int oldChildrenBottom=老孩子。长度-1;最终列表新孩子=老孩子。长度==新部件。岁儿童长度列表(新部件。长度);元素前一个孩子//从顶部向下更新子元素//更新列表的顶部while((oldChildrenTop=oldChildrenBottom)(newChildrenTop=newChildrenBottom)){ final Element old children=replacewithnullifforgetten(old children top));final Widget new Widget=new widgets[new children top];if (oldChild==null ||!小部件。可以更新(老小孩。widget,新widget))break;最后的元素newChild=updateChild(oldChild,newWidget,IndexedSlot(newChildrenTop,previous child));新的孩子[新的孩子顶端]=新的孩子;previous child=new child new children top=1;oldChildrenTop=1;}//从底部向上扫描子元素//扫描列表底部while((oldChildrenTop=oldChildrenBottom)(newChildrenTop=newChildrenBottom)){ final Element old children=replaceWithNullIfForgotten(old childrenbottom));final Widget new Widget=new widgets[newChildrenBottom];if (oldChild==null ||!小部件。可以更新(老小孩。widget,新widget))break;oldChildrenBottom-=1;newChildrenBottom-=1;}//扫描旧的子元素列表里面中间的子元素,保存小部件有钥匙的元素到老掉牙的孩子,其他的失效//扫描列表中间的老孩子final bool有old children=old children top=old children bottom;映射oldKeyedChildrenif(有老子女){ oldkeyedchilds={ };while(oldChildrenTop=oldChildrenBottom){ final Element old child=replaceWithNullIfForgotten(old children top]);如果(oldChild!=null) { if (oldChild.widget.key!=null)oldkeyedchilds[旧孩子。小部件。key]=老小孩;否则停用孩子(旧孩子);} oldChildrenTop=1;} }//根据小部件的钥匙更新旧钥匙儿童中的元素.//更新列表中间while(newChildrenTop=newChildrenBottom){ Element old child;final Widget new Widget=new widgets[new children top];if(有老孩子){ final Key Key=new widget。关键;如果(关键!=null){ old child=oldkeyedchilds[key];如果(oldChild!=null){ if(widget。可以更新(老小孩。widget,newWidget)) { //我们找到匹配了!//从旧钥匙儿童中移除它,这样我们以后就不会取消同步它oldkeyedchilders。移除(键);} else { //不匹配,暂时就当没看到吧oldChild=null } } } } final元素newChild=updateChild(oldChild,newWidget,IndexedSlot(newChildrenTop,previous child));新的孩子[新的孩子顶端]=新的孩子;previous child=new child new children top=1;} newChildrenBottom=新的小部件。长度-1;oldChildrenBottom=大孩子。长度-1;//从下到上更新底部的元素.while((oldChildrenTop=oldChildrenBottom)(newChildrenTop=newChildrenBottom)){ final Element old children=old children top];final Widget new Widget=new widgets[new children top];最后的元素newChild=updateChild(oldChild,newWidget,IndexedSlot(newChildrenTop,previous child));新的孩子[新的孩子顶端]=新的孩子;previous child=new child new children top=1;oldChildrenTop=1;}//清除旧子元素列表中其他所有剩余Element //从旧列表中清除任何剩余的中间节点如果(有老孩子oldkeyedchilds。isnotempty){ for(oldkeyedchilds。价值观念中的最后一个元素老小孩){ if(被遗忘的孩子==null | |!被遗忘的孩子。包含(旧孩子))停用孩子(旧孩子);} }返回newChildren}该函数的主要职责如下:
重用可重用的子节点,并调用updateChild来更新子节点。对于无法更新的子节点,调用deactivateChild使子节点失效。步骤如下:
自上而下更新子元素。自下而上扫描子元素。扫描旧子元素列表中中间的子元素,将Widget中带Key的元素保存到oldKeyChildren,其他无效。对于新子元素列表,如果其对应小部件的键与oldKeyChildren中的键相同,则更新oldKeyChildren中的元素。从下到上更新底部的元素。清除旧的子元素列表中所有其他剩余的元素。6.本文主要介绍了元素的相关知识,重点介绍了元素的分类、生命周期和核心功能。要点如下:
维护元素树,根据Widget树的变化更新元素树,包括插入、更新、删除、移动节点;并充当链接,将小部件和RenderObject关联到元素树。元素ComponentElement和RenderObjectElement,前者负责组合子元素,后者负责渲染。元素的主要复用和更新逻辑是通过其核心函数updateChild实现的。具体逻辑见上。7.

其他教程

premiereprocs6教程视频(premiere cs6配置要求)

2022-9-6 8:57:26

其他教程

Rotato for mac(3D原型宣传视频制作软件)

2022-9-6 8:59:29

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索