从源码看flutter(四):Layer篇

从源码看flutter系列集合canvas

开篇

这一篇,咱们将简单的了解一下 Layer 相关内容,由于其中大部分是与C++交互,因此只是从结构上作一个认识与分析bash

从上一篇中,咱们了解到了,若是 RenderObjectisRepaintBoundarytrue 会经过本身的 Layer 对象去渲染,若是没有为 RenderObject 手动指定 Layer 的话,默认会是 OffestLayer;为 false 则经过父节点的 Layer 对象渲染。app

其中 paint 相关的 Layer 逻辑都在 PaintingContext 中,每次 paint 都会建立一个新的 PaintingContext 对象dom

同时经过 PaintingContext 获取 Canvans 时会建立一个 PictureLayer 被合成到 PaintingContext 的建立时所接收的 Layeride

下面,咱们简单的看一下 Layer 对象布局

Layer

abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
  
  @override
  ContainerLayer get parent => super.parent as ContainerLayer;
  ...
  Layer get nextSibling => _nextSibling;
  ...
  Layer get previousSibling => _previousSibling;
  ...
  @protected
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
  ...
}
复制代码

LayerRenderObject 同样,都是 AbstractNode 的子类,能够看到持有的 parent 对象都是 ContainerLayer,同时 Layer 还有两个对象 nextSiblingpreviousSibling,看起来像一个双向链表的结构post

addToScene(...) 交由子类实现,就是将 Layer 对象交给 engine 去处理,传递给 engine 的逻辑都在 SceneBuilder测试

Layer 有多个子类,分别实现不一样的渲染功能ui

其中 PictureLayout 是主要的图像绘制层;
TextureLayer 则用于外界纹理的实现,经过它能够实现诸如相机、视频播放、OpenGL等相关操做;
ContainerLayout 则是各个 Layer 组成的复合层this

Layer的刷新

上一篇中,咱们自定义了 RenderObject 而且重写了它的 paint(...) 方法,经过对 Canvans 对象进行操做,咱们绘制了本身想要的图像,而 Canvans 是从 PaintingContext 中获取的,在获取 Canvans 时,其实作了和 Layer 有关的一系列操做

class PaintingContext extends ClipContext {
  ...
  @override
  Canvas get canvas {
    if (_canvas == null)
      _startRecording();
    return _canvas;
  }

  void _startRecording() {
    assert(!_isRecording);
    _currentLayer = PictureLayer(estimatedBounds);
    _recorder = ui.PictureRecorder();
    _canvas = Canvas(_recorder);
    _containerLayer.append(_currentLayer);
  }
  ...
}
复制代码

能够看到,在这里建立了一个新的 PictureLayer 被添加到了 _containerLayer 中,咱们的 Layer 最终是如何被渲染的呢?

信息仍是能够从上一篇得到,咱们知道 RendererBindingdrawFrame() 中进行了布局与绘制操做

@protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }
复制代码

最终的渲染实际上是经过 compositeFrame() 来进行的,而这里的 renderView 就是咱们的根RenderObject 咱们能够看一下 compositeFrame() 作了些什么

class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
  ...
  void compositeFrame() {
    ...
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
    ...
  }
  ...
}
复制代码

能够看到,这里经过 buildScene(...) 建立了 Scene 对象,根据注释来看,它至关于一颗 Layer

以后经过 Window 对象的 render(...) 方法来进行渲染

void render(Scene scene) native 'Window_render';
复制代码

它直接调用的是 engine 的方法,经过这个方法,就能够对图像进行渲染,后面咱们会进行一个测试来展现它的做用

这里的 layer 就是根 Layer ,它实际上是一个 TransformLayer,咱们能够简单看一下它的建立流程

根Layer建立流程

RendererBindinginitInstances() 中经过 initRenderView() 进行了 RenderView 的建立

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  ...
  @override
  void initInstances() {
    super.initInstances();
    ...
    initRenderView();
    ...
  }
  ...
}

复制代码

RendererBinding -> initRenderView()

void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }
复制代码

Layer 就是在 prepareInitialFrame() 中建立的

RenderView -> prepareInitialFrame()

void prepareInitialFrame() {
    ...
    scheduleInitialLayout();
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
    ...
  }
复制代码

建立的方法就是 _updateMatricesAndCreateNewRootLayer()

TransformLayer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    ...
    return rootLayer;
  }
复制代码

这里咱们只是简单的了解一下根 Layer 的建立流程,它就是一个 TransformLayer 对象。

建立流程咱们知道了,而想要了解刷新流程,咱们须要回到 compositeFrame() 方法中,执行刷新的方法就在 buildScene(...)

buildScene(...)

从前面的关系图咱们知道,TransformLayer 的父类是 OffsetLayer,而 OffsetLayer 的父类是 ContainerLayer,它们都没有重写 buildScene(...) 方法,因此最后会调用 ContainerLayerbuildScene(...)

ui.Scene buildScene(ui.SceneBuilder builder) {
    ...
    updateSubtreeNeedsAddToScene();
    addToScene(builder);
    ...
    _needsAddToScene = false;
    ...
    return scene;
  }
复制代码

能够先看一下 updateSubtreeNeedsAddToScene() 方法

updateSubtreeNeedsAddToScene()

///ConstraintLayer
  @override
  void updateSubtreeNeedsAddToScene() {
    super.updateSubtreeNeedsAddToScene();
    Layer child = firstChild;
    while (child != null) {
      child.updateSubtreeNeedsAddToScene();
      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
      child = child.nextSibling;
    }
  }
  
  ///Layer
  @protected
  @visibleForTesting
  void updateSubtreeNeedsAddToScene() {
    _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
  }
