当前位置: 首页 > news >正文

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');
}

那么这个名为appWidget是怎样到界面上的呢? 开始吧…

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.addPersistentFrameCallbackSchedulerBinding.addPostFrameCallback方法安排的帧回调就是在这里进行处理的.

之后Flutter通过无限循环执行PlatformDispatcher.onBeginFramePlatformDispatcher.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.

这你纠正一点, 在ElementrenderObject是一个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!;
}

而之后子元素Elementmount方法就会在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中的onMeasureonLayout)
  • paint方法, 用来实现自绘(类似Android中的onDraw)
  • handleEvent方法, 用来处理手势事件(类似Android中的onTouchEvent)

我将在之后的文章中介绍Flutter中的自定义控件:

  • 自定义自绘Widget(类似于自定义Android中的View)
  • 自定义布局Widget(类似于自定义Android中的ViewGroup)

总结

Widget是什么?

用来变换成Element的配置对象.

Element是什么?

用来创建最终的RenderObject对象.

RenderObject是什么?

使用Canvas绘制的, 界面上能看到的都是绘制出来的. 其余类其实都是控制在什么地方绘制用的.

至此文章就结束了! 感谢读者的宝贵时间~


群内有各(pian)种(ni)各(jin)样(qun)的大佬,等你来撩.

联系作者

点此QQ对话 该死的空格 点此快速加群
在这里插入图片描述

在这里插入图片描述


http://www.mrgr.cn/news/8887.html

相关文章:

  • 高等数学精解【12】
  • shell程序设计入门(二)
  • 在Python中使用OpenCV录制视频并保存
  • 【Kubernetes】K8s 持久化存储方式
  • CSS的简单介绍
  • python小游戏——躲避球(可当课设)
  • Spark MLlib 特征工程系列—特征转换Polynomial Expansion
  • 【数据结构与算法】基数排序
  • 同步 异步
  • Spire.PDF for .NET【文档操作】演示:创建 PDF 组合
  • SpringMVC核心机制环境搭建
  • 编织网络之魂:Ruby网络编程指南
  • rt-studio+clion+cubemx联合使用(使用scons进行整合)
  • Python高光谱遥感数据处理与机器学习深度应用
  • 华为OD题目 csv格式的数据 字符串 用C没写出来
  • 分布式数据一致性小结
  • 2002-2023年中债国债3年期到期收益率
  • Golang测试func TestXX(t *testing.T)的使用
  • 【机器学习】 1. 总览介绍
  • openai whisper使用