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

戀貓de小郭發表於2020-03-27

作為系列文章的第二十一篇,本篇將通過不一樣的角度來介紹 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 的藍色方塊,並且程式碼裡沒有用到任何 WidgetRenderObject 甚至 Layer,而是使用了 PictureRecorderCanvasSceneBuilder 這些相對陌生的物件完成了畫面繪製,並且在最後執行的是 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 繪製藍色小方塊;
  • 結束繪製後通過 SceneBuilderpushOffsetaddPicture 載入了繪製的內容;
  • 通過 window.render 繪製出畫面。

需要注意⚠️: render 方法被限制必須在 onBeginFrameonDrawFrame 中呼叫,所以上方程式碼才會有 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_ 用於繪製,所以首先可以知道SceneEngine 是和 layer_tree_ 有關係

然後就是在 Flutter Framework 中 Scene 只能通過 SceneBuilder 構建,而 SceneBuilder 中存在很多方法比如: pushOffsetpushClipRectpushOpacity 等,這些方法的執行後,可以通過 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';
複製程式碼

所以 SceneBuilderbuildScene 之前,可以通過 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 ,而查詢該方法的實現恰好就有OffsetLayerPictureLayer 等。

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

所以如下程式碼所示,在 OffsetLayerPictureLayeraddToScene 方法實現中可以看到:

  • PictureLayer 呼叫了 SceneBuilderaddPicture;
  • OffsetLayer 呼叫了 SceneBuilderpushOffset
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();
  }
  ···
}
複製程式碼

所以到這裡 SceneBuilderLayer 通過 EngineLayeraddToScene 方法成功關聯起來,而 window.render 提交的 Scene 又是通過 SceneBuilder 構建得到,所以如下圖所示, LayerScene 就這樣“苟且”到了一起

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

對面前面的藍色小方塊程式碼,如下程式碼所示,這裡修改為使用 Layer 的方式實現,可以看到這樣的實現更接近 Flutter Framework 的實現:通過 rootLayer 一級一級 append 構建出Layer 樹,而 rootLayer 呼叫 addToScene 方法後,因為會執行 addChildrenToScene 方法,從而往下執行 child LayeraddToScene

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 的重新整理和複用。

我們知道當 RenderObjectisRepaintBoundaryture 時,Flutter Framework 就會自動建立一個 OffsetLayer 來“承載”這片區域,而 Layer 內部的畫面更新一般不會影響到其他 Layer

Layer 是如何更新?這就涉及了 Layer 內部的 markNeedsAddToSceneupdateSubtreeNeedsAddToScene 這兩個方法。

如下程式碼所示,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 時,對應 LayeraddToScene 才會被呼叫;而當 Layer_needsAddToScenefalse_engineLayer 不為空時就觸發 Layer 的複用

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

    _needsAddToScene = false;
  }
複製程式碼

是的,當一個 Layer_needsAddToScenefalse 時 表明了自己不需要更新,那這個 LayerEngineLayer 又存在,那 就可以被複用。舉個例子:當一個新的頁面開啟時,底部的頁面並沒有發生變化時,它只是參與畫面的合成,所以對於底部頁面來說它 “Layer” 是可以直接被複用參與繪製。

markNeedsAddToScene 在什麼時候會被呼叫?

如下圖所示,當 Layer 子的引數,比如: PictureLayerpictureOffsetLayeroffset 發生變化時,Layer 就會主動呼叫 markNeedsAddToScene 標記自己為“髒”區域。另外當 LayerengineLayer 發生變化時,就會嘗試觸發父節點的 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 是在 RenderViewcompositeFrame 方法中被呼叫;而 RenderView 是在RendererBindinginitRenderView 被初始化;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,並且在 WindowdrawFrame 方法中呼叫了 renderView.compositeFrame(); 提交了繪製,而 RenderView 作為根節點,它攜帶的 rootLayerOffsetLayer 的子類 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 它的 ownerRenderView,然後它有兩個 child 節點: child1 OffsetLayer 和 child2 PictureLayer

預設情況下因為 Layer 的形成機制(isRepaintBoundaryture 自動建立一個 OffsetLayer)和 Canvas 繪製需要,至少會有一個 OffsetLayerPictureLayer

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 所示,首先看:

  • OffsetLayercreatorRepaintBoundary,而其來源是 Overlay,我們知道 Flutter 中可以通過 Overlay 做全域性懸浮控制元件,而 Overlay 就是在 MaterialAppNavigator 中建立,並且它是一個獨立的Layer
  • OffsetLayer 的 child 是 PageStoragePageStorage 是通過 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,
    );
  }
}
複製程式碼

可以看到這裡多了一個 PhysicalModelLayerPictureLayerPhysicalModelLayer 是用於除錯時顯示除錯蒙層的,比如開啟蒙層後可以看到各種顏色的標註,如果不需要可以設定 debugDisablePhysicalShapeLayers,而之後的 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 樹,可以看到又可以多了個 PictureLayerAnnotatedRegionLayerTransformLayer : 其中多了的 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 繪製

自此,第二十一篇終於結束了!(///▽///)

資源推薦

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

相關文章