天天看點

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

作為系列文章的第二十一篇,本篇将通過不一樣的角度來介紹 Flutter Framework 的整體渲染原理,深入剖析 Flutter 中構成 Layer 後的繪制流程,讓開發者對 Flutter 的渲染原理和實作邏輯有更清晰的認知。

文章彙總位址:

​​Flutter 完整實戰實戰系列文章專欄​​

​​Flutter 番外的世界系列文章專欄​​

一、Layer 相關的回顧

先回顧下,我們知道在 Flutter 中的控件會經曆 ​

​Widget​

​​ -> ​

​Element​

​​ -> ​

​RenderObject​

​​ -> ​

​Layer​

​​ 這樣的變化過程,而其中 ​

​Layer​

​​ 的組成由 ​

​RenderObject​

​​ 中的 ​

​isRepaintBoundary​

​ 标志位決定。

當調用 ​

​setState​

​​ 時,​

​RenderObject​

​​ 就會往上的父節點去查找,根據 ​

​isRepaintBoundary​

​是否為 true,會決定是否從這裡開始往下去觸發重繪,換個說法就是:确定要更新哪些區域。

比如 ​

​Navigator​

​​ 跳轉不同路由頁面,每個頁面内部就有一個 ​

​RepaintBoundary​

​​ 控件,這個控件對應的 ​

​RenderRepaintBoundary​

​​ 内的 ​

​isRepaintBoundary​

​​ 标記位就是為 ​

​true​

​​ ,進而路由頁面之間形成了獨立的 ​

​Layer​

​ 。

是以相關的 ​

​RenderObject​

​ 在一起組成了 ​

​Layer​

​,而由 ​

​Layer​

​ 構成的 ​

​Layer Tree​

​ 最後會被送出到 Flutter Engine 繪制出畫面。

那 ​

​Layer​

​​ 是怎麼工作的?它的本質又是什麼? Flutter Framework

中 ​​

​Layer​

​ 是如何被送出到 Engine 中?

二、Flutter Framework 中的繪制

帶着前面 ​

​Layer​

​​ 的問題,我們先做個假設:如果抛開 Flutter Framework 中封裝好的控件,我們應該如何繪制出一個畫面?或者說如何建立一個 ​

​Layer​

​ ?

舉個例子,如下代碼所示,運作後可以看到一個居中顯示的 100 x 100 的藍色方塊,并且代碼裡沒有用到任何 ​

​Widget​

​​ 、 ​

​RenderObject​

​​ 甚至 ​

​Layer​

​​,而是使用了 ​

​PictureRecorder​

​ 、​

​Canvas​

​ 、 ​

​SceneBuilder​

​ 這些相對陌生的對象完成了畫面繪制,并且在最後執行的是 ​

​window.render​

​ 。

import 'dart:ui' as ui;

void main() {
  ui.window.onBeginFrame = beginFrame;

  ui.window.scheduleFrame();
}

void beginFrame(Duration timeStamp) {
  final double devicePixelRatio = ui.window.devicePixelRatio;

  ///建立一個畫闆
  final ui.PictureRecorder recorder = ui.PictureRecorder();

  ///基于畫闆建立一個 Canvas
  final ui.Canvas canvas = ui.Canvas(recorder);
  canvas.scale(devicePixelRatio, devicePixelRatio);

  var centerX = ui.window.physicalSize.width / 2.0;
  var centerY = ui.window.physicalSize.height / 2.0;

  ///畫一個 100 的劇中藍色
  canvas.drawRect(
      Rect.fromCenter(
          center: Offset.zero,
          width: 100,
          height: 100),
      new Paint()..color = Colors.blue);

  ///結束繪制
  final ui.Picture picture = recorder.endRecording();

  final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
    ..pushOffset(centerX, centerY)
    ..addPicture(ui.Offset.zero, picture)
    ..pop();

  ui.window.render(sceneBuilder.build());
}      

因為在 Flutter 中 ​

​Canvas​

​​ 的建立是必須有 ​

​PictureRecorder​

​​ ,而 ​

​PictureRecorder​

​ 顧名思義就是建立一個圖檔用于記錄繪制,是以在上述代碼中:

  • 先是建立了​

    ​PictureRecorder​

    ​;
  • 然後使用​

    ​PictureRecorder​

    ​​ 建立了​

    ​Canvas​

    ​ ;
  • 之後使用​

    ​Canvas​

    ​ 繪制藍色小方塊;
  • 結束繪制後通過​

    ​SceneBuilder​

    ​​ 的​

    ​pushOffset​

    ​​ 和​

    ​addPicture​

    ​ 加載了繪制的内容;
  • 通過​

    ​window.render​

    ​ 繪制出畫面。