复制代码

其实全部 Layer 类中,只有 ConstraintLayerLayer 具有这两个方法,这里其实就是遍历全部子 Layer 对象,调用他们的 updateSubtreeNeedsAddToScene() 来设置 _needsAddToScene 的值

这个值顾名思义,就是表示是否须要将改 Layer 添加到 Scene 中,若是须要添加,则就是进行刷新了。它根据 _needsAddToScenealwaysNeedsAddToScene 来设置,当调用 markNeedsAddToScene() 方法的时候, _needsAddToScene 就会被设置为 true

updateSubtreeNeedsAddToScene() 执行结束后,接下来会调用 addToScene(builder) 方法

addToScene(...)

正好 TransformLayer 重写了这个方法,而且没有调用父类的方法

@override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    ...
    engineLayer = builder.pushTransform(
      _lastEffectiveTransform.storage,
      oldLayer: _engineLayer as ui.TransformEngineLayer,
    );
    addChildrenToScene(builder);
    builder.pop();
  }
复制代码

这里的 engineLayer 对象是用于进行复用的

能够看到这里调用了 addChildrenToScene(builder) 方法,这个方法只在 ContainerLayer 中,且没有被重写

addChildrenToScene(...)

void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
    Layer child = firstChild;
    while (child != null) {
      if (childOffset == Offset.zero) {
        child._addToSceneWithRetainedRendering(builder);
      } else {
        child.addToScene(builder, childOffset);
      }
      child = child.nextSibling;
    }
  }
复制代码

在这里就是遍历 child ,而后调用它们各自实现的 addToScene(...) 方法,而是否要将 Layer 添加到 Scene 的判断依据,已经在以前的 updateSubtreeNeedsAddToScene() 中完成了。

这里须要注意一下, _addToSceneWithRetainedRendering(builder) 就是用于对以前的 _engineLayer 进行复用,当 childOffsetOffset.zero

那么到这里 Layer 的刷新流程就结束了。而本篇文章差很少也快到头了,接下来咱们完成上面提到过的,进行一个渲染测试

渲染测试

能够在这里进行测试:dartpad.dev/

import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';


void main(){
    final OffsetLayer rootLayer = new OffsetLayer();
    final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
    rootLayer.append(pictureLayer);

    PictureRecorder recorder = PictureRecorder();
    Canvas canvas = Canvas(recorder);

    Paint paint = Paint();
    paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];

    canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
    pictureLayer.picture = recorder.endRecording();
    
    SceneBuilder sceneBuilder = SceneBuilder();
    rootLayer.addToScene(sceneBuilder);

    Scene scene = sceneBuilder.build();
    window.onDrawFrame = (){
      window.render(scene);
    };
    window.scheduleFrame();
}
复制代码

效果以下

能够看到,咱们没有使用任何 Widget 就在设备上展现了一个图案。因此其实从这里就能够了解到为何说 Flutter是经过skia引擎去绘制的了。

关于 Layer 的其余内容,这里也再也不深刻了,毕竟再深刻就是C++了

本篇是咱们讲过的四棵树中最后的一颗,而这里很是方便用于测试一个咱们前三篇都遇到了可是都略过了的部分,那就是 热重载

额外部分:热重载

当你经过上面的用例进行测试的时候,点击一下热重载按钮,是否是发现会报错:

Error -32601 received from application: Method not found
复制代码

而且图案的颜色并不会更改,这就涉及到咱们以前提到过的一个方法了:reassemble()

ElementRenderObject 中你常常能看到与之相关的方法,它就是用于实现热重载的核心逻辑

BindingBase 中,咱们能够看到找到这样一个方法:reassembleApplication() ,就是它来进行热重载控制的

它会调用 performReassemble() 方法

@mustCallSuper
  @protected
  Future<void> performReassemble() {
    FlutterError.resetErrorCount();
    return Future<void>.value();
  }
复制代码

WidgetsBindingRendererBinding 都重写了这个方法,若是感兴趣的话,能够去看一下,他们分在其中调用了让 ElementRenderObject 进行热重载的方法

那么,咱们想要实现实现热重载其实就很简单了,看代码:

import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart';

void main() => TestBinding();

class TestBinding extends BindingBase{

  @override
  Future<void> performReassemble(){
    final OffsetLayer rootLayer = new OffsetLayer();
    final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
    rootLayer.append(pictureLayer);

    PictureRecorder recorder = PictureRecorder();
    Canvas canvas = Canvas(recorder);

    Paint paint = Paint();
    paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];

    canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
    pictureLayer.picture = recorder.endRecording();

    SceneBuilder sceneBuilder = SceneBuilder();
    rootLayer.addToScene(sceneBuilder);

    Scene scene = sceneBuilder.build();
    window.onDrawFrame = (){
      window.render(scene);
    };
    window.scheduleFrame();
    super.performReassemble();
    return Future<void>.value();
  }
}
复制代码

热重载效果以下,你们能够在设备上进行测试

固然,热重载的核心逻辑就是这个了。

不过此前会进行代码文件的变动检查等,详情能够看这一篇文章:揭秘Flutter Hot Reload(原理篇)

本篇到这里就结束了,而【从源码看flutter】 还没有结束,敬请期待吧

相关文章
相关标签/搜索