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 的藍色方塊,并且代碼里沒有用到任何 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/ 下有不少類似的例子。

image

可以看到 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 等。

image

所以如下代碼所示,在 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 就這樣“茍且”到了一起

image

對面前面的藍色小方塊代碼,如下代碼所示,這里修改為使用 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

image

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 ,這樣父節點也會對應產生變化。

image
@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 的根節點

image

這里舉個例子,如下圖所示是一個簡單的不規范代碼,運行后出現的結果是一個黑色空白頁面,這里我們通過 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 是用于設置陰影等效果的,比如關閉 debugDisablePhysicalShapeLayersAppBar 的陰影會消失,而之后的 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 繪制

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

資源推薦

image
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容