从源码了解Flutter绘制

Flutter是怎么构建一个视图页面的,Widget是如何绘制到屏幕上的,这涉及到三棵树:

  • Widget Tree
  • Element Tree
  • RenderObject Tree

Flutter入口函数为main()函数

void main()=> run(new MyApp());//MyApp是一个Widget

runApp 函数接收一个Widget类型的对象作为参数,也就可以理解为万物皆为Widget,其他的业务逻辑等都只是在为Widget的数据,状态改变而服务,下面我们看看runApp里面都做了些什么:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)//把传进来的Widget挂载到跟Widget
    ..scheduleWarmUpFrame();//主动构建视图
}


class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  //单例
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}


在runApp中会实例化一个WidgetsFlutterBinding单例,然后将传进来的Widget挂载到跟Widget上,WidgetsFlutterBinding通过mixin来使用框架中实现的其他 binding的 Service,比如 手势、基础服务、队列、绘图等

接下来我们看看attachRootWidget方法做了什么:

// Element
Element _renderViewElement;

void attachRootWidget(Widget rootWidget) {
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner, renderViewElement);
}

attachRootWidget把 widget交给了 RenderObjectToWidgetAdapter这个适配器,通过attachRootWidget,Element被创建,并且同时能持有 Widget和 RenderObject的引用。然后我们看看attachToRenderTree做了什么:

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element.assignOwner(owner);
    });
    owner.buildScope(element, () {
      element.mount(null, null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element;
}

从源码中我们能看到如果element是空,则调用createElement方法去创建,然后通过mount方法将其挂载到视图树上。但是走到这我们都不知道Widget是怎么被画出来的,只是大概了解到当当一个Widget首次被创建的时候,那么这个Widget会过Widget.createElement inflate成一个element,挂在 element tree 上。现在我们看一个简单的控件Opacity(设置控件的不透明度,取值[0,1])

  • Opacity 继承关系
Opacity extends SingleChildRenderObjectWidget extends RenderObjectWidget extends Widget
  • StatelessWidget 继承关系
StatelessWidget extends Widget
  • StatefulWidget 继承关系
StatefulWidget extends Widget

Opacity 比StatelessWidget,StatefulWidget多了 SingleChildRenderObjectWidget,RenderObjectWidget两层继承关系

  • RenderObjectWidget 源码
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
//半吊子注释: RenderObjectWidgets 为 [RenderObjectElement]s提供配置,而真正为应用渲染视图的的是包裹Widget的 [RenderObject]s,所以RenderObject 才是实际绘制视图的对象
abstract class RenderObjectWidget extends Widget {

  //构造
  const RenderObjectWidget({ Key key }) : super(key: key);

  /// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
  //RenderObjectWidgets 一直填充于一个 RenderObjectElement 的子类,创建element对象
  @override
  RenderObjectElement createElement();

  /// Creates an instance of the [RenderObject] class that this
  /// [RenderObjectWidget] represents, using the configuration described by this
  /// [RenderObjectWidget].
  ///
  /// This method should not do anything with the children of the render object.
  /// That should instead be handled by the method that overrides
  /// [RenderObjectElement.mount] in the object rendered by this object's
  /// [createElement] method. See, for example,
  /// [SingleChildRenderObjectElement.mount].
  @protected
  RenderObject createRenderObject(BuildContext context);

  ....
}

通过此类可以知道Widget为Element提供配置,RenderObject真正绘制视图。还有一个方法就是createRenderObject(BuildContext context),看其注释,此方法返回一个RenderObject实例,去描述(表现)RenderObjectWidget的配置信息。此方法不应对render对象的子代执行任何操作。
而是由可覆盖的RenderObjectElement.mount方法调用处理,例如SingleChildRenderObjectElement中的mount方法。

看到这里让我想起之前 attachRootWidget 这个方法的源码,再贴一次:

     RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
       if (element == null) {
         owner.lockState(() {
           element = createElement();
           assert(element != null);
           element.assignOwner(owner);
         });
         owner.buildScope(element, () {
           element.mount(null, null);
         });
       } else {
         element._newWidget = this;
         element.markNeedsBuild();
       }
       return element;
     }

可以看到attachToRenderTree中element调用mount()方法,mount 方法实例化一个RenderObject,由RenderObject 对象绘制视图。还是接着看SingleChildRenderObjectWidget类:

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {

  const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);

  final Widget child;

  // 重写 createElement,返回 SingleChildRenderObjectElement 实例对象
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

接着看 SingleChildRenderObjectElement类:

class SingleChildRenderObjectElement extends RenderObjectElement {
  Element _child;

  //重写 的mount方法
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
}
  • super.mount(parent, newSlot)
RenderObject _renderObject;
@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  //创建_renderObject实例对象
  _renderObject = widget.createRenderObject(this);
  assert(() { _debugUpdateRenderObjectOwner(); return true; }());
  assert(_slot == newSlot);
  attachRenderObject(newSlot);
  _dirty = false;
}

