Flutter学习之视图体系
一、前言
经过之前的学习,可以知道Flutter是一种全新的响应式跨平台的移动开发框架,越来越多的开发者参与学习或者研究中,确实在iOS和Android平台上能够用一套代码构建出性能比较高的应用程序。我刚开始接触FlutterFlutter中文网看到这么一句话:Widget
是Flutter应用程序用户界面的基本构建块。每个Widget
都是用户界面一部分的不可变声明。与其他将试图、控制器、布局和其他属性分离的框架不同,Flutter具有一致的统一对象模型:Widget。在开发过程中也可以知道Widget
可以被定义按钮(button)、样式(style)、填充(Padding)、布局(Row)、手势(GestureDetector)等,我刚开始以为这个Widget
就是眼中所看到的视图,然而并不是这样的,下面慢慢讲述。
二、视图基础
1.Widget
在Flutter官方网站介绍Widgets
开篇有这么一段话:
Flutter widgets are built using a modern react-style framework, which takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.
这段话的意思是:Flutter widgets
是采取React思想使用响应式框架构建的。核心思想就是使用widgets
构建出UI(界面)。Widgets
根据其当前配置和状态描述了它们的视图。当某个widget
的状态发生更改时,widget
会重新构建所描述的视图,framework会根据前面所描述的视图(状态没改变时)进行区分,以确定底层呈现树从一个状态转换到下一个状态所需的最小更改步骤。
在Flutter开发者文档对Widget
的定义如下:
widget
为element
(下面再描述)提供配置信息,这里可以知道widget
和element
存在某种联系。Widgets
在Flutter framework是中心类层次结构,widget
是不可变的对象并且是界面的一部分,widget
会被渲染在elements
上,并(elelments)管理底层渲染树(render tree),这里可以得到一个信息:widget
在渲染的时候会最终转换成element
。继续往下看:
Wigets
本身是没有可变的状态(其所有的字段必须是final)。如果你想吧可变状态和一个widget
关联起来,可以使用StatefulWidget
,StatefulWidget
通过使用StatefulWidget.createState
方法创建State
对象,并且扩充到element
和合并到树中。那么这段可以得出的信息是:widget
并不会直接渲染和管理状态,管理状态是交给State
对象负责。
给定的widget
可以零次或者多次被包含在树中,一个给定的widget
可以多次放置在树中,每次将一个widget
放入树中,他都会被扩充到一个Element
,这就意味着多次并入树中的widget
将会多次扩充到对应的Element
。这段可以这么理解:在一个界面中,有多个Text
被挂载在视图树上,这些Text
的widget
会被填充进自己独立的Element
中,就算widget
被重复使用,还是会创建多个不同的element
对象。继续往下看:
每一个widget
都有自己的唯一的key,这里也很容易理解,就是借助key作为唯一标识符。这段话的意思是:key这个属性控制一个widget
如何替换树中的另一个widget
。如果两个widget
的runtimeType和key属性相等==,则新的widget
通过更新Element
(通过新的widget
来来调用Element.update)来替换旧的widget
。否则,如果两个widget
的runtimeType和key属性不相等,则旧的Element
将从树中被移除,新的widget
将被扩充到一个新的Element
中,这个新的Element
将被插入树中。这里可以得出:如果涉及到widget
的移动或者删除操作前,会根据widget
的runtime和key进行对比。
综上所述:
- widget向Element提供配置信息(数据),界面构造出来的widget树其实只是一颗配置信息树,为了构造出Element树。
- Element和widget有对应关系,因为,element是通过widget来生成的。
- 同一个widget可以创建多个element,也就是一个widget对象可以对应多个element对象。
下面初步看看widget源码:
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
//省略注释
final Key key;
//构建出element
@protected
Element createElement();
//简短文字描述这个widget
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
//根据字面意思 应该是调试诊断树的信息
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
//静态方法,跟上一段解释一样,就是是否用新的widget对象去更新旧UI渲染树的配置
//如果oldWidget和newWidget的runtimeType和key同时相等就会用newWidget对象去更新对应element信息
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
还要注意:widget
是抽象类,在平时,一般继续StatelessWidget
和StatefulWidget
,而这两个类其实也是继承Widget
,这两个类肯定会实现这个createElement
方法,简单看一下:
StatelessWidget
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key key }) : super(key: key);
....
@override
StatelessElement createElement() => StatelessElement(this);
....
}
StatefulWidget
abstract class StatefulWidget extends Widget {
/// Initializes [key] for subclasses.
const StatefulWidget({ Key key }) : super(key: key);
.....
@override
StatefulElement createElement() => StatefulElement(this);
.....
}
确实可以看出,都会创建Element
,只是StatelessWidget
和StatefulWidget
所创建的Element
类型不一样,这里就先不深入了。上面可以知道widget
和element
存在对应的关系,那下面看看element
。
2.Element
看看官方开发者文档中开篇看到:
An instantiation of a Widget at a particular location in the tree.
意思是:element
是树中特定位置的widget
实例。这里描述的很明显,也就是Widget
是总监,部署技术规划,而element
就是员工,真正干活。
widget
描述如何配置子树,由于widgets
是不可变的,所以可以用相同的widget
来同时配置多个子树,Element
表示widget
配置树中的特定位置的实例,随着时间的推移,和给定的Element
关联的Widget
可能会随时变化,例如,如果父widget
重建并为此位置创建新的widget
。
Elements
构成一棵树,大多数elements
都会有唯一的孩子,但是一些widgets
(如RenderObjectElement)可以有多个孩子。
** Element具有以下生命周期:**
- framework通过调用即将用来作
element
的初始化配置信息的Widget
的Widget.createElement方法来创建一个element
。 - framework通过调用mount方法以将新创建的Element添加到给定父级中给定槽点的树上。 mount方法负责将任何子
Widget
扩充到Widget
并根据需要调用attachRenderObject
,以将任何关联的渲染对象附加到渲染树上。 - 此时,
element
被视为激活
,可能出现在屏幕上。 - 在某些情况下,父可能会更改用于配置此
Element
的Widget,例如因为父重新创建了新状态。发生这种情况时,framework将调用新的Widget
的update方法。新Widget
将始终具有与旧Widget
相同的runtimeType和key属性。如果父希望在树中的此位置更改Widget
的runtimeType或key,可以通过unmounting(卸载)此Element并在此位置扩充新Widget
来实现。 - 在某些时候,祖先(Element)可能会决定从树中移除该
element
(或中间祖先),祖先自己通过调用deactivateChild来完成该操作。停用中间祖先将从渲染树中移除该element
的渲染对象,并将此element
添加到所有者属性中的非活动元素列表中,从而framework调用deactivate方法作用在此element
上。 - 此时,该
element
被视为“无效”,不会出现在屏幕上。一个element
直到动画帧结束前都可以保存“非活动”状态。动画帧结束时,将卸载仍处于非活动状态的所有element
。 - 如果
element
被重写组合到树中(例如,因为它或其祖先之一有一个全局建(global key)被重用),framework将从所有者的非活动elements
列表中移除该element
,并调用该element
的activate方法,并重新附加到element
的渲染对象到渲染树上。(此时,该元素再次被视为“活动”并可能出现在屏幕上) - 如果
element
在当前动画帧的末尾(最后一帧)没有被重新组合到树中,那么framework将会调用该元素的unmount方法。
这里可以知道element
的生命周期。并且平时开发没有接触到Element
,都是直接操控widget
,也就是说Flutter已经帮我们对widget
的操作映射到element
上,我这里想象到的有点事降低开发复杂。下面结合一个例子(绘制Text),看看element
是不是最后渲染出来的view:
new Text("hello flutter");
下面看下new Text
的源码:
...
@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
if (MediaQuery.boldTextOverride(context))
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
Widget result = RichText( ---->Text原来是通过RichText这个widget来构建树
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <TextSpan>[textSpan] : null,
),
);
....
继续往RichText
里面看:
....
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
//返回RenderParagraph widget
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
....
发现最终它会返回RenderParagraph
widget,继续往里看:
class RichText extends LeafRenderObjectWidget {
}
可以发现RichText
继承LeafRenderObjectWidget
,继续往下看:
//用于配置RenderObject子类的RenderObjectWidgets的超类,没有孩子,也就是没有字节点(child)
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({ Key key }) : super(key: key);
//构建出类型是LeafRenderObjectElement
@override
LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
可以看到LeafRenderObjectWidget
是抽象类,并且继承了RenderObjectWidget
,继续往下看:
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
//上面注释是:RenderObjectWidgets向[RenderObjectElement]提供了配置信息,包装了[RenderObject],在应用程序了提供了实际的渲染
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
//创建element
@override
RenderObjectElement createElement();
//创建RenderObject
@protected
RenderObject createRenderObject(BuildContext context);
//更新RenderObject
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
//卸载RenderObject
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
由注释可以知道RenderObject
才是绘制UI背后的真正对象,那下面继续简单跟踪:
3.RenderObjectWidget
先看看RenderObjectWidget
继承关系:
看看文档的一一介绍:
3.1.SingleChildRenderObjectWidget
image.png官方文档写的很清楚: 是RenderObjectWidgets
的超类,用于配置有单个孩子的RenderObject
子类(为子类提供存储,实际不提供更新逻辑),并列了具体的实际widget
,如常用的Align、ClipRect、DecoratedBox都是属于这类。
3.2.MultiChildRenderObjectWidget
image.png同样也是RenderObjectWidgets
的超类,用于配置有单个children(也就是多个child)的RenderObject
子类,如Flex、Flow、Stack都属于这类。
3.3.LeafRenderObjectWidget
image.png同样也是RenderObjectWidgets
的超类,用于配置没有孩子的RenderObject
子类,如:RichText(平时的Text)、RawImage、Texture等。 这里总结一下:
-
SingleChildRenderObjectWidget
用作只有一个child的widget
。 -
MultiChildRenderObjectWidget
用作有children(多个孩子)的widget
。 -
LeafRenderObjectWidget
用作没有child的widget
。 -
RenderObjectWidget
定义了创建,更新,删除RenderObject的方法,子类必须实现,RenderObject
是最终布局、UI渲染的实际对象。
那么假如我现在界面上,假如布局如下:
image.png 那么渲染流程如下: image.png这时候会想:为什么要加中间层Element
呢,不直接由Widget
直接转换成RendObject
不是直接更好么?其实并不是这样的。首先知道Flutter是响应式框架,在某一个时刻,可能会受到不同的输入流影响,中间层Element
对这一时刻的事件做了汇总,最后将需要修改的部分同步到RendObject
tree上,也就是:
- 尽可能的降低
RenderObject
tree的更改,提高页面渲染效率。 - 没有直接操作UI,通过数据驱动视图,代码更容易理解和简洁。
上面得出UI树是由一个个element
节点组成,但是最终的渲染是通过RenderObject
来完成,一个widget
从创建到显示到界面的流程是:widget生成element,然后通过createRenderObject方法创建对应的RenderObject
关联到Element.renderObject
上,最后通过RenderObject
来完成绘制。
三、启动到显示
上面知道,widget
并不是真正显示的对象,知道了一个widget
是怎样渲染在屏幕上显示,那下面就从main()方法入口,看看一个app从点击启动到运行的流程:
1.WidgetsFlutterBinding.ensureInitialized
//app入口
void main() => runApp(MyApp());
app入口函数就是调用了runApp方法,看看runApp方法里面做了什么:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
首先参数(Widget app)是一个widget
,下面一行一行分析,进入WidgetsFlutterBinding.ensureInitialized()方法:
/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
//意思:基于widget framework的应用程序的具体绑定
//这是将framework widget和Flutter engine绑定的桥梁
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
/// Returns an instance of the [WidgetsBinding], creating and
/// initializing it if necessary. If one is created, it will be a
/// [WidgetsFlutterBinding]. If one was previously initialized, then
/// it will at least implement [WidgetsBinding].
///
/// You only need to call this method if you need the binding to be
/// initialized before calling [runApp].
///
/// In the `flutter_test` framework, [testWidgets] initializes the
/// binding instance to a [TestWidgetsFlutterBinding], not a
/// [WidgetsFlutterBinding].
//上面注释意思是:返回[WidgetsBinding]的具体实例,必须创建和初始化,如果已//经创建了,那么它就是一个[WidgetsFlutterBinding],如果已经初始化了,那么//至少要实现[WidgetsBinding]
//如果你只想调用这个方法,那么你需要在调用runApp之前绑定并且初始化
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
看到这个WidgetsFlutterBinding
混入(with)很多的Binding
,下面先看父类BindingBase
:
abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
static bool _debugInitialized = false;
static bool _debugServiceExtensionsRegistered = false;
ui.Window get window => ui.window;//获取window实例
@protected
@mustCallSuper
void initInstances() {
assert(!_debugInitialized);
assert(() { _debugInitialized = true; return true; }());
}
}
看到有句代码ui.Window get window => ui.window;,在Android中所有的视图都是通过window
来呈现的,那Flutter中也有window
,那看看window
在Flutter中的作用看看官方对它的定义:
意思是:链接宿主操作系统的接口,也就是Flutter framework 链接宿主操作系统的接口。系统中有一个Window实例,可以从window属性来获取,看看源码:
class Window {
Window._();
//返回DPI,DPI是每英寸的像素点数,是设备屏幕的固件属性
//获取可能不准确
double get devicePixelRatio => _devicePixelRatio;
double _devicePixelRatio = 1.0;
//绘制UI的区域大小
Size get physicalSize => _physicalSize;
Size _physicalSize = Size.zero;
//获取矩形的物理像素
WindowPadding get viewInsets => _viewInsets;
WindowPadding _viewInsets = WindowPadding.zero;
//获取内边距
WindowPadding get padding => _padding;
WindowPadding _padding = WindowPadding.zero;
//当绘制区域改变时触发
VoidCallback get onMetricsChanged => _onMetricsChanged;
VoidCallback _onMetricsChanged;
Zone _onMetricsChangedZone;
set onMetricsChanged(VoidCallback callback) {
_onMetricsChanged = callback;
_onMetricsChangedZone = Zone.current;
}
//当系统语言发生变化时触发回调
Locale get locale {
if (_locales != null && _locales.isNotEmpty) {
return _locales.first;
}
return null;
}
//获取系统语言
List<Locale> get locales => _locales;
List<Locale> _locales;
//当Local发生改变时触发回调
VoidCallback get onLocaleChanged => _onLocaleChanged;
VoidCallback _onLocaleChanged;
Zone _onLocaleChangedZone;
set onLocaleChanged(VoidCallback callback) {
_onLocaleChanged = callback;
_onLocaleChangedZone = Zone.current;
}
//当系统字体大小改变时触发回调
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
VoidCallback _onTextScaleFactorChanged;
Zone _onTextScaleFactorChangedZone;
set onTextScaleFactorChanged(VoidCallback callback) {
_onTextScaleFactorChanged = callback;
_onTextScaleFactorChangedZone = Zone.current;
}
//屏幕亮度改变时触发回调
VoidCallback get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
VoidCallback _onPlatformBrightnessChanged;
Zone _onPlatformBrightnessChangedZone;
set onPlatformBrightnessChanged(VoidCallback callback) {
_onPlatformBrightnessChanged = callback;
_onPlatformBrightnessChangedZone = Zone.current;
}
//屏幕刷新时会回调
FrameCallback get onBeginFrame => _onBeginFrame;
FrameCallback _onBeginFrame;
Zone _onBeginFrameZone;
set onBeginFrame(FrameCallback callback) {
_onBeginFrame = callback;
_onBeginFrameZone = Zone.current;
}
//绘制屏幕时回调
VoidCallback get onDrawFrame => _onDrawFrame;
VoidCallback _onDrawFrame;
Zone _onDrawFrameZone;
set onDrawFrame(VoidCallback callback) {
_onDrawFrame = callback;
_onDrawFrameZone = Zone.current;
}
//点击或者指针事件触发回调
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback _onPointerDataPacket;
Zone _onPointerDataPacketZone;
set onPointerDataPacket(PointerDataPacketCallback callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}
//获取请求的默认路由
String get defaultRouteName => _defaultRouteName();
String _defaultRouteName() native 'Window_defaultRouteName';
//该方法被调用后,onBeginFrame和onDrawFrame将紧连着会在合适时机调用
void scheduleFrame() native 'Window_scheduleFrame';
//更新应用在GPU上的渲染,这方法会直接调用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
//窗口的语义内容是否改变
bool get semanticsEnabled => _semanticsEnabled;
bool _semanticsEnabled = false;
//当窗口语言发生改变时回调
VoidCallback get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
VoidCallback _onSemanticsEnabledChanged;
Zone _onSemanticsEnabledChangedZone;
set onSemanticsEnabledChanged(VoidCallback callback) {
_onSemanticsEnabledChanged = callback;
_onSemanticsEnabledChangedZone = Zone.current;
}
//当用户表达写的动作时回调
SemanticsActionCallback get onSemanticsAction => _onSemanticsAction;
SemanticsActionCallback _onSemanticsAction;
Zone _onSemanticsActionZone;
set onSemanticsAction(SemanticsActionCallback callback) {
_onSemanticsAction = callback;
_onSemanticsActionZone = Zone.current;
}
//启用其他辅助功能回调
VoidCallback get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
VoidCallback _onAccessibilityFeaturesChanged;
Zone _onAccessibilityFlagsChangedZone;
set onAccessibilityFeaturesChanged(VoidCallback callback) {
_onAccessibilityFeaturesChanged = callback;
_onAccessibilityFlagsChangedZone = Zone.current;
}
//更新此窗口的语义数据
void updateSemantics(SemanticsUpdate update) native 'Window_updateSemantics';
//设置Isolate调试名称
void setIsolateDebugName(String name) native 'Window_setIsolateDebugName';
//向特定平台发送消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw new Exception(error);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
//获取平台消息的回调
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
PlatformMessageCallback _onPlatformMessage;
Zone _onPlatformMessageZone;
set onPlatformMessage(PlatformMessageCallback callback) {
_onPlatformMessage = callback;
_onPlatformMessageZone = Zone.current;
}
//由_dispatchPlatformMessage调用
void _respondToPlatformMessage(int responseId, ByteData data)
native 'Window_respondToPlatformMessage';
//平台信息响应回调
static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
if (callback == null)
return null;
// Store the zone in which the callback is being registered.
final Zone registrationZone = Zone.current;
return (ByteData data) {
registrationZone.runUnaryGuarded(callback, data);
};
}
}
可以知道window
包含当前设备系统的一些信息和回调,那么现在看看混入的各种Binding
:
- GestureBinding:绑定手势子系统,提供
onPointerDataPacket
回调。 - ServicesBinding:绑定平台消息通道,提供
onPlatformMessage
回调。 - SchedulerBinding:绑定绘制调度子系统,提供
onBeginFrame
和onDrawFrame
回调。 - PaintingBinding:绑定绘制库,处理图像缓存。
- SemanticsBinding:语义层和flutter engine的桥梁,对辅助功能的底层支持。
- RendererBinding:是渲染树和Flutter engine的桥梁,提供
onMetricsChanged
和onTextScaleFactorChanged
回调。 - WidgetsBinding:是Flutter widget和engine的桥梁,提供
onLocaleChanged
,onBuildSchedule
回调。
也就是WidgetsFlutterBinding.ensureInitialized()
这行代码看名字是将WidgetsFlutterBinding
实例初始化。其实并非那么简单,另外获取一些系统基本信息和初始化监听window
对象的一些事件,然后将这些事件按照上层Framework模型规则进行包装、抽象最后分发。简而言之WidgetsFlutterBinding
是Flutter engine
和Framework
的桥梁。
2.attachRootWidget(app)
第二行是attachRootWidget(app)
,看名字意思是将根RootWidget
挂载。点进去看:
//如果这个widget有必要创建,就将它附加到[renderViewElement]
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}
renderView
是UI渲染树的根节点:
// The render tree that's attached to the output surface.
//挂载在渲染树 rootNode根节点
RenderView get renderView => _pipelineOwner.rootNode;
rootWidget
是外面所传进来的根Widget
,这里不用管它,下面看attachToRenderTree(buildOwner, renderViewElement)
这个方法,根据意思,这个方法应该构建RenderTree
,这个方法需要两个参数,首先看buildOwner
这个参数:
/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner;
final BuildOwner _buildOwner = BuildOwner();
看官网对它的定义:
image.png意思是:是widget framework的管理类,用来跟踪哪些widget需要重建,并处理widget树的其他任务,例如管理树的非活动元素列表,并在调试时在热重载期间在必要时触发“重组”命令,下面看另外一个参数renderViewElement
,代码注释如下:
/// The [Element] that is at the root of the hierarchy (and which wraps the
/// [RenderView] object at the root of the rendering hierarchy).
///
/// This is initialized the first time [runApp] is called.
Element get renderViewElement => _renderViewElement;
Element _renderViewElement;
renderViewElement
是renderView
对应的Element
对象,因为renderView
是树的根,所以renderViewElement
位于层次结构的根部,那下面点击attachToRenderTree
的源码看:
/// Inflate this widget and actually set the resulting [RenderObject] as the
/// child of [container].
///
/// If `element` is null, this function will create a new element. Otherwise,
/// the given element will have an update scheduled to switch to this widget.
///
/// Used by [runApp] to bootstrap applications.
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
创建根element
,element
和widget
进行关联。接着调用element.assignOwner(owner)
,这个方法其实就是设置这个element
的跟踪,最后调用owner.buildScope
这个方法,这个方法是确定更新widget
的范围。如果element
已经创建了,将根element
和关联的widget
设为新的,并且重新构建这个element
,为了后面的复用。
3.scheduleWarmUpFrame
runApp()方法最后一行执行scheduleWarmUpFrame
方法:
/// Schedule a frame to run as soon as possible, rather than waiting for
/// the engine to request a frame in response to a system "Vsync" signal.
///
/// This is used during application startup so that the first frame (which is
/// likely to be quite expensive) gets a few extra milliseconds to run.
///
/// Locks events dispatching until the scheduled frame has completed.
///
/// If a frame has already been scheduled with [scheduleFrame] or
/// [scheduleForcedFrame], this call may delay that frame.
///
/// If any scheduled frame has already begun or if another
/// [scheduleWarmUpFrame] was already called, this call will be ignored.
///
/// Prefer [scheduleFrame] to update the display in normal operation.
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
assert(_warmUpFrame);
handleBeginFrame(null);--->1
});
Timer.run(() {
assert(_warmUpFrame);
handleDrawFrame(); ---->2
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame's timestamp will typically be far in
// the past (the time of the last real frame), so if we didn't reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the "real" frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
首先这个方法在SchedulerBinding
里,这两个方法主要执行了handleBeginFrame
和handleDrawFrame
方法:
3.1.handleBeginFrame
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
可以看到主要是对transientCallbacks
队列操作,这个集合主要是放一些临时回调,存放动画回调。
3.2.handleDrawFrame
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS ----->
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS ------>
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
profile(() {
_profileFrameStopwatch.stop();
_profileFramePostEvent();
});
assert(() {
if (debugPrintEndFrameBanner)
debugPrint('▀' * _debugBanner.length);
_debugBanner = null;
return true;
}());
_currentFrameTimeStamp = null;
}
}
可以看到执行persistentCallbacks
队列,这个队列用于存放一些持久的回调,不能再此类回调中在请求新的绘制帧,持久回调一经注册则不能移除。接着执行postFrameCallbacks
这个队列在每一Frame
(一次绘制)结束时只会调用一次,调用后被系统移除。
也就是scheduleWarmUpFrame
这个方法安排帧尽快执行,当一次帧绘制结束之前不会响应各种事件,这样保证绘制过程中不触发重绘。上面说过:
RendererBinding:是渲染树和Flutter engine的桥梁,提供onMetricsChanged和onTextScaleFactorChanged回调
Flutter真正渲染和绘制是在这个绑定里:
4.渲染绘制
void initInstances() {
super.initInstances();
_instance = this;//初始化
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
//添加设置监听
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
//添加persistentFrameCallback
addPersistentFrameCallback(_handlePersistentFrameCallback);
//创建触摸管理
_mouseTracker = _createMouseTracker();
}
addPersistentFrameCallback
这个方法主要向persistentFrameCallback添加了回调:
void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback);
}
再看_handlePersistentFrameCallback
这个回调做了什么:
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();//更新布局信息
pipelineOwner.flushCompositingBits();//在flushLayout只后调用,在flushPaint之前调用,更新RenderObject是否需要重绘
pipelineOwner.flushPaint();//更新绘制RenderObject
renderView.compositeFrame(); // 发送bit数据给GPU
pipelineOwner.flushSemantics(); // 发送语义数据给操作系统
}
下面一个一个方法走:
4.1.flushLayout
void flushLayout() {
profile(() {
Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
});
assert(() {
_debugDoingLayout = true;
return true;
}());
try {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
assert(() {
_debugDoingLayout = false;
return true;
}());
profile(() {
Timeline.finishSync();
});
}
}
看源码得知首先获取哪些标记为脏
的RenderObject
的布局信息,然后通过ode._layoutWithoutResize();
重新调整这些RenderObject
。
4.2.flushCompositingBits
void flushCompositingBits() {
profile(() { Timeline.startSync('Compositing bits'); });
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
profile(() { Timeline.finishSync(); });
}
检查RenderObject
是否需要重绘,并且通过node._updateCompositingBits();
更新_needsCompositing
这个属性,若为true
就要重新绘制,否则不需要。
4.3.flushPaint
void flushPaint() {
profile(() { Timeline.startSync('Paint', arguments: timelineWhitelistArguments); });
assert(() {
_debugDoingPaint = true;
return true;
}());
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
//方向遍历这些标记过的node
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
//重新绘制
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
assert(_nodesNeedingPaint.isEmpty);
} finally {
assert(() {
_debugDoingPaint = false;
return true;
}());
profile(() { Timeline.finishSync(); });
}
}
这个方法通过反向遍历(dirty
标记)取得需要重绘的RenderObject
,最后通过PaintingContext.repaintCompositedChild(node);
重绘。
4.4.compositeFrame
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
//创建Scene对象
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
//使用render方法将Scene对象显示在屏幕上
_window.render(scene);//调用flutter engine的渲染API
scene.dispose();
assert(() {
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
return true;
}());
} finally {
Timeline.finishSync();
}
}
Scene
是用来保存渲染后的最终像素信息,这个方法将Canvas
画好的Scene
对象传给window.render()
方法,该方法会直接将Scene
信息发送给Flutter engine,最终Flutter engine将图像画在设备屏幕上,这样整个绘制流程就算完了。
注意:RendererBinding
只是混入对象,最终混入到WidgetsBinding
,回到最开始来看:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
/// Returns an instance of the [WidgetsBinding], creating and
/// initializing it if necessary. If one is created, it will be a
/// [WidgetsFlutterBinding]. If one was previously initialized, then
/// it will at least implement [WidgetsBinding].
///
/// You only need to call this method if you need the binding to be
/// initialized before calling [runApp].
///
/// In the `flutter_test` framework, [testWidgets] initializes the
/// binding instance to a [TestWidgetsFlutterBinding], not a
/// [WidgetsFlutterBinding].
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
所以应该WidgetsBinding
来重写实现drawFrame
方法:
@override
void drawFrame() {
assert(!debugBuildingDirtyElements);
assert(() {
debugBuildingDirtyElements = true;
return true;
}());
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //调用Renderbinding的drawFrame方法
buildOwner.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
profile(() {
if (_needToReportFirstFrame && _reportFirstFrame) {
developer.Timeline.instantSync('Widgets completed first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
_needToReportFirstFrame = false;
}
});
}
四、总结
-
widget
的功能是向element
提供配置信息,每一个widget
在Flutter里是一份配置数据,而代表屏幕背后的元素是element
,而真正的布局、渲染是通过RenderObject
来完成的,从创建到渲染的主要流程是:widget
信息生成element
,创建对应的RenderObject
关联到Element.renderObject
属性上,最后通过RenderObject
布局和绘制。 - Flutter从启动到显示图像在屏幕主要经过:首先监听处理
window
对象的事件,将这些事件处理包装为Framework模型进行分发,通过widget
创建element
树,接着通过scheduleWarmUpFrame
进行渲染,接着通过Rendererbinding
进行布局,绘制,最后通过调用ui.window.render(scene)
Scene信息发给Flutter engine,Flutter engine最后调用渲染API把图像画在屏幕上。
【附】相关架构及资料
image加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。