Flutter構建、布局、繪制三部曲

學習Fullter也有些時間了,寫過不少demo,對一些常用的widget使用也比較熟練,但是總覺得對Flutter的框架沒有一個大致的了解,碰到有些細節(jié)的地方又沒有文檔可以查詢,例如在寫UI時總不知道為什么container添加了child就變小了;widget中key的作用,雖然官方有解釋但是憑空來講的話有點難理解。所以覺得深入一點的了解Flutter框架還是很有必要的。

構建

初次構建

flutter的入口main方法直接調用了runApp(Widget app)方法,app參數(shù)就是我們的根視圖的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手勢子系統(tǒng)的綁定,處理用戶輸入事件
  • ServicesBinding 接受平臺的消息將他們轉換成二進制消息,用于平臺與flutter的通信
  • SchedulerBinding 調度系統(tǒng),用于調用Transient callbacks(Window.onBeginFrame的回調)、Persistent callbacks(Window.onDrawFrame的回調)、Post-frame callbacks(在Frame結束時只會被調用一次,調用后會被系統(tǒng)移除,在Persistent callbacks后Window.onDrawFrame回調返回之前執(zhí)行)
  • PaintingBinding 繪制庫的綁定,主要處理圖片緩存
  • SemanticsBinding 語義化層與Flutter engine的橋梁,主要是輔助功能的底層支持
  • RendererBinding 渲染樹與Flutter engine的橋梁
  • WidgetsBinding Widget層與Flutter engine的橋梁

以上是這些Binding的主要作用,在此不做過多贅述,WidgetsFlutterBinding.ensureInitialized()返回的是WidgetsBinding對象,然后馬上調用了WidgetsBinding的attachRootWidget(app)方法,將我們的根視圖的Widget對象穿進去,我們繼續(xù)看attachRootWidget方法

void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget
    ).attachToRenderTree(buildOwner, renderViewElement);
}

創(chuàng)建了一個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先鎖定狀態(tài),然后調用了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 {}
      }
      ...
  }

省略了部分以及后續(xù)代碼,可以看到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),讓后又繼續(xù)向上調用了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方法來創(chuàng)建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)創(chuàng)建新的Element
  }
  return inflateWidget(newWidget, newSlot);//根據(jù)Widget對象以及slot創(chuàng)建新的Element
}

由于我們是第一次構建,child是null,所以就直接走到inflateWidget方法創(chuàng)建新的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狀態(tài)的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()進行創(chuàng)建
  newChild.mount(this, newSlot);//繼續(xù)調用newChild Element的mount方法(如此就行一直遞歸下去,當遞歸完成,整個構建過程也就結束了)
  return newChild;//返回子Element
}

inflateWidget中其實就是通過Widget得到Element對象,讓后繼續(xù)調用子Element的mount的方將進行遞歸。

不同的Element,mount的實現(xiàn)會有所不同,我們看一下比較常用的StatelessElement、StatefulElement,他們的mount方法實現(xiàn)在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實現(xiàn)
@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));//有錯誤的化就創(chuàng)建一個ErrorWidget
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);//讓后還是根據(jù)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根據(jù)Widget創(chuàng)建新的Element
    _children[i] = newChild;
    previousChild = newChild;
  }
}

可以看到不同的Element構建方式會有些不同,Element(第一層Element)的mount方法主要是確定當前Element在整個樹種的位置并插入;ComponentElement(第二層)的mount方法先構建Widget樹,讓后再遞歸更新(包括重用,更新,直接創(chuàng)建inflate)其Element樹;RenderObjectElement(第二層)中的mount方法來創(chuàng)建RenderObject對象并將其插入到渲染樹中。MultiChildRenderObjectElement(RenderObjectElement的子類)在RenderObjectElement還要繼續(xù)創(chuàng)建children Element。

