Flutter-->Widget上屏之路
本文主要介绍Flutter
中创建一个Widget
到屏幕上渲染出Widget内容
的路程.
拾用本文您将获得:
Widget
是什么Element
是什么RenderObject
是什么
附加Buff
:
Widget
直接渲染成图片- 文本
String
的绘制 - 图片
ui.Image
的绘制
这一切都要从runApp
方法开始说起, 如果你还不知道runApp
是什么, 建议从
https://docs.flutter.dev/ui 开始阅读…
runApp
runApp
方法就是进入Flutter
世界的入口, 方法参数也是唯一的参数就是一个Widget
void runApp(Widget app) {final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
那么这个名为app
的Widget
是怎样到界面上的呢? 开始吧…
Element
要想将Widget
渲染上屏, 首先就需要将Widget
变换成Element
.
所以在runApp
方法的执行链上, 很容易就能跟踪到下面的代码:
void attachToBuildOwner(RootWidget widget) {final bool isBootstrapFrame = rootElement == null;_readyToProduceFrames = true;// 请注意这里_rootElement = widget.attach(buildOwner!, rootElement as RootElement?);if (isBootstrapFrame) {SchedulerBinding.instance.ensureVisualUpdate();}
}
关键代码widget.attach(buildOwner!, rootElement as RootElement?)
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {if (element == null) {owner.lockState(() {element = createElement();assert(element != null);element!.assignOwner(owner);});owner.buildScope(element!, () {element!.mount(/* parent */ null, /* slot */ null);});} else {element._newWidget = this;element.markNeedsBuild();}return element!;
}
上述代码,就是将一个Widget
变换成Element
的关键代码. 请注意上面的代码, 因为这玩意在另一个类中原封不动的也出现过.
那就是RenderObjectToWidgetAdapter
如下:
/// 代码来自[RenderObjectToWidgetAdapter.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!;
}
Widget
变换成Element
同样也是将Widget
渲染成图片的关键步骤.
到这一步WidgetsBinding.rootElement
就赋值完成了, 接下来就是等待屏幕信号刷新帧,开始渲染了…
上述attachToBuildOwner
方法中, 有一行代码SchedulerBinding.instance.ensureVisualUpdate()
就是用来安排刷新帧的, 触发之后, 等待屏幕信号即可.
/// 代码来自[SchedulerBinding.ensureVisualUpdate]
void ensureVisualUpdate() {switch (schedulerPhase) {case SchedulerPhase.idle:case SchedulerPhase.postFrameCallbacks:scheduleFrame();return;case SchedulerPhase.transientCallbacks:case SchedulerPhase.midFrameMicrotasks:case SchedulerPhase.persistentCallbacks:return;}
}
/// 代码来自[SchedulerBinding.scheduleFrame]
void scheduleFrame() {if (_hasScheduledFrame || !framesEnabled) {return;}assert(() {if (debugPrintScheduleFrameStacks) {debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');}return true;}());ensureFrameCallbacksRegistered();platformDispatcher.scheduleFrame();_hasScheduledFrame = true;
}void ensureFrameCallbacksRegistered() {platformDispatcher.onBeginFrame ??= _handleBeginFrame;platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
PlatformDispatcher.onBeginFrame
调用链路SchedulerBinding.ensureVisualUpdate
->SchedulerBinding.scheduleFrame
->SchedulerBinding._handleBeginFrame
->SchedulerBinding.handleBeginFrame
->SchedulerBinding._invokeFrameCallback
平时通过SchedulerBinding.scheduleFrameCallback
方法安排的帧回调就是在SchedulerBinding._invokeFrameCallback
方法中执行的.
PlatformDispatcher.onDrawFrame
调用链路SchedulerBinding.ensureVisualUpdate
->SchedulerBinding.scheduleFrame
->SchedulerBinding._handleDrawFrame
->SchedulerBinding.handleDrawFrame
->SchedulerBinding._invokeFrameCallback
平时通过SchedulerBinding.addPersistentFrameCallback
和SchedulerBinding.addPostFrameCallback
方法安排的帧回调就是在这里进行处理的.
之后Flutter
通过无限循环执行PlatformDispatcher.onBeginFrame
和PlatformDispatcher.onDrawFrame
方法渲染出精美的软件界面.
读到这里, 你是否注意到和WidgetsBinding.rootElement
似乎一毛钱关系都没有呢?
不慌, 它在这里…
WidgetsFlutterBinding.ensureInitialized
还记得runApp
方法吗?
void runApp(Widget app) {final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
这方法头一句就是WidgetsFlutterBinding.ensureInitialized
, 来看看它的神秘面纱.
/// 代码来自[WidgetsFlutterBinding.ensureInitialized]
static WidgetsBinding ensureInitialized() {if (WidgetsBinding._instance == null) {WidgetsFlutterBinding();}return WidgetsBinding.instance;
}
聪明的你, 应该看出来了, 就是创建了一个单例WidgetsFlutterBinding
对象. 您可千万不要被它的表象所迷惑, 这玩意可是一个巨兽…
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding
当您创建WidgetsFlutterBinding
对象后会执行父类的构造方法BindingBase
BindingBase() {if (!kReleaseMode) {FlutterTimeline.startSync('Framework initialization');}assert(() {_debugConstructed = true;return true;}());assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');//注意这里initInstances();assert(_debugInitializedType != null);assert(!_debugServiceExtensionsRegistered);initServiceExtensions();assert(_debugServiceExtensionsRegistered);if (!kReleaseMode) {developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});FlutterTimeline.finishSync();}
}
会触发BindingBase.initInstances
方法, 此方法会依次执行:
GestureBinding.initInstances
主要用来执行屏幕手势回调处理
void initInstances() {super.initInstances();_instance = this;platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
SchedulerBinding.initInstances
在开发阶段用来计算帧率时间等
void initInstances() {super.initInstances();_instance = this;if (!kReleaseMode) {addTimingsCallback((List<FrameTiming> timings) {timings.forEach(_profileFramePostEvent);});}
}
ServicesBinding.initInstances
主要用于平台服务支持等
void initInstances() {super.initInstances();_instance = this;_defaultBinaryMessenger = createBinaryMessenger();_restorationManager = createRestorationManager();_initKeyboard();initLicenses();SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);platformDispatcher.onViewFocusChange = handleViewFocusChanged;TextInput.ensureInitialized();readInitialLifecycleStateFromNativeWindow();initializationComplete();
}
PaintingBinding.initInstances
没想到吧,Flutter
原生就有图片缓存池
void initInstances() {super.initInstances();_instance = this;_imageCache = createImageCache();shaderWarmUp?.execute();
}
SemanticsBinding.initInstances
平台一些语义,无障碍服务等
void initInstances() {super.initInstances();_instance = this;_accessibilityFeatures = platformDispatcher.accessibilityFeatures;platformDispatcher..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged..onSemanticsActionEvent = _handleSemanticsActionEvent..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;_handleSemanticsEnabledChanged();
}
RendererBinding.initInstances
Flutter
引擎渲染调度核心
void initInstances() {super.initInstances();_instance = this;_rootPipelineOwner = createRootPipelineOwner();platformDispatcher..onMetricsChanged = handleMetricsChanged..onTextScaleFactorChanged = handleTextScaleFactorChanged..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;addPersistentFrameCallback(_handlePersistentFrameCallback);initMouseTracker();if (kIsWeb) {addPostFrameCallback(_handleWebFirstFrame, debugLabel: 'RendererBinding.webFirstFrame');}rootPipelineOwner.attach(_manifold);
}
注意, 注意, 注意, 这个addPersistentFrameCallback(_handlePersistentFrameCallback)
方法就是无限循环渲染的关键. addPersistentFrameCallback
方法, 在前面已经介绍过了, 是不是忘记了? 往上翻一翻~~
WidgetsBinding.initInstances
一些和Widget
有关的操作
void initInstances() {super.initInstances();_instance = this;assert(() {_debugAddStackFilters();return true;}());// Initialization of [_buildOwner] has to be done after// [super.initInstances] is called, as it requires [ServicesBinding] to// properly setup the [defaultBinaryMessenger] instance._buildOwner = BuildOwner();buildOwner!.onBuildScheduled = _handleBuildScheduled;platformDispatcher.onLocaleChanged = handleLocaleChanged;SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);SystemChannels.backGesture.setMethodCallHandler(_handleBackGestureInvocation,);assert(() {FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);return true;}());platformMenuDelegate = DefaultPlatformMenuDelegate();
}
去掉杂念, 让我们关注 _handlePersistentFrameCallback方法:
/// 代码来自[RendererBinding._handlePersistentFrameCallback]
void _handlePersistentFrameCallback(Duration timeStamp) {drawFrame();_scheduleMouseTrackerUpdate();
}/// 代码来自[RendererBinding.drawFrame]
void drawFrame() {rootPipelineOwner.flushLayout();rootPipelineOwner.flushCompositingBits();rootPipelineOwner.flushPaint();if (sendFramesToEngine) {for (final RenderView renderView in renderViews) {renderView.compositeFrame(); // this sends the bits to the GPU}rootPipelineOwner.flushSemantics(); // this sends the semantics to the OS._firstFrameSent = true;}
}
注意到drawFrame
方法了吗? 此方法在WidgetsBinding.drawFrame
被重写了:
void drawFrame() {assert(!debugBuildingDirtyElements);assert(() {debugBuildingDirtyElements = true;return true;}());TimingsCallback? firstFrameCallback;bool debugFrameWasSentToEngine = false;if (_needToReportFirstFrame) {assert(!_firstFrameCompleter.isCompleted);firstFrameCallback = (List<FrameTiming> timings) {assert(debugFrameWasSentToEngine);if (!kReleaseMode) {// Change the current user tag back to the default tag. At this point,// the user tag should be set to "AppStartUp" (originally set in the// engine), so we need to change it back to the default tag to mark// the end of app start up for CPU profiles.developer.UserTag.defaultTag.makeCurrent();developer.Timeline.instantSync('Rasterized first useful frame');developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});}SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);firstFrameCallback = null;_firstFrameCompleter.complete();};// Callback is only invoked when FlutterView.render is called. When// sendFramesToEngine is set to false during the frame, it will not be// called and we need to remove the callback (see below).SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!);}try {//注意此处if (rootElement != null) {buildOwner!.buildScope(rootElement!);}super.drawFrame();assert(() {debugFrameWasSentToEngine = sendFramesToEngine;return true;}());buildOwner!.finalizeTree();} finally {assert(() {debugBuildingDirtyElements = false;return true;}());}if (!kReleaseMode) {if (_needToReportFirstFrame && sendFramesToEngine) {developer.Timeline.instantSync('Widgets built first useful frame');}}_needToReportFirstFrame = false;if (firstFrameCallback != null && !sendFramesToEngine) {// This frame is deferred and not the first frame sent to the engine that// should be reported._needToReportFirstFrame = true;SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);}
}
注意到buildOwner!.buildScope(rootElement!);
了吗?
Element
被丢到BuildOwner
对象中, 然后在super.drawFrame
方法中调用了PipelineOwner.flushPaint
完成一帧渲染的所有流程.
此流程在将Widget
直接渲染成图片如出一辙,
直接将Widget
渲染成图片代码如下:
Future<ui.Image> buildImage(Widget widget) async {final repaintBoundary = RenderRepaintBoundary();final view = ui.PlatformDispatcher.instance.implicitView ??RendererBinding.instance.renderView.flutterView;//渲染树根final renderView = RenderView(view: view,child: RenderPositionedBox(alignment: Alignment.center,child: repaintBoundary,),configuration: ViewConfiguration.fromView(view),);//管道final pipelineOwner = PipelineOwner()..rootNode = renderView;renderView.prepareInitialFrame();//管道对象final buildOwner = BuildOwner(focusManager: FocusManager());//根元素final rootElement = RenderObjectToWidgetAdapter<RenderBox>(container: repaintBoundary,child: Directionality(textDirection: TextDirection.ltr,child: widget,)).attachToRenderTree(buildOwner);//构建树buildOwner..buildScope(rootElement)..finalizeTree();//渲染树pipelineOwner..flushLayout()..flushCompositingBits()..flushPaint();final image = await repaintBoundary.toImage();return image;}
总结
runApp
方法的参数app
是怎么到界面上的?
- 使用
RootWidget
包裹app
Widget
- 然后使用
RootWidget.attach
方法将Widget
转变成RootElement
Element
被丢到BuildOwner
对象中- 然后调用
PipelineOwner.flushPaint
完成一帧渲染
到这里, 我们还有一个东西没有介绍RenderObject
, 它要来了…
RenderObject
首先, 并不是所有的Element
都有RenderObject
,
而Element
又是由Widget
变换来的, 所以并不是所有的Widget
都需要RenderObject
.
这你纠正一点, 在Element
中renderObject
是一个get
方法, 所以Element
能获取到renderObject
, 但不一定有值…
/// 代码来自[Element.renderObject]
RenderObject? get renderObject {Element? current = this;while (current != null) {if (current._lifecycleState == _ElementLifecycle.defunct) {break;} else if (current is RenderObjectElement) {return current.renderObject;} else {current = current.renderObjectAttachingChild;}}return null;
}
在Flutter
系统里面, 只有RenderObjectElement
才有renderObject
, RenderObjectWidget
会默认创建RenderObjectElement
, 所以…
/// 代码来自[RenderObjectElement.renderObject]
RenderObject get renderObject {assert(_renderObject != null, '$runtimeType unmounted');return _renderObject!;
}
RenderObject? _renderObject; //注意这里
这里顺便说一下, 平时使用的
Text
小部件, 文本InlineSpan
是通过渲染对象RenderObject
->RenderParagraph
使用TextPainter
绘制出来的.
平时使用的Image
小部件, 图片ui.Image
是通过渲染对象RenderObject
>RenderImage
使用paintImage
方法绘制出来的
Element
会经历->Element.mount
->Element.update
->Element.unmount
可能不全, 但最核心的就这几个生命周期的回调.
RootElement
根元素的mount
方法在RootWidget.attach
中的BuildOwner.buildScope
方法中调用, 代码在之前已经提到过, 这里再来一次, 不劳烦翻页了.
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {if (element == null) {owner.lockState(() {element = createElement();assert(element != null);element!.assignOwner(owner);});owner.buildScope(element!, () {//注意这里element!.mount(/* parent */ null, /* slot */ null);});} else {element._newWidget = this;element.markNeedsBuild();}return element!;
}
而之后子元素Element
的mount
方法就会在inflateWidget
方法中调用了:
Element inflateWidget(Widget newWidget, Object? newSlot) {...final Element newChild = newWidget.createElement();...//注意这里newChild.mount(this, newSlot);...return newChild;} finally {if (isTimelineTracked) {FlutterTimeline.finishSync();}}
}
方法调用链:RootElement.mount
->RootElement._rebuild
->Element.updateChild
->Element.inflateWidget
->Widget.createElement
->Element.mount
火车就这样开起来了…
RenderObjectElement._renderObject
对象就是在RenderObjectElement.mount
方法中调用RenderObjectWidget.createRenderObject
方法赋值的.
/// 代码来自[RenderObjectElement.mount]
void mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);assert(() {_debugDoingBuild = true;return true;}());//注意此处_renderObject = (widget as RenderObjectWidget).createRenderObject(this);assert(!_renderObject!.debugDisposed!);assert(() {_debugDoingBuild = false;return true;}());assert(() {_debugUpdateRenderObjectOwner();return true;}());assert(slot == newSlot);attachRenderObject(newSlot);super.performRebuild(); // clears the "dirty" flag
}
然后RenderObject.paint
方法会被调用, 用来绘制, 里面有熟悉的canvas
对象, 这对于Android
开发的同学, 再熟悉不过了把?
/// 代码来自[RenderObject.paint]
void paint(PaintingContext context, Offset offset) { final canvas = context.canvas;
}
RenderObject
对象中有:
performLayout
方法, 用来实现布局(类似Android
中的onMeasure
和onLayout
)paint
方法, 用来实现自绘(类似Android
中的onDraw
)handleEvent
方法, 用来处理手势事件(类似Android
中的onTouchEvent
)
我将在之后的文章中介绍Flutter
中的自定义控件:
- 自定义自绘
Widget
(类似于自定义Android
中的View
) - 自定义布局
Widget
(类似于自定义Android
中的ViewGroup
)
总结
Widget
是什么?
用来变换成Element
的配置对象.
Element
是什么?
用来创建最终的RenderObject
对象.
RenderObject
是什么?
使用Canvas
绘制的, 界面上能看到的都是绘制出来的. 其余类其实都是控制在什么地方绘制用的.
至此文章就结束了! 感谢读者的宝贵时间~
群内有各(pian)种(ni)各(jin)样(qun)
的大佬,等你来撩.
联系作者
点此QQ对话 该死的空格
点此快速加群