需要注意⚠️: ​

​render​

​​ 方法被限制必須在 ​

​onBeginFrame​

​​ 或 ​

​onDrawFrame​

​​ 中調用,是以上方代碼才會有 ​

​window.onBeginFrame = beginFrame;​

​。在官方的​​examples/layers/raw/​​ 下有不少類似的例子。
Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

可以看到 Flutter Framework 在底層繪制的最後一步是 ​

​window.render​

​​ ,而如下代碼所示: ​

​render​

​​ 方法需要的參數是 ​

​Scene​

​​ 對象,并且 ​

​render​

​​ 方法是一個 ​

​native​

​ 方法,說明 Flutter Framework 最終送出給 Engine 的是一個 ​

​Scene​

​。

void render(Scene scene) native 'Window_render';      

那 ​

​Scene​

​ 又是什麼?前面所說的 ​

​Layer​

​ 又在哪裡呢?它們之間又有什麼樣的關系?

三、Scene 和 Layer 之間的苟且

在 Flutter 中 ​

​Scene​

​​ 其實是一個 ​

​Native​

​​ 對象,它對應的其實是 ​

​Engine​

​​ 中的 ​​scene.cc​​ 結構,而 Engine 中的 ​

​scene.cc​

​​ 内包含了一個 ​

​layer_tree_​

​ 用于繪制,是以首先可以知道​

​Scene​

​ 在 ​

​Engine​

​ 是和 ​

​layer_tree_​

​ 有關系。

然後就是在 Flutter Framework 中 ​

​Scene​

​ 隻能通過 ​

​SceneBuilder​

​ 建構,而 ​

​SceneBuilder​

​​ 中存在很多方法比如: ​

​pushOffset​

​​、​

​pushClipRect​

​​、​

​pushOpacity​

​​ 等,這些方法的執行後,可以通過 Engine 會建立出一個對應的 ​

​EngineLayer​

​。

OffsetEngineLayer pushOffset(double dx, double dy, { OffsetEngineLayer oldLayer }) {
    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset'));
    final OffsetEngineLayer layer = OffsetEngineLayer._(_pushOffset(dx, dy));
    assert(_debugPushLayer(layer));
    return layer;
  }
  EngineLayer _pushOffset(double dx, double dy) native 'SceneBuilder_pushOffset';      

是以 ​

​SceneBuilder​

​ 在 ​

​build​

​ 出 ​

​Scene​

​ 之前,可以通過 ​

​push​

​ 等相關方法産生 ​

​EngineLayer​

​, 比如前面的藍色小方塊例子,​

​SceneBuilder​

​​ 就是通過 ​

​pushOffset​

​ 建立出對應的圖層偏移。

接着看 Flutter Framework 中的 ​

​Layer​

​​ ,如下代碼所示,在 ​

​Layer​

​​ 預設就存在 ​

​EngineLayer​

​​ 參數,是以可以得知 ​

​Layer​

​​ 肯定和 ​

​SceneBuilder​

​ 有一定關系。

@protected
  ui.EngineLayer get engineLayer => _engineLayer;

  @protected
  set engineLayer(ui.EngineLayer value) {
    _engineLayer = value;
    if (!alwaysNeedsAddToScene) {

      if (parent != null && !parent.alwaysNeedsAddToScene) {
        parent.markNeedsAddToScene();
      }
    }
  }
  ui.EngineLayer _engineLayer;

  /// Override this method to upload this layer to the engine.
  ///
  /// Return the engine layer for retained rendering. When there no
  /// corresponding engine layer, null is returned.

  @protected
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);      

其次在 ​

​Layer​

​​ 中有一個關鍵方法: ​

​addToScene​

​​,先通過注釋可以得知這個方法是由子類實作,并且執行後可以得到一個 ​

​EngineLayer​

​​ ,并且這個方法需要一個 ​

​SceneBuilder​

​​ ,而查詢該方法的實作恰好就有​

​OffsetLayer​

​​ 和 ​

​PictureLayer​

​ 等。

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

是以如下代碼所示,在 ​

​OffsetLayer​

