亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

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

icattlecoder / 1136人閱讀

摘要:所以這里為時(shí)把指向自身,因?yàn)樽陨淼目隙ǚ霞s束的條件,也是提高布局效率的一個(gè)關(guān)鍵點(diǎn)。舉一個(gè)栗子,在中先讓布局之后,根據(jù)的,來(lái)設(shè)置自身的。意味著父控件要依賴子控件的,可能父控件的布局要根據(jù)子控件的來(lái)做調(diào)整。

布局約束

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

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

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

bounded,如果最大約束不是infinite

unbounded,如果最大約束是infinite

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

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

既然是parent傳遞給child的約束條件,當(dāng)然是在performLayout的時(shí)候調(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();
  }

開(kāi)始分析這段代碼前一小半,決定relayoutBoundary的值也就是布局邊界,一個(gè)RenderObject想要重新布局,應(yīng)該從哪里開(kāi)始。
parentUsesSize,如果為false也就是,parent的布局并不需要依賴child的布局結(jié)果,那么child如果要重新布局并不需要通知parent,布局的邊界就是自身了,而parentUsesSize的默認(rèn)值也是為false,也就是大部分時(shí)候也只需自身重新布局。
parentUsesSize,如果為true就是parent的布局要依賴child布局(或者parent的size依賴于child的size,想象一下兩個(gè)div嵌套的情況),再看如果sizedByParent和constraints.isTight都為false,在這種情況之下relayoutBoundary要指向parent.relayoutBoundary,也就是說(shuō)child如果要重新布局,必須從relayoutBoundary開(kāi)始,在RenderObject.markNeedsLayout方法實(shí)現(xiàn)里面,最終只會(huì)把relayoutBoundary加入到_nodesNeedingLayout列表中,跟isRepaintBoundary處理是幾乎一樣的;
但是如果constraints.isTight為true,也就是minWidth和maxWidth(或者minHeight和maxHeight)值都一樣child的size沒(méi)有變化的空間,只能在限定死的約束空間中布局,這個(gè)時(shí)候relayoutBoundary也是指向自身。
最后就是sizedByParent這個(gè)屬性,說(shuō)實(shí)在看名字不太明白它的意圖,但是sizedByParent卻決定performResize這個(gè)方法會(huì)不會(huì)調(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給予的約束條件來(lái)計(jì)算size,而設(shè)置size只能在performResize或者performLayout中進(jìn)行,如果設(shè)置sizedByParent為true,則只能在performResize中進(jìn)行,否則就只能在performLayout中與child的布局同時(shí)進(jìn)行。所以sizedByParent為true也意味著這個(gè)RenderObject的size不需要依賴于child的size,完全可以根據(jù)parent給予的約束條件可以確定(取最大或者最小的寬度和高度或者根據(jù)其他算法);但是為false,自身的size就要在performLayout決定,可能要在child的size和約束條件中計(jì)算出來(lái),應(yīng)該是更為復(fù)雜,根據(jù)注釋sizedByParent默認(rèn)為false永遠(yuǎn)都不會(huì)有問(wèn)題的。所以這里sizedByParent為true時(shí)把relayoutBoundary指向自身,因?yàn)樽陨淼膕ize肯定符合約束的條件,也是提高布局效率的一個(gè)關(guān)鍵點(diǎn)。
舉一個(gè)栗子,在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,來(lái)設(shè)置自身的size。這里還涉及到一個(gè)Offset的問(wèn)題,因?yàn)閘ayout只是獲取了size,但是元素在哪里開(kāi)始繪制,一般也是由parent控制,當(dāng)parent設(shè)置好每個(gè)child的offset之后在繪制的過(guò)程中就可以在適當(dāng)?shù)奈恢弥欣L制了。

parentUsesSize & sizedByParent

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

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

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

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

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

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

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

Layer

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

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

這種關(guān)系不言而喻,F(xiàn)lutter確實(shí)就是一個(gè)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差不多,那么啥時(shí)候RenderObject會(huì)加入到PipelineOwner._needsCompositingBitsUpdate列表上尼?
掃了一下代碼,發(fā)現(xiàn)除了框架初始化以外,一般都是在添加child和刪除child的時(shí)候,調(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;
  }

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

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

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

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

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

接著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方法里面,會(huì)為RenderObject創(chuàng)建一個(gè)屬于自己的Layer,其實(shí)也只限于isRepaintBoundary為true的RenderObject,因?yàn)橹挥羞@樣的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最終會(huì)調(diào)起RenderObject.paint方法,在paint方法里面我們就可以自由繪制了,但是一般情況下CustomPaint組件就可以滿足我們的需求。
再看一下PaintingContext類(lèi)中:

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;
  }

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

好吧,當(dāng)flushPaint完成后,Layer Tree也構(gòu)建出來(lái)了,最后就是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會(huì)把所有的Layer都加入到ui.SceneBuilder對(duì)象中,然后ui.SceneBuilder會(huì)構(gòu)建出ui.Scene(場(chǎng)景),交給ui.window.render方法去做最后真實(shí)渲染,之后就是底層引擎的工作內(nèi)容,有機(jī)會(huì)再去更加深入去學(xué)習(xí)吧。

結(jié)束

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/89627.html

相關(guān)文章

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

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

    duan199226 評(píng)論0 收藏0
  • Flutter之SchedulerBinding簡(jiǎn)析

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

    BlackMass 評(píng)論0 收藏0
  • Flutter樣式和布局控件簡(jiǎn)析()

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

    yck 評(píng)論0 收藏0
  • Flutter樣式和布局控件簡(jiǎn)析()

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

    leanxi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<