阅读 5190

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/ 下有不少类似的例子。

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

所以如下代码所示,在 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 就这样“苟且”到了一起

对面前面的蓝色小方块代码,如下代码所示,这里修改为使用 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

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 ,这样父节点也会对应产生变化。

@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 的根节点

这里举个例子,如下图所示是一个简单的不规范代码,运行后出现的结果是一个黑色空白页面,这里我们通过 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 绘制

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

资源推荐