​​ 和 ​

​PictureLayer​

​​ 的 ​

​addToScene​

​ 方法實作中可以看到:

  • ​PictureLayer​

    ​​ 調用了​

    ​SceneBuilder​

    ​​ 的​

    ​addPicture​

    ​;
  • ​OffsetLayer​

    ​​ 調用了​

    ​SceneBuilder​

    ​​ 的​

    ​pushOffset​

    ​ ;
class PictureLayer extends Layer {
  ···
  @override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
  }
  ···
}

class OffsetLayer extends ContainerLayer {
  ···
  OffsetLayer({ Offset offset = Offset.zero }) : _offset = offset;

  @override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    engineLayer = builder.pushOffset(
      layerOffset.dx + offset.dx,
      layerOffset.dy + offset.dy,
      oldLayer: _engineLayer as ui.OffsetEngineLayer,
    );
    addChildrenToScene(builder);
    builder.pop();
  }
  ···
}      

是以到這裡 ​

​SceneBuilder​

​ 和 ​

​Layer​

​ 通過 ​

​EngineLayer​

​ 和 ​

​addToScene​

​ 方法成功關聯起來,而 ​

​window.render​

​ 送出的 ​

​Scene​

​ 又是通過 ​

​SceneBuilder​

​ 建構得到,是以如下圖所示, ​

​Layer​

​ 和 ​

​Scene​

​ 就這樣“苟且”到了一起。

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

對面前面的藍色小方塊代碼,如下代碼所示,這裡修改為使用 ​

​Layer​

​ 的方式實作,可以看到這樣的實作更接近 Flutter Framework 的實作:通過 ​

​rootLayer​

​ 一級一級 ​

​append​

​ 建構出​

​Layer​

​ 樹,而 ​

​rootLayer​

​ 調用 ​

​addToScene​

​ 方法後,因為會執行 ​

​addChildrenToScene​

​ 方法,進而往下執行 child ​

​Layer​

​ 的 ​

​addToScene​

​。

import 'dart:ui' as ui;

void main() {
  ui.window.onBeginFrame = beginFrame;

  ui.window.scheduleFrame();
}

void beginFrame(Duration timeStamp) {
  final double devicePixelRatio = ui.window.devicePixelRatio;

  ///建立一個畫闆
  final ui.PictureRecorder recorder = ui.PictureRecorder();

  ///基于畫闆建立一個 Canvas
  final ui.Canvas canvas = ui.Canvas(recorder);
  canvas.scale(devicePixelRatio, devicePixelRatio);

  var centerX = ui.window.physicalSize.width / 2.0;
  var centerY = ui.window.physicalSize.height / 2.0;

  ///畫一個 100 的劇中藍色
  canvas.drawRect(Rect.fromCenter(center: Offset.zero, width: 100, height: 100),
      new Paint()..color = Colors.blue);

  final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();

  OffsetLayer rootLayer = new OffsetLayer();


  OffsetLayer offsetLayer = new OffsetLayer(offset: Offset(centerX, centerY));
  rootLayer.append(offsetLayer);

  PictureLayer pictureLayer = new PictureLayer(Rect.zero);
  pictureLayer.picture = recorder.endRecording();
  offsetLayer.append(pictureLayer);


  rootLayer.addToScene(sceneBuilder);


  ui.window.render(sceneBuilder.build());
}      

四、Layer 的品種

這裡額外介紹下 Flutter 中常見的 ​

​Layer​

​​,如下圖所示,一般 Flutter 中 ​

​Layer​

​​ 可以分為 ​

​ContainerLayer​

​​ 和非 ​

​ContainerLayer​

​ 。

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

​ContainerLayer​

​​ 是可以具備子節點,也就是帶有 ​

​append​

​ 方法,大緻可以分為:

  • 位移類(​

    ​OffsetLayer​

    ​​/​

    ​TransformLayer​

    ​);
  • 透明類(​

    ​OpacityLayer​

    ​)
  • 裁剪類(​

    ​ClipRectLayer​

    ​​/​

    ​ClipRRectLayer​

    ​​/​

    ​ClipPathLayer​

    ​);
  • 陰影類 (​

    ​PhysicalModelLayer​

    ​)

為什麼這些 ​

​Layer​

​​ 需要是 ​

​ContainerLayer​

​ ?因為這些 ​

​Layer​

