在flutter
中,一切皆Widget
。不管是显示界面的UI元素,如Text
、Image
、Icon
等;仍是功能性组件,如手势检测的GestureDetector
组件、应用主题数据传递的Theme
组件、移除系统组件自带Padding的MediaQuery
组件等。能够说,flutter
界面就是由一个个粒度很是细的Widget
组合起来的。html
因为Widget
是不可变的,因此当视图更新时,flutter
会建立新的Widget
来替换旧的Widget
并将旧的Widget
销毁。但这样就会涉及到大量Widget
对象的销毁和重建,从而对垃圾回收形成压力。也所以,flutter
将Widget
设计的十分轻量,并将视图的配置信息与渲染抽象出来,分别交给Element
与RenderObject
。从而使得Widget
只起一个组织者做用,能够将Element
与RenderObject
组合起来,构成一个视图。算法
前面说过Widget
是一种很是轻量且不可变的数据结构,只起一个组织者做用。那么它是如何轻量的尼?下面咱们就从源码来一窥究竟。markdown
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
/// 建立Widget对应的Element对象,Element对象存储了Widget的配置信息
@protected
Element createElement();
/// 判断是否能够更新Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
复制代码
Widget
是一个抽象类,它只有两个方法:数据结构
Widget
对应的Element
对象。Widget
是否可更新。根据Widget
的runtimeType
与key
这两个字段来判断。因为Widget
能够将Element
与RenderObject
组合成一个视图,但从上面源码咱们能够发现,Widget
并无建立RenderObject
对象的方法,那么它是如何建立RenderObject
对象的尼?实际上是经过RenderObjectWidget
的createRenderObject
方法来建立的,此Widget
是一个很是重要的类,若是不直接或间接继承该类,Widget
就没法显示在界面上。下面咱们对RenderObjectWidget
源码一窥究竟。框架
abstract class RenderObjectWidget extends Widget {
...
const RenderObjectWidget({ Key key }) : super(key: key);
/// RenderObjectWidget对应着RenderObjectElement及其子类
@override
RenderObjectElement createElement();
/// 建立一个RenderObject对象
@protected
RenderObject createRenderObject(BuildContext context);
/// 更新renderObject
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
/// 将renderObject从render树中移除
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
复制代码
RenderObjectWidget
是一个继承自Widget
的子类,但它比Widget
多几个方法。ide
RenderObject
对象,在该对象中会将视图数据绘制到不一样的图层上。笔者认为它对应着Android中ViewManager的addView方法。Widget
所持有的RenderObject
对象。笔者认为它对应着Android中ViewManager的updateViewLayout方法。RenderObject
对象从Render树中移除,也就是销毁RenderObject
对象。笔者认为它对应着Android中ViewManager的removeView方法。因为RenderObject
主要是将视图绘制成不一样的图层,而后再显示在屏幕上。因此只有当咱们的组件直接或间接继承自RenderObjectWidget
时,才会经过RenderObject
来进行绘制、渲染,从而显示在屏幕上,如RichText
、Row
、Center
等。不然只是一个用来组装组件的容器,如Text
、ListView
等。函数
Element
是可变的,这里的可变是指Element
拥有本身的生命周期,能够根据生命周期来重用或销毁Element
对象,减小对象的频繁建立及销毁。它承载了视图构建的上下文数据,也是Element
在链接Widget
与RenderObject
的桥梁,Element
与Widget
是一对多的关系。因为Element
是可变的,因此经过Element
将Widget
树的变化(相似React虚拟DOM diff)作了抽象,能够将真正须要修改的部分同步到真实的RenderObject
树中,最大程度下降对真实渲染视图的修改,提升渲染效率,而不是销毁整个渲染视图树重建。下面咱们来对Element
的源码一窥究竟。布局
/// Element对象中存储的是widget的配置信息
abstract class Element extends DiagnosticableTree implements BuildContext {
...
/// 是一个很是重要的方法,主要是更新子Element的配置信息。
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {...}
/// 将当前Element添加到Element树中指定的位置,该位置由父级指定
/// 该方法会改变当前Element的状态,由初始化(initial)状态改成活动(active)状态。
@mustCallSuper
void mount(Element parent, dynamic newSlot) {...}
/// 更新当前Element对应的Widget
/// 该方法仅在Element的活动(active)状态期间调用
@mustCallSuper
void update(covariant Widget newWidget) {...}
/// 改变当前Element在父级中的位置
/// 在MultiChildRenderObjectElement或其余具备多个子元素的[RenderObjectElement]子类中调用。如:Flex及其子类(Column、Row)、Stack等
@protected
void updateSlotForChild(Element child, dynamic newSlot) {...}
void _updateSlot(dynamic newSlot) {...}
void _updateDepth(int parentDepth) {...}
/// 从render树中移除当前Element所对应的RenderObject对象
void detachRenderObject() {...}
/// 将RenderObject对象添加到render树中指定的位置上
void attachRenderObject(dynamic newSlot) {...}
...
/// 初始化Widget,建立Widget对应的Element对象。该方法会调用Widget的createElement方法
/// 该方法一般由updateChild方法调用,但也能够由须要对建立Element进行更细粒度控制的子类直接调用
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {...}
...
/// 将指定Element的状态由活动状态改成非活动状态,并从Render树中移除该Element持有的RenderObject对象
@protected
void deactivateChild(Element child) {...}
/// 从Element的子列表中移除指定的子Element,以准备在Element树的其余地方重用子Element
@protected
void forgetChild(Element child);
void _activateWithParent(Element parent, dynamic newSlot) {...}
static void _activateRecursively(Element element) {...}
/// 将Element由初始化状态改成活动状态的具体实现,在mount方法中会调用该方法
@mustCallSuper
void activate() {...}
/// 将Element的状态由活动转变为非活动状态(等待)。处于非活动状态时,该Element不会被Widget使用,亦不会出如今屏幕上。在当前帧结束以前,该Element会一直存在,若是当前帧结束后,该Element还没有被使用,则该Element状态将改变为销毁状态,从而销毁该Element。
@mustCallSuper
void deactivate() {...}
...
/// 将Element的状态由非活动(等待)状态改成销毁状态,销毁当前Element
@mustCallSuper
void unmount() {...}
@override
RenderObject findRenderObject() => renderObject;
//计算Widget的size,size是从RenderObject中获取的的
@override
Size get size {...}
...
/// 当Element的依赖发生变化时会调用该方法
@mustCallSuper /// 这个是必须调用父类方法的注解吧???
void didChangeDependencies() {...}
...
/// 将Element元素标记为dirty并添加到全局列表中,以便下次在下一帧中从新构建
void markNeedsBuild() {...}
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
/// called to mark this element dirty, by [mount] when the element is first
/// built, and by [update] when the widget has changed.
void rebuild() {...}
/// 在进行必定的检查后调用rebuild()方法
@protected
void performRebuild();
}
复制代码
Element
源码里东西仍是蛮多的,但其实只有如下一些核心的方法。post
Element
,它主要有如下几种状况。newWidget == null | newWidget != null | |
---|---|---|
child == null | 返回null | 返回一个新的Element对象 |
child != null | 移除child并返回null | 若是可能就更新child,返回child或者一个新的Element对象。 |
Element
添加到Element
树中指定的位置,该位置由父级指定。该方法会改变当前Element
的状态,由初始化(initial)状态改成活动(active)状态。Element
对应的Widget
。该方法仅在Element
的活动(active)状态期间调用。MultiChildRenderObjectElement
或其余具备多个子元素的RenderObjectElement
子类中调用。如:Flex
及其子类(Column
、Row
)、Stack
等。Element
所对应的RenderObject
对象。RenderObject
对象添加到render树中指定的位置上。Element
对象。该方法会调用Widget
的createElement
方法来建立Element
对象。该方法一般由updateChild方法调用,但也能够由须要对建立Element进行更细粒度控制的子类直接调用。Element
的状态由活动状态改成非活动状态,并从render树中移除该 Element
持有的RenderObject
对象Element
由初始化状态改成活动状态的具体实现,在mount
方法中会调用该方法Element
的状态由活动转变为非活动状态(等待)。处于非活动状态时,该Element
不会被Widget
使用,亦不会出如今屏幕上。在当前帧结束以前,该Element
会一直存在,若是当前帧结束后,该Element
还没有被使用,则该Element
状态将改变为销毁状态,从而销毁该Element
。Element
的状态由非活动(等待)状态改成销毁状态,销毁当前Element
。从上面代码中,咱们能够发现Element
在每一帧内都会在如下几种状态之间转换。性能
createElement
方法建立了一个Elmenet
对象。mount
方法将Elmenet
对象添加到了Elmenet
树中。deactivate
方法将Elmenet
对象从Elmenet
树中移除。unmount
方法将Elmenet
对象销毁。Element
的生命周期流程以下:
RenderObject
与Widget
、Element
相比,干的活是最苦逼的。由于要进行进行视图的具体渲染,将视图数据绘制成不一样层级。若是没有它,视图就没法显示在屏幕上。下面就从源码里一窥究竟。
/// 顾名思义,主要是来绘制界面
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
/// 布局开始
...
@override
void attach(PipelineOwner owner) {...}
/// 将当前RenderObject对象的布局信息标记为dirty
void markNeedsLayout() {...}
/// 将当前RenderObject对象的父对象的布局信息标记为dirty
@protected
void markParentNeedsLayout() {...}
/// 该方法里仅调用了markNeedsLayout与markParentNeedsLayout方法
void markNeedsLayoutForSizedByParentChange() {...}
void _cleanRelayoutBoundary() {...}
void scheduleInitialLayout() {...}
void _layoutWithoutResize() {...}
/// 完成当前渲染对象的布局,也是界面UI真正开始布局的地方。对应着Android中View的layout方法
void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
...
/// 仅使用约束更新渲染对象大小。
/// 仅当[sizesByParent]为true时才调用此函数。
@protected
void performResize();
/// 为当前Render对象计算布局大小,不能直接调用,由layout方法调用
@protected
void performLayout();
@protected
void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) {...}
/// 旋转当前Render对象(还没有实现)
void rotate({
int oldAngle, // 0..3
int newAngle, // 0..3
Duration time
}) { }
/// 布局结束
// 绘制开始
...
/// 将当前Render对象的合成状态设为dirty
void markNeedsCompositingBitsUpdate() {...}
...
bool _needsPaint = true;
/// 将当前Render对象标记为须要从新绘制
void markNeedsPaint() {...}
void _skippedPaintingOnLayer() {...}
void scheduleInitialPaint(ContainerLayer rootLayer) {...}
/// 图层替换。
void replaceRootLayer(OffsetLayer rootLayer) {...}
void _paintWithContext(PaintingContext context, Offset offset) {...}
...
/// 在该方法中进行真正的绘制,通常由子类重写,
void paint(PaintingContext context, Offset offset) { }
...
/// 绘制结束
...
复制代码
别看RenderObject
源码那么多。但核心方法其实只有两个。
Widget
指定其在屏幕上的位置。笔者认为它对应着Android中View的layout
方法。draw
方法。经过RenderObject
就能够将一个个Widget
绘制成对应的图层,因为图层每每会很是多,因此直接向GPU传递这些图层数据会很是低效。所以须要在Engine
中将这些图层进行合并及光栅化,最后在将这些处理后的数据传递给GPU。
关于绘制原理的更多知识能够去YouTube看谷歌推出的讲解视频:Flutter’s renderding pipeline
接下来用一个示例来讲明Widget
、Element
及RenderObject
三者之间的关系。
_myWidget() {
return Center(
child: Column(
children: <Widget>[
Text("1111"),
Row(
children: <Widget>[Text("222"), Text("3333")],
),
Icon(Icons.ac_unit)
],
),
);
}
复制代码
在上面例子中,有一个Center
来让Widget
居中展现,一个Column
来让Widget
按照垂直方向排列,一个Row
来让Widget
按照水平方向排列,多个Text
及Icon
来展现。
flutter
在对上面的Widget
遍历完成之后,就会建立对应的Widget
树、Element
树及RenderObject
树。以下:
注意:因为
Text
组件不持有RenderObject
对象,因此render树中的Text
只是一个泛指。
能够发现,在上面的三个树中,Widget
、Element
及RenderObject
是一一对应的,在Element
对象中会同时持有Widget
对象及RenderObject
对象。
固然,Widget
东西很是多,不可能仅凭一篇文章就可以描述清楚的,本文也只是从总体结构上来对Widget
进行一次描述,方便你们深刻的去了解Widget
。
最后来一个思考题,Flutter
的Widget
粒度为何那么细?一位阿里大佬给了我一种思路。
因为Flutter借鉴了React Native的差分算法来更新界面。那么粒度越细,更新界面的效果就越好。
那么你们觉得尼???
【参考资料】