成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Flutter中的布局繪制流程簡析(二)

icattlecoder / 1060人閱讀

摘要:所以這里為時把指向自身,因為自身的肯定符合約束的條件,也是提高布局效率的一個關(guān)鍵點。舉一個栗子,在中先讓布局之后,根據(jù)的,來設(shè)置自身的。意味著父控件要依賴子控件的,可能父控件的布局要根據(jù)子控件的來做調(diào)整。

布局約束

剛才所說的改變一個控件的高度,有時候并不像剛才所說只是改變一下屬性就能起作用,這里涉及到一個布局約束規(guī)則。
直接看BoxConstraints的實現(xiàn),這個類主要定義了minWidth和maxWidth,minHeight和maxHeight這些約束條件,child布局的時候可以根據(jù)parent給予的這些條件進行對應(yīng)的布局。
簡單介紹相關(guān)的一些術(shù)語:

tightly,如果最小約束(minWidth)和最大約束(maxWidth)都是一樣的

loose,如果最小約束是0.0(不管最大約束);如果最小約束和最大約束都是0.0,就同時是tightly和loose

bounded,如果最大約束不是infinite

unbounded,如果最大約束是infinite

expanding,如果最小約束和最大約束都是infinite

如果一個size滿足BoxConstraints的約束,那么它就是constrained的。

既然是parent傳遞給child的約束條件,當然是在performLayout的時候調(diào)起child.layout方法:

void layout(Constraints constraints, { bool parentUsesSize: false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
    if (!_needsLayout && constraints == _constraints 
    && relayoutBoundary == _relayoutBoundary) {    
      return;
    }
    _constraints = constraints;
    _relayoutBoundary = relayoutBoundary;
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException("performResize", e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
      assert(() { debugAssertDoesMeetConstraints(); return true; }());
    } catch (e, stack) {
      _debugReportException("performLayout", e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
  }

開始分析這段代碼前一小半,決定relayoutBoundary的值也就是布局邊界,一個RenderObject想要重新布局,應(yīng)該從哪里開始。
parentUsesSize,如果為false也就是,parent的布局并不需要依賴child的布局結(jié)果,那么child如果要重新布局并不需要通知parent,布局的邊界就是自身了,而parentUsesSize的默認值也是為false,也就是大部分時候也只需自身重新布局。
parentUsesSize,如果為true就是parent的布局要依賴child布局(或者parent的size依賴于child的size,想象一下兩個div嵌套的情況),再看如果sizedByParent和constraints.isTight都為false,在這種情況之下relayoutBoundary要指向parent.relayoutBoundary,也就是說child如果要重新布局,必須從relayoutBoundary開始,在RenderObject.markNeedsLayout方法實現(xiàn)里面,最終只會把relayoutBoundary加入到_nodesNeedingLayout列表中,跟isRepaintBoundary處理是幾乎一樣的;
但是如果constraints.isTight為true,也就是minWidth和maxWidth(或者minHeight和maxHeight)值都一樣child的size沒有變化的空間,只能在限定死的約束空間中布局,這個時候relayoutBoundary也是指向自身。
最后就是sizedByParent這個屬性,說實在看名字不太明白它的意圖,但是sizedByParent卻決定performResize這個方法會不會調(diào)起,但是看了RenderBox.size的setter方法:

set size(Size value) {
    assert(!(debugDoingThisResize && debugDoingThisLayout));
    assert(sizedByParent || !debugDoingThisResize);
    assert(() {
      if ((sizedByParent && debugDoingThisResize) ||
          (!sizedByParent && debugDoingThisLayout))
        return true;
      assert(!debugDoingThisResize);
      String contract, violation, hint;
      if (debugDoingThisLayout) {
        assert(sizedByParent);
        violation = "It appears that the size setter was called from performLayout().";
        hint = "";
      } else {
        violation = "The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).";
        if (owner != null && owner.debugDoingLayout)
          hint = "Only the object itself can set its size. It is a contract violation for other objects to set it.";
      }
      if (sizedByParent)
        contract = "Because this RenderBox has sizedByParent set to true, it must set its size in performResize().";
      else
        contract = "Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().";
      throw new FlutterError(
        "RenderBox size setter called incorrectly.
"
        "$violation
"
        "$hint
"
        "$contract
"
        "The RenderBox in question is:
"
        "  $this"
      );
    }());
    assert(() {
      value = debugAdoptSize(value);
      return true;
    }());
    _size = value;
    assert(() { debugAssertDoesMeetConstraints(); return true; }());
  }

大致可以總結(jié)一下,一般情況下都是都是根據(jù)parent給予的約束條件來計算size,而設(shè)置size只能在performResize或者performLayout中進行,如果設(shè)置sizedByParent為true,則只能在performResize中進行,否則就只能在performLayout中與child的布局同時進行。所以sizedByParent為true也意味著這個RenderObject的size不需要依賴于child的size,完全可以根據(jù)parent給予的約束條件可以確定(取最大或者最小的寬度和高度或者根據(jù)其他算法);但是為false,自身的size就要在performLayout決定,可能要在child的size和約束條件中計算出來,應(yīng)該是更為復(fù)雜,根據(jù)注釋sizedByParent默認為false永遠都不會有問題的。所以這里sizedByParent為true時把relayoutBoundary指向自身,因為自身的size肯定符合約束的條件,也是提高布局效率的一個關(guān)鍵點。
舉一個栗子,在RenderPadding中:

void performLayout() {
    _resolve();
    assert(_resolvedPadding != null);
    if (child == null) {
      size = constraints.constrain(new Size(
        _resolvedPadding.left + _resolvedPadding.right,
        _resolvedPadding.top + _resolvedPadding.bottom
      ));
      return;
    }
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
    child.layout(innerConstraints, parentUsesSize: true);
    final BoxParentData childParentData = child.parentData;
    childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);
    size = constraints.constrain(new Size(
      _resolvedPadding.left + child.size.width + _resolvedPadding.right,
      _resolvedPadding.top + child.size.height + _resolvedPadding.bottom
    ));
  }

RenderPadding先讓child布局之后,根據(jù)child的size,來設(shè)置自身的size。這里還涉及到一個Offset的問題,因為layout只是獲取了size,但是元素在哪里開始繪制,一般也是由parent控制,當parent設(shè)置好每個child的offset之后在繪制的過程中就可以在適當?shù)奈恢弥欣L制了。

parentUsesSize & sizedByParent

個人覺得這兩個名稱是最令人迷惑,所以這里再總結(jié)一下:

sizedByParent 顧名思義控件的大小完全在父控件的約束條件下,例如約束條件maxWidth=100,minWidth=0就意味著子控件的寬度只能在0到100的范圍內(nèi)。

parentUsesSize 意味著父控件要依賴子控件的size,可能父控件的布局要根據(jù)子控件的size來做調(diào)整。

還有這兩者是否是沖突的尼,能否都為true?

根據(jù)我的分析這兩個應(yīng)該是不會造成沖突的,在選定布局的邊界情況下,剛才代碼中:

if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
   relayoutBoundary = this;
} else {
   final RenderObject parent = this.parent;
   relayoutBoundary = parent._relayoutBoundary;
}

如果parentUsesSize為true,布局邊界毫無疑問指向了parent,也就是子控件要重新布局必須先從父控件開始,因為父控件需要用到子控件重新布局后的結(jié)果,所以在選定布局邊界的問題上parentUsesSize起到?jīng)Q定性的作用。
如果sizedByParent和parentUsesSize都為true,例如父控件把maxWidth=100,minWidth=0這樣的約束條件傳遞給子控件,意味著子控件布局的范圍只能在0到100之間,假設(shè)子控件最終的size為50,父控件可能直接會使用子控件的size作為自身的size,這樣一看好像現(xiàn)在父控件的布局范圍現(xiàn)在應(yīng)該是0到50,但其實是并不會影響一開始傳遞給子控件的約束條件0到100之間,所以不會觸發(fā)一個循環(huán)布局。