​ 都是一些像素合成的操作,其本身是不具備“描繪”控件的能力,就如前面的藍色小方塊例子一樣,如果要呈現畫面一般需要和 ​

​PictureLayer​

​ 結合。

比如 ​

​ClipRRect​

​​ 控件的 ​

​RenderClipRRect​

​​ 内部,在 ​

​pushClipRRect​

​​ 時可以會建立 ​

​ClipRRectLayer​

​​ ,而新建立的 ​

​ClipRRectLayer​

​​ 會通過 ​

​appendLayer​

​​ 方法觸發 ​

​append​

​​ 操作添加為父 ​

​Layer​

​ 的子節點。

而非 ​

​ContainerLayer​

​ 一般不具備子節點,比如:

  • ​PictureLayer​

    ​ 是用于繪制畫面,Flutter 上的控件基本是繪制在這上面;
  • ​TextureLayer​

    ​ 是用于外界紋理,比如視訊播放或者攝像頭資料;
  • ​PlatformViewLayer​

    ​​ 是用于 iOS 上​

    ​PlatformView​

    ​ 相關嵌入紋理的使用;

舉個例子,控件繪制時的 ​

​Canvas​

​​ 來源于 ​

​PaintingContext​

​​ , 而如下代碼所示 ​

​PaintingContext​

​​ 通過 ​

​_repaintCompositedChild​

​​ 執行繪制後得到的 ​

​Picture​

​​ 最後就是送出給所在的 ​

​PictureLayer.picture​

​。

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

五、Layer 的内外兼修

了解完 ​

​Layer​

​​ 是如何送出繪制後,接下來介紹的就是 ​

​Layer​

​ 的重新整理和複用。

我們知道當 ​

​RenderObject​

​​ 的 ​

​isRepaintBoundary​

​​ 為 ​

​ture​

​​ 時,Flutter Framework 就會自動建立一個 ​

​OffsetLayer​

​​ 來“承載”這片區域,而 ​

​Layer​

​​ 内部的畫面更新一般不會影響到其他 ​

​Layer​

​ 。

那 ​

​Layer​

​ 是如何更新?這就涉及了 ​

​Layer​

​ 内部的 ​

​markNeedsAddToScene​

​ 和 ​

​updateSubtreeNeedsAddToScene​

​ 這兩個方法。

如下代碼所示,​

​markNeedsAddToScene​

​​ 方法其實就是把 ​

​Layer​

​​ 内的 ​

​_needsAddToScene​

​​ 标記為 ​

​true​

​​ ; 而 ​

​updateSubtreeNeedsAddToScene​

​​ 方法就是周遊所有 child ​

​Layer​

​​,通過遞歸調用 ​

​updateSubtreeNeedsAddToScene()​

​​ 判斷是否有 ​

​child​

​​ 需要 ​

​_needsAddToScene​

​​ ,如果是那就把自己也标記為 ​

​true​

​。

@protected
  @visibleForTesting
  void markNeedsAddToScene() {
    // Already marked. Short-circuit.
    if (_needsAddToScene) {
      return;
    }

    _needsAddToScene = true;
  }

  @override
  void updateSubtreeNeedsAddToScene() {
    super.updateSubtreeNeedsAddToScene();
    Layer child = firstChild;
    while (child != null) {
      child.updateSubtreeNeedsAddToScene();
      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
      child = child.nextSibling;
    }
  }      

是不是和 ​

​setState​

​​ 調用 ​

​markNeedsBuild​

​​ 把自己标志為 ​

​_dirty​

​ 很像?當 ​

​_needsAddToScene​

​ 等于 ​

​true​

​ 時,對應 ​

​Layer​

​ 的 ​

​addToScene​

​ 才會被調用;而當 ​

​Layer​

​ 的 ​

​_needsAddToScene​

​ 為 ​

​false​

​ 且 ​

​_engineLayer​

​ 不為空時就觸發 ​

​Layer​

​ 的複用。

void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {

    if (!_needsAddToScene && _engineLayer != null) {
      builder.addRetained(_engineLayer);
      return;
    }
    addToScene(builder);

    _needsAddToScene = false;
  }      

是的,當一個 ​

​Layer​

​​ 的 ​

​_needsAddToScene​

​​ 為 ​

​false​

​​ 時 表明了自己不需要更新,那這個 ​

​Layer​

​​ 的 ​

​EngineLayer​

