从源码开始了解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(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}

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

RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement 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 attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement 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又有啥作用?