从源码了解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又有啥作用?

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

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