​​ 又存在,那 就可以被複用。舉個例子:當一個新的頁面打開時,底部的頁面并沒有發生變化時,它隻是參與畫面的合成,是以對于底部頁面來說它 “​

​Layer​

​” 是可以直接被複用參與繪制。

那 ​

​markNeedsAddToScene​

​ 在什麼時候會被調用?

如下圖所示,當 ​

​Layer​

​​ 子的參數,比如: ​

​PictureLayer​

​​ 的 ​

​picture​

​​、​

​OffsetLayer​

​​ 的 ​

​offset​

​​ 發生變化時,​

​Layer​

​​ 就會主動調用 ​

​markNeedsAddToScene​

​​ 标記自己為“髒”區域。另外當 ​

​Layer​

​​ 的 ​

​engineLayer​

​​ 發生變化時,就會嘗試觸發父節點的 ​

​Layer​

​​ 調用 ​

​markNeedsAddToScene​

​ ,這樣父節點也會對應産生變化。

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)
@protected
  set engineLayer(ui.EngineLayer value) {
    _engineLayer = value;
    if (!alwaysNeedsAddToScene) {
      if (parent != null && !parent.alwaysNeedsAddToScene) {
        parent.markNeedsAddToScene();
      }
    }
  }      

而 ​

​updateSubtreeNeedsAddToScene​

​​ 是在 ​

​buildScene​

​​ 的時候觸發,在 ​

​addToScene​

​​ 之前調用 ​

​updateSubtreeNeedsAddToScene​

​ 再次判斷 child 節點,進而确定是否需要發生改變。

ui.Scene buildScene(ui.SceneBuilder builder) {
    List<PictureLayer> temporaryLayers;
    assert(() {
      if (debugCheckElevationsEnabled) {
        temporaryLayers = _debugCheckElevations();
      }
      return true;
    }());
    updateSubtreeNeedsAddToScene();
    addToScene(builder);

    _needsAddToScene = false;
    final ui.Scene scene = builder.build();

    return scene;
  }      

六、Flutter Framework 的 Layer 構成

最後回歸到 Flutter Framework ,在 Flutter Framework 中 ​

​_window.render​

​​ 是在 ​

​RenderView​

​​ 的 ​

​compositeFrame​

​​ 方法中被調用;而 ​

​RenderView​

​​ 是在​

​RendererBinding​

​​ 的 ​

​initRenderView​

​​ 被初始化;​

​initRenderView​

​​ 是在 ​

​initInstances​

​​ 時被調用,也就是 ​

​runApp​

​ 的時候。

簡單來說就是:​

​runApp​

​ 的時候建立了 ​

​RenderView​

​ ,并且 ​

​RenderView​

​ 内部的 ​

​compositeFrame​

​ 就是通過 ​

​_window.render​

​來送出 ​

​Layer​

​ 的繪制。

void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true;
      }());
    } finally {
      Timeline.finishSync();
    }
  }      

是以 ​

​runApp​

​​ 的時候 Flutter 建立了 ​

​RenderView​

​​,并且在 ​

​Window​

​​ 的 ​

​drawFrame​

​​ 方法中調用了 ​

​renderView.compositeFrame();​

​​ 送出了繪制,而 ​

​RenderView​

​ 作為根節點,它攜帶的 ​

​rootLayer​

​ 為 ​

​OffsetLayer​

​ 的子類 ​

​TransformLayer​

​,屬于是 Flutter 中 ​

​Layer​

​ 的根節點。

Flutter完整開發實戰詳解(二十一、 Flutter 畫面渲染的全面解析)

這裡舉個例子,如下圖所示是一個簡單的不規範代碼,運作後出現的結果是一個黑色空白頁面,這裡我們通過 ​

​debugDumpLayerTree​

​​ 方法列印出 ​

​Layer​

​ 的機構。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    new Future.delayed(Duration(seconds: 1), () {
      debugDumpLayerTree();
    });
    return MaterialApp(
      title: 'GSY Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Container(),
      //routes: routers,
    );
  }
}      

列印出的結果如下 LOG 所示,正如前面所說 ​

​TransformLayer​

​​ 作為 ​

​rooterLayer​

​​ 它的 ​

​owner​

​​ 是 ​

​RenderView​

​​,然後它有兩個 child 節點: child1 ​

​OffsetLayer​

​​ 和 child2 ​

​PictureLayer​

​ 。

預設情況下因為 ​

​Layer​