由此知道,当调用mount挂载的时候,会调用createRenderObject生成_renderObject实例。而createRenderObject 方法我们可以在Opacity这个组件类里看到,它返回了一个RenderOpacity的实例:

@override
RenderOpacity createRenderObject(BuildContext context) {
  return RenderOpacity(
    opacity: opacity,
    alwaysIncludeSemantics: alwaysIncludeSemantics,
  );
}

在RenderOpacity中,重写paint() 方法,调用context.pushOpacity绘制视图,控制透明度:

void paint(PaintingContext context, Offset offset) {
  if (child != null) {
    if (_alpha == 0) {
      return;
    }
    if (_alpha == 255) {
      context.paintChild(child, offset);
      return;
    }
    assert(needsCompositing);
    context.pushOpacity(offset, _alpha, super.paint);
  }
}

小结

  • 调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树

  • Widget:存放渲染内容、视图布局信息,widget的属性最好都是immutable(一成不变的)

  • Element:存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject
    RenderObject:根据Widget的布局属性进行layout,paint Widget传入的内容

在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 被不会重建,只是更新而已。这个目前的我还没看相关的源码。。。

代码

class ElementApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: OpacityHome(),theme: ThemeData.light(),);
  }
}


class OpacityHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("flutter绘制机制"),),
      body: Center(
        child: Opacity(opacity: 0.5,child: Container(
          width: 100,
          color: Colors.black87,
          alignment: Alignment.center,
          height: 100,
          child: Text("flutter",style: TextStyle(fontSize: 20,color: Colors.white),),
        ),),
      ),

    );
  }
}

效果图

看到这虽然对flutter绘制有一定了解,但是好多问题出现了:

  • 视图的更新机制,更新的依据是什么样?
  • BuildContext 又有啥作用?
  • Widget中的Key又有啥作用?

如果觉得有什么错误,欢迎拍砖。。。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 背景 最近小组在尝试使用一套自定义的DSL,通过动态模板下发,实现Flutter端的动态化模板渲染;本来以为只是D...
    没想好像阅读 557评论 0 7
  • 前言 前面两篇Flutter框架分析的文章介绍了渲染流水线,window和框架的初始化。这篇文章继续来理一下对Fl...
    HowHardCanItBe阅读 6,557评论 1 30
  • 二月十日,起床时光线暗淡,还是雨天吧。到了十点多,叔叔发了条微信给我让我过去——今天要去另一个亲戚家拜年。在奶奶...
    舒游阅读 170评论 0 0
  • 朋友圈进入了婚恋季,好多我们以为会是结婚最晚的女神们反而成了圈里最早定下来的,还有很多是从初中高中坚持到结婚的,私...
    原女阅读 396评论 0 2
  • 杨柳轻吐绿芽眉,新梅含羞醉春风。在“三八”妇女节到来之际,枣庄市市中区永安镇政府携手区妇保院在辖区内的龙子心中学开...
    王琴wq70阅读 368评论 0 5