您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關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,如果沒有孩子就只繪制自己的內容,流程比較簡單。
感謝各位的閱讀!關于“Flutter如何構建、布局及繪制三部曲”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。