​​ 的形成機制(​

​isRepaintBoundary​

​​ 為 ​

​ture​

​​ 自動建立一個 ​

​OffsetLayer​

​​)和 ​

​Canvas​

​​ 繪制需要,至少會有一個 ​

​OffsetLayer​

​​ 和 ​

​PictureLayer​

​。
I/flutter (32494): TransformLayer#f8fa5
I/flutter (32494):  │ owner: RenderView#2d51e
I/flutter (32494):  │ creator: [root]
I/flutter (32494):  │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ transform:
I/flutter (32494):  │   [0] 2.8,0.0,0.0,0.0
I/flutter (32494):  │   [1] 0.0,2.8,0.0,0.0
I/flutter (32494):  │   [2] 0.0,0.0,1.0,0.0
I/flutter (32494):  │   [3] 0.0,0.0,0.0,1.0
I/flutter (32494):  │
I/flutter (32494):  ├─child 1: OffsetLayer#4503b
I/flutter (32494):  │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494):  │ │   ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494):  │ │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#e1be1]
I/flutter (32494):  │ │   ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#95107] ←
I/flutter (32494):  │ │   Stack ← _Theatre ←
I/flutter (32494):  │ │   Overlay-[LabeledGlobalKey<OverlayState>#ceb36] ← ⋯
I/flutter (32494):  │ │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ │
I/flutter (32494):  │ └─child 1: OffsetLayer#e8309
I/flutter (32494):  │     creator: RepaintBoundary-[GlobalKey#bbad8] ← IgnorePointer ←
I/flutter (32494):  │       FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494):  │       _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494):  │       ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494):  │     offset: Offset(0.0, 0.0)
I/flutter (32494):  │
I/flutter (32494):  └─child 2: PictureLayer#be4f1
I/flutter (32494):      paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)      

根據上述 LOG 所示,首先看:

  • ​OffsetLayer​

    ​​ 的​

    ​creator​

    ​​ 是​

    ​RepaintBoundary​

    ​​,而其來源是​

    ​Overlay​

    ​​,我們知道 Flutter 中可以通過​

    ​Overlay​

    ​​ 做全局懸浮控件,而​

    ​Overlay​

    ​​ 就是在​

    ​MaterialApp​

    ​​ 的​

    ​Navigator​

    ​​ 中建立,并且它是一個獨立的​

    ​Layer​

    ​ ;
  • 而​

    ​OffsetLayer​

    ​​ 的 child 是​

    ​PageStorage​

    ​​ ,​

    ​PageStorage​

    ​​ 是通過​

    ​Route​

    ​ 産生的,也即是預設的路由第一個頁面。

是以現在知道為什麼 ​

​Overlay​

​ 可以在 ​

​MaterialApp​

​ 的所有路由頁面下全局懸浮顯示了吧。

如下代碼所示,再原本代碼的基礎上增加 ​

​Scaffold​

​​ 後繼續執行 ​

​debugDumpLayerTree​

​。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    new Future.delayed(Duration(seconds: 1), () {
      debugDumpLayerTree();
    });
    return MaterialApp(
      title: 'GSY Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Container(),
      ),
      //routes: routers,
    );
  }
}      

可以看到這裡多了一個 ​

​PhysicalModelLayer​

​​ 和 ​

​PictureLayer​

​​ ,​

​PhysicalModelLayer​

​​ 是用于設定陰影等效果的,比如關閉 ​

​debugDisablePhysicalShapeLayers​

​​ 後 ​

​AppBar​

​​ 的陰影會消失,而之後的 ​

​PictureLayer​

​ 也是用于繪制。