總結:首先是由WidgetBinding創(chuàng)建RenderObjectToWidgetAdapter然后調用它的attachToRenderTree方法,創(chuàng)建了RenderObjectToWidgetElement對象,讓后將它mount(調用mount方法),mount方法中調用的_rebuild,繼而調用updateChild方法,updateChild會進行遞歸的更新Element樹,若child沒有則需要重新創(chuàng)建新的Element,讓后將其mount進Element樹中(如果是RenderobjectElement的化,mount的過程中會去創(chuàng)建RenderObject對象,并插入到RenderTree)。

通過setState觸發(fā)構建

通常我們在應用中要更新狀態(tài)都是通過State中的setState方法來觸發(fā)界面重繪,setState方法就是先調用了callback讓后調用該State的Element對象的markNeedsBuild方法,markNeedsBuild中將Element標記為dirty并通過BuildOwner將其添加到dirty列表中并調用onBuildScheduled回調(在WidgetsBinding初始化時設置的,它回去調用window.scheduleFrame方法),讓后window的onBeginFrame,onDrawFrame回調(在SchedulerBinding初始化時設置的,這兩個回調會執(zhí)行一些callback)會被調用,SchedulerBinding通過persisterCallbacks來調用到BuildOwner中buildScope方法。上面我們只看了buildScope的一部分,當通過setState方法來觸發(fā)界面重繪時,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并執(zhí)行他們的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來執(zhí)行handleBeginFrame方法(執(zhí)行transientCallbacks)和handleDrawFrame方法(執(zhí)行persistentCallbacks,postFrameCallbacks),在RendererBinding初始化時添加了_handlePersistentFrameCallback,它調用了核心的繪制方法drawFrame。

@protected
void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();//布局
  pipelineOwner.flushCompositingBits();//刷新dirty的renderobject的數(shù)據(jù)
  pipelineOwner.flushPaint();//繪制
  renderView.compositeFrame(); // 將二進制數(shù)據(jù)發(fā)送給GPU
  pipelineOwner.flushSemantics(); // 將語義發(fā)送給系統(tǒng)
}

flushLayout觸發(fā)布局,將RenderObject樹的dirty節(jié)點通過調用performLayout方法進行逐一布局,我們先看一下RenderPadding中的實現(xiàn)

@override
void performLayout() {
  _resolve();//解析padding參數(shù)
  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值
  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來確定自己的大?。环駝t就先去布局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直接就根據(jù)約束來確定了,并沒去有先布局孩子,所以RenderFlow的size不依賴與孩子,后面依舊是對每一個child依次進行布局。

還有一種比較典型的樹尖類型的RenderBox,LeafRenderObjectWidget子類創(chuàng)建的RenderObject對象都是,他們沒有孩子,他們才是最終需要渲染的對象,例如

@override
void performLayout() {
  size = _sizeForConstraints(constraints);
}

非常簡單就通過約束確定自己的大小就結束了。所以performLayout過程就是兩點,確定自己的大小以及布局孩子。我們上面提到的都是RenderBox的子類,這些RenderObject約束都是通過BoxConstraints來完成,但是RenderSliver的子類的約束是通過SliverConstraints來完成,雖然他們對child的約束方式不同,但他們在布局過程需要執(zhí)行的操作都是一致的。

繪制

布局完成了,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) {
  _painter ??= _decoration.createBoxPainter(markNeedsPaint);//獲取painter畫筆
  final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
  if (position == DecorationPosition.background) {//畫背景
    _painter.paint(context.canvas, offset, filledConfiguration);//繪制過程,具體細節(jié)再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方法中的實現(xiàn)就應該包括繪制自己以及繪制child,如果沒有孩子就只繪制自己的內容,看一下RenderImage

@override
void paint(PaintingContext context, Offset offset) {
  if (_image == null)
    return;
  _resolve();
  paintImage(//直接繪制Image,具體細節(jié)再此方法中
    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方法中的實現(xiàn)就應該包括繪制自己以及繪制child,如果沒有孩子就只繪制自己的內容,流程比較簡單。

以上是自己學習的一些總結,如有錯誤之處請指出,大家共同探討,覺得不錯的話,點個贊唄!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容