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又有啥作用?
如果觉得有什么错误,欢迎拍砖。。。