Layer

繼續(xù)PipelineOwner處理流程,在flushLayout之后就是flushCompositingBits方法,而flushCompositingBits目標是為每個RenderObject設(shè)置適當needCompositing值,這影響flutter最終會生成多少層Layer,而這些Layer會組成一棵Layer Tree并交由引擎最終composite成一幀畫面。
總結(jié)之前的,現(xiàn)在我們可以得出以下一張的關(guān)系圖:

再對比一下之前的Chromium文檔里面的一張圖:

這種關(guān)系不言而喻,F(xiàn)lutter確實就是一個super webview。

繼續(xù)flushCompositingBits方法的深入:

void flushCompositingBits() {
    Timeline.startSync("Compositing bits");
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    Timeline.finishSync();
  }

跟之前flushLayout差不多,那么啥時候RenderObject會加入到PipelineOwner._needsCompositingBitsUpdate列表上尼?
掃了一下代碼,發(fā)現(xiàn)除了框架初始化以外,一般都是在添加child和刪除child的時候,調(diào)起RenderObject.markNeedsCompositingBitsUpdate方法。
繼續(xù)_updateCompositingBits方法:

void _updateCompositingBits() {
    if (!_needsCompositingBitsUpdate)
      return;
    final bool oldNeedsCompositing = _needsCompositing;
    _needsCompositing = false;
    visitChildren((RenderObject child) {
      child._updateCompositingBits();
      if (child.needsCompositing)
        _needsCompositing = true;
    });
    if (isRepaintBoundary || alwaysNeedsCompositing)
      _needsCompositing = true;
    if (oldNeedsCompositing != _needsCompositing)
      markNeedsPaint();
    _needsCompositingBitsUpdate = false;
  }

舉個栗子,最初可能是這樣的,RenderObject添加一個新的child,而這個child是被設(shè)置為alwaysNeedsCompositing

經(jīng)過_updateCompositingBits處理后:

而needsComposting這個屬性會用在那里尼?同樣掃一遍代碼,都可以發(fā)現(xiàn)類似的處理:

 if (needsCompositing) {
      pushLayer(new ClipRectLayer(clipRect: offsetClipRect), painter, offset, childPaintBounds: offsetClipRect);
    } else {
      canvas
        ..save()
        ..clipRect(offsetClipRect);
      painter(this, offset);
      canvas
        ..restore();
    }

如果needsCompositing為true,都會創(chuàng)建一個新的Layer,所以needsCompositing更多像一個暗示的作用,在clip,transform或者設(shè)置opacity都會創(chuàng)建一個Layer來處理,這樣可以把一些經(jīng)常變化的區(qū)域隔離開來,每次只需要繪制這部分區(qū)域來提高效率。

接著flushPaint方法:

void flushPaint() {
    Timeline.startSync("Paint", arguments: timelineWhitelistArguments);
    try {
      final List dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = [];
      // Sort the dirty nodes in reverse order (deepest first).
      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            PaintingContext.repaintCompositedChild(node);
          } else {
            node._skippedPaintingOnLayer();
          }
        }
      }
    } finally {
      Timeline.finishSync();
    }
  }

在repaintCompositedChild方法里面,會為RenderObject創(chuàng)建一個屬于自己的Layer,其實也只限于isRepaintBoundary為true的RenderObject,因為只有這樣的RenderObject才可以加入到_nodesNeedingPaint列表中:

static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent: false }) {
    if (child._layer == null) {
      child._layer = new OffsetLayer();
    } else {
      child._layer.removeAllChildren();
    }
    final PaintingContext childContext = new PaintingContext._(child._layer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    childContext._stopRecordingIfNeeded();
  }

接著就是創(chuàng)建PaintingContext,就像前端需要獲取Canvas2D Context一樣,_paintWithContext最終會調(diào)起RenderObject.paint方法,在paint方法里面我們就可以自由繪制了,但是一般情況下CustomPaint組件就可以滿足我們的需求。
再看一下PaintingContext類中:

Canvas get canvas {
    if (_canvas == null)
      _startRecording();
    return _canvas;
  }

  void _startRecording() {
    _currentLayer = new PictureLayer(canvasBounds);
    _recorder = new ui.PictureRecorder();
    _canvas = new Canvas(_recorder, canvasBounds);
    _containerLayer.append(_currentLayer);
  }

  void _stopRecordingIfNeeded() {
    if (!_isRecording)
      return;
    _currentLayer.picture = _recorder.endRecording();
    _currentLayer = null;
    _recorder = null;
    _canvas = null;
  }

當我們獲取canvas做繪制操作的時候,每次都會創(chuàng)建一個新的Canvas對象,并使用使用ui.PictureRecorder記錄我們在canvas的操作,最后_stopRecordingIfNeeded會從recorder上獲取到繪制的picture,感覺這里有涉及到底層解析得不太好。。。

好吧,當flushPaint完成后,Layer Tree也構(gòu)建出來了,最后就是composite階段了,回到RenderView.compositeFrame方法:

void compositeFrame() {
    Timeline.startSync("Compositing", arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = new ui.SceneBuilder();
      layer.addToScene(builder, Offset.zero);
      final ui.Scene scene = builder.build();
      ui.window.render(scene);
      scene.dispose();
    } finally {
      Timeline.finishSync();
    }
  }

Flutter會把所有的Layer都加入到ui.SceneBuilder對象中,然后ui.SceneBuilder會構(gòu)建出ui.Scene(場景),交給ui.window.render方法去做最后真實渲染,之后就是底層引擎的工作內(nèi)容,有機會再去更加深入去學習吧。

結(jié)束

大致把整個布局渲染流程梳理一遍,感覺越到后面越吃力,有點超出認知的范圍,也證明自己知識面仍然有很多不足的地方,如果有什么錯漏的地方希望大家能夠指正。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89627.html

相關(guān)文章

  • Flutter中的布局繪制流程簡析(一)

    摘要:入口界面的布局和繪制在每一幀都在發(fā)生著,甚至界面沒有變化,它也會存在可以想象每一幀里面,引擎都像流水線的一樣重復(fù)著幾個過程構(gòu)建控件樹,布局繪制和合成,周而復(fù)始。大概可以想到的主要功能負責管理那些,讓它們進行布局和繪制。 開始 Flutter對比前端流行的框架,除了構(gòu)建控件樹和控件狀態(tài)管理等,還多了布局和繪制的流程,布局和繪制以往都是前端開發(fā)可望而不可及的都被封鎖在瀏覽器渲染引擎的實現(xiàn)里...

    duan199226 評論0 收藏0
  • Flutter之SchedulerBinding簡析

    摘要:但是接下來并不是討論單線程如何方便開發(fā),而是要深入的調(diào)度器,看一下是如何安排任務(wù),調(diào)度工作。總結(jié)在大部分情況下,其實并不用擔心會像游戲一樣瘋狂消耗電量,消耗電量表現(xiàn)應(yīng)該跟原生沒有多大差別。 開始 在原生開發(fā)中(例如Android)都會強調(diào)不能阻塞主線程,但是開發(fā)中經(jīng)常會遇到發(fā)送請求或者操作數(shù)據(jù)庫等,這些操作都會阻塞主線程,幾乎唯一辦法就是用多線程處理這些工作;而在Flutter中就像跟...

    BlackMass 評論0 收藏0
  • Flutter樣式和布局控件簡析()

    摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深的體會。關(guān)于屬性,指前一個組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深...

    yck 評論0 收藏0
  • Flutter樣式和布局控件簡析()

    摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深的體會。關(guān)于屬性,指前一個組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深...

    leanxi 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<