I/flutter (32494): TransformLayer#ac14b
I/flutter (32494):  │ owner: RenderView#f5ecc
I/flutter (32494):  │ creator: [root]
I/flutter (32494):  │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ transform:
I/flutter (32494):  │   [0] 2.8,0.0,0.0,0.0
I/flutter (32494):  │   [1] 0.0,2.8,0.0,0.0
I/flutter (32494):  │   [2] 0.0,0.0,1.0,0.0
I/flutter (32494):  │   [3] 0.0,0.0,0.0,1.0
I/flutter (32494):  │
I/flutter (32494):  ├─child 1: OffsetLayer#c0128
I/flutter (32494):  │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494):  │ │   ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494):  │ │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#fe143]
I/flutter (32494):  │ │   ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#9cb60] ←
I/flutter (32494):  │ │   Stack ← _Theatre ←
I/flutter (32494):  │ │   Overlay-[LabeledGlobalKey<OverlayState>#ee455] ← ⋯
I/flutter (32494):  │ │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ │
I/flutter (32494):  │ └─child 1: OffsetLayer#fb2a6
I/flutter (32494):  │   │ creator: RepaintBoundary-[GlobalKey#fd46b] ← IgnorePointer ←
I/flutter (32494):  │   │   FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494):  │   │   _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494):  │   │   ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494):  │   │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │   │
I/flutter (32494):  │   └─child 1: PhysicalModelLayer#f1460
I/flutter (32494):  │     │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494):  │     │   PrimaryScrollController ← _ScaffoldScope ← Scaffold ← Semantics
I/flutter (32494):  │     │   ← Builder ← RepaintBoundary-[GlobalKey#fd46b] ← IgnorePointer ←
I/flutter (32494):  │     │   FadeTransition ← FractionalTranslation ← ⋯
I/flutter (32494):  │     │ elevation: 0.0
I/flutter (32494):  │     │ color: Color(0xfffafafa)
I/flutter (32494):  │     │
I/flutter (32494):  │     └─child 1: PictureLayer#f800f
I/flutter (32494):  │         paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 738.2)
I/flutter (32494):  │
I/flutter (32494):  └─child 2: PictureLayer#af14d
I/flutter (32494):      paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)
I/flutter (32494):      

最後通過再使用 ​

​Navigator​

​​ 跳到另外一個頁面,再新頁面列印 ​

​Layer​

​​ 樹,可以看到又可以多了個 ​

​PictureLayer​

​​ 、​

​AnnotatedRegionLayer​

​​ 和 ​

​TransformLayer​

​​ : 其中多了的 ​

​AnnotatedRegionLayer​

​ 是用于處理新頁面頂部狀态欄的顯示效果。

I/flutter (32494): TransformLayer#12e21
I/flutter (32494):  │ owner: RenderView#aa5c7
I/flutter (32494):  │ creator: [root]
I/flutter (32494):  │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ transform:
I/flutter (32494):  │   [0] 2.8,0.0,0.0,0.0
I/flutter (32494):  │   [1] 0.0,2.8,0.0,0.0
I/flutter (32494):  │   [2] 0.0,0.0,1.0,0.0
I/flutter (32494):  │   [3] 0.0,0.0,0.0,1.0
I/flutter (32494):  │
I/flutter (32494):  ├─child 1: OffsetLayer#fc176
I/flutter (32494):  │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494):  │ │   ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494):  │ │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#43140]
I/flutter (32494):  │ │   ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#46f19] ←
I/flutter (32494):  │ │   Stack ← _Theatre ←
I/flutter (32494):  │ │   Overlay-[LabeledGlobalKey<OverlayState>#af6f4] ← ⋯
I/flutter (32494):  │ │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │ │
I/flutter (32494):  │ └─child 1: OffsetLayer#b6e14
I/flutter (32494):  │   │ creator: RepaintBoundary-[GlobalKey#0ce90] ← IgnorePointer ←
I/flutter (32494):  │   │   FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494):  │   │   _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494):  │   │   ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494):  │   │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │   │
I/flutter (32494):  │   └─child 1: PhysicalModelLayer#4fdc6
I/flutter (32494):  │     │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494):  │     │   PrimaryScrollController ← _ScaffoldScope ← Scaffold ←
I/flutter (32494):  │     │   ClipDemoPage ← Semantics ← Builder ←
I/flutter (32494):  │     │   RepaintBoundary-[GlobalKey#0ce90] ← IgnorePointer ←
I/flutter (32494):  │     │   FadeTransition ← ⋯
I/flutter (32494):  │     │ elevation: 0.0
I/flutter (32494):  │     │ color: Color(0xfffafafa)
I/flutter (32494):  │     │
I/flutter (32494):  │     ├─child 1: PictureLayer#6ee26
I/flutter (32494):  │     │   paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 738.2)
I/flutter (32494):  │     │
I/flutter (32494):  │     ├─child 2: AnnotatedRegionLayer<SystemUiOverlayStyle>#cbeaf
I/flutter (32494):  │     │ │ value: {systemNavigationBarColor: 4278190080,
I/flutter (32494):  │     │ │   systemNavigationBarDividerColor: null, statusBarColor: null,
I/flutter (32494):  │     │ │   statusBarBrightness: Brightness.dark, statusBarIconBrightness:
I/flutter (32494):  │     │ │   Brightness.light, systemNavigationBarIconBrightness:
I/flutter (32494):  │     │ │   Brightness.light}
I/flutter (32494):  │     │ │ size: Size(392.7, 83.6)
I/flutter (32494):  │     │ │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │     │ │
I/flutter (32494):  │     │ └─child 1: PhysicalModelLayer#edb15
I/flutter (32494):  │     │   │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494):  │     │   │   AnnotatedRegion<SystemUiOverlayStyle> ← Semantics ← AppBar ←
I/flutter (32494):  │     │   │   FlexibleSpaceBarSettings ← ConstrainedBox ← MediaQuery ←
I/flutter (32494):  │     │   │   LayoutId-[<_ScaffoldSlot.appBar>] ← CustomMultiChildLayout ←
I/flutter (32494):  │     │   │   AnimatedBuilder ← ⋯
I/flutter (32494):  │     │   │ elevation: 4.0
I/flutter (32494):  │     │   │ color: MaterialColor(primary value: Color(0xff2196f3))
I/flutter (32494):  │     │   │
I/flutter (32494):  │     │   └─child 1: PictureLayer#418ce
I/flutter (32494):  │     │       paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 83.6)
I/flutter (32494):  │     │
I/flutter (32494):  │     └─child 3: TransformLayer#7f867
I/flutter (32494):  │       │ offset: Offset(0.0, 0.0)
I/flutter (32494):  │       │ transform:
I/flutter (32494):  │       │   [0] 1.0,0.0,0.0,-0.0
I/flutter (32494):  │       │   [1] -0.0,1.0,0.0,0.0
I/flutter (32494):  │       │   [2] 0.0,0.0,1.0,0.0
I/flutter (32494):  │       │   [3] 0.0,0.0,0.0,1.0
I/flutter (32494):  │       │
I/flutter (32494):  │       └─child 1: PhysicalModelLayer#9f36b
I/flutter (32494):  │         │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (32494):  │         │   ConstrainedBox ← _FocusMarker ← Focus ← _InputPadding ←
I/flutter (32494):  │         │   Semantics ← RawMaterialButton ← KeyedSubtree-[GlobalKey#9ead9]
I/flutter (32494):  │         │   ← TickerMode ← Offstage ← ⋯
I/flutter (32494):  │         │ elevation: 6.0
I/flutter (32494):  │         │ color: Color(0xff2196f3)
I/flutter (32494):  │         │
I/flutter (32494):  │         └─child 1: PictureLayer#2a074
I/flutter (32494):  │             paint bounds: Rect.fromLTRB(320.7, 666.2, 376.7, 722.2)
I/flutter (32494):  │
I/flutter (32494):  └─child 2: PictureLayer#3d42d
I/flutter (32494):      paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)
I/flutter (32494):      

是以可以看到,Flutter 中的 ​

​Widget​

​​ 在最終形成各式各樣的 ​

​Layer​

​​ ,每個 ​

​Layer​

​​ 都有自己單獨的區域和功能,比如 ​

​AnnotatedRegionLayer​

​​在新的頁面處理狀态欄顔色的變化,而這些 ​

​Layer​

​​ 最終通過 ​

​SceneBuilder​

​​ 轉化為 ​

​EngineLayer​

​​ ,最後送出為 ​

​Scene​

​ 經由 Engine 繪制。

最後總結一下:Flutter Framework 的 ​

​Layer​

​ 在繪制之前,需要經曆 ​

​SceneBuinlder​

​ 的處理得到 ​

​EngineLayer​

​,其實 Flutter Framework 中的 ​

​Layer​

​ 可以了解為 ​

​SceneBuinlder​

​ 的對象封裝,而 ​

​EngineLayer​

​ 才是真正的 Engine 圖層 ,在之後得到的 ​

​Scene​

​ 會被送出 Engine 繪制。

資源推薦

  • Github :​​github.com/CarGuo​​
  • 開源 Flutter 完整項目:​​github.com/CarGuo/GSYG…​​
  • 開源 Flutter 多案例學習型項目: ​​github.com/CarGuo/GSYF…​​
  • 開源 Fluttre 實戰電子書項目:​​github.com/CarGuo/GSYF…​​
  • 開源 React Native 項目:​​github.com/CarGuo/GSYG…​​