写点什么

Flutter 的渲染逻辑,以及与 Native 通信(一)

  • 2019-11-30
  • 本文字数:3646 字

    阅读完需:约 12 分钟

Flutter 的渲染逻辑,以及与 Native 通信(一)

本文来自 RTC 开发者社区(rtcdeveloper.com)的用户投稿,作者是一位资深 Android 工程师,熟悉跨平台开发。在这篇文章中,主要包含两个部分内容, Flutter 的渲染逻辑,以及 Flutter 和 Native 互通的方法,这里的 Native 是以 Android 为例。对于在 Flutter 上构建实时音视频场景开发者有一定的参考意义。

1 Flutter 渲染

在 Android 中,我们所说的 View 的渲染逻辑指的是 onMeasure(), onLayout(), onDraw(), 我们只要重写这三个方法就可以自定义出符合我们需求的 View。其实,即使我们不懂 Android 中 View 的渲染逻辑,也能写出大部分的 App,但是当系统提供的 View 满足不了我们的需求的时候,这时就需要我们自定义 View 了,而自定义 View 的前提就是要知道 View 的渲染逻辑。


Flutter 中也一样,系统提供的 Widget 可以满足我们大部分的需求,但是在一些情况下我们还是得渲染自己的 Widget。


和 Android 类似,Flutter 中的渲染也会经历几个必要的阶段,如下:


  • Layout : 布局阶段,Flutter 会确定每一个子 Widget 的大小和他们在屏幕中将要被放置的位置。

  • Paint : 绘制阶段,Flutter 为每个子 Widget 提供一个 canvas,并让他们绘制自己。

  • Composite : 组合阶段,Flutter 会将所有的 Widget 组合在一起,并交由 GPU 处理。


上面三个阶段中,比较重要的就是 Layout 阶段了,因为一切都始于布局。


在 Flutter 中,布局阶段会做两个事情:父控件将 约束(Constraints) 向下传递到子控件;子控件将自己的 布局详情(Layout Details) 向上传递给父控件。如下图:



布局过程如下:


这里我们将父 widget 称为 parent;将子 widget 称为 child


  1. parent 会将某些布局约束传递给 child,这些约束是每个 child 在 layout 阶段必须要遵守的。如同 parent 这样告诉 child :“只要你遵守这些规则,你可以做任何你想做的事”。最常见的就是 parent 会限制 child 的大小,也就是 child 的 maxWidth 或者 maxHeight。

  2. 然后 child 会根据得到的约束生成一个新的约束,并将这个新的约束传递给自己的 child(也就是 child 的 child),这个过程会一直持续到出现没有 child 的 widget 为止。

  3. 之后,child 会根据 parent 传递过来的约束确定自己的布局详情(Layout Details)。如:假设 parent 传递给 child 的最大宽度约束为 500px,child 可能会说:“好吧,那我就用 500px”,或者 “我只会用 100px”。这样,child 就确定了自己的布局详情,并将其传递给 parent。

  4. parent 反过来做同样的事情,它根据 child 传递回来的 Layout Details 来确定其自身的 Layout Details,然后将这些 Layout Details 向上层的 parent 传递,直到到达 root widget (根 widget)或者遇到了某些限制。


那我们上面所提到的 约束(Constraints) 和 布局详情(Layout Details) 都是什么呢?这取决于布局协议(Layout protocol)。Flutter 中有两种主要的布局协议:Box Protocol 和 Sliver Protocol,前者可以理解为类似于盒子模型协议,后者则是和滑动布局相关的协议。这里我们以前者为例。


在 Box Protocol 中,parent 传递给 child 的约束都叫做 BoxConstraints 这些约束决定了每个 child 的 maxWidth 和 maxHeight 以及 minWidth 和 minHeight。如:parent 可能会将如下的 BoxConstraints 传递给 child。



上图中,浅绿色的为 parent,浅红色的小矩形为 child。 那么,parent 传递给 child 的约束就是 150 ≤ width ≤ 300, 100 ≤ height ≤ 无限大 而 child 回传给 parent 的布局详情就是 child 的尺寸(Size)。


有了 child 的 Layout Details ,parent 就可以绘制它们了。


在我们渲染自己的 widget 之前,先来了解下另外一个东西 Render Tree。


Render Tree


我们在 Android 中会有 View tree,Flutter 中与之对应的为 Widget tree,但是 Flutter 中还有另外一种 tree,称为 Render tree。


在 Flutter 中 我们常见的 widget 有 StatefulWidget,StatelessWidget,InheritedWidget 等等。但是这里还有另外一种 widget 称为 RenderObjectWidget,这个 widget 中没有 build() 方法,而是有一个 createRenderObject() 方法,这个方法允许创建一个 RenderObject 并将其添加到 render tree 中。


RenderObject 是渲染过程中非常重要的组件,render tree 中的内容都是 RenderObject,每个 RenderObject 中都有许多用来执行渲染的属性和方法:


  • constraints : 从 parent 传递过来的约束。

  • parentData: 这里面携带的是 parent 渲染 child 的时候所用到的数据。

  • performLayout():此方法用于布局所有的 child。

  • paint():这个方法用于绘制自己或者 child。

  • 等等…


但是,RenderObject 是一个抽象类,他需要被子类继承来进行实际的渲染。RenderObject 的两个非常重要的子类是 RenderBox 和 RenderSliver 。这两个类是所有实现 Box Protocol 和 Sliver Protocol 的渲染对象的父类。而且这两个类还扩展了数十个和其他几个处理特定场景的类,并且实现了渲染过程的细节。


现在我们开始渲染自己的 widget,也就是创建一个 RenderObject。这个 widget 需要满足下面两点要求:


  • 它只会给 child 最小的宽和高

  • 它会把它的 child 放在自己的右下角


如此 “小气” 的 widget ,我们就叫他 Stingy 吧!Stingy 所属的树形结构如下:


MaterialApp  |_Scaffold  |_Container      // Stingy 的 parent    |_Stingy      // 自定义的 RenderObject      |_Container   // Stingy 的 child
复制代码


代码如下:


void main() {  runApp(MaterialApp(    home: Scaffold(      body: Container(        color: Colors.greenAccent,        constraints: BoxConstraints(            maxWidth: double.infinity,            minWidth: 100.0,            maxHeight: 300,            minHeight: 100.0),        child: Stingy(          child: Container(            color: Colors.red,          ),        ),      ),    ),  ));}
复制代码


Stingy


class Stingy extends SingleChildRenderObjectWidget {  Stingy({Widget child}) : super(child: child);
@override RenderObject createRenderObject(BuildContext context) { // TODO: implement createRenderObject return RenderStingy(); }}
复制代码


Stingy 继承了 SingleChildRenderObjectWidget,顾名思义,他只能有一个 child 而 createRenderObject(…) 方法创建并返回了一个 RenderObject 为 RenderStingy 类的实例


RenderStingy


class RenderStingy extends RenderShiftedBox {  RenderStingy() : super(null);  // 绘制方法  @override  void paint(PaintingContext context, Offset offset) {      // TODO: implement paint    super.paint(context, offset);  }     // 布局方法  @override  void performLayout() {       // 布局 child 确定 child 的 size    child.layout(        BoxConstraints(            minHeight: 0.0,            maxHeight: constraints.minHeight,            minWidth: 0.0,            maxWidth: constraints.minWidth),        parentUsesSize: true);        print('constraints: $constraints');             // child 的 Offset    final BoxParentData childParentData = child.parentData;    childParentData.offset = Offset(constraints.maxWidth - child.size.width,        constraints.maxHeight - child.size.height);    print('childParentData: $childParentData');    // 确定自己(Stingy)的大小 类似于 Android View 的 setMeasuredDimension(...)    size = Size(constraints.maxWidth, constraints.maxHeight);    print('size: $size');  }}
复制代码


RenderStingy 继承自 RenderShiftedBox,该类是继承自 RenderBox。RenderShiftedBox 实现了 Box Protocol 所有的细节,并且提供了 performLayout() 方法的实现。我们需要在 performLayout() 方法中布局我们的 child,还可以设置他们的偏移量。


我们在使用 child.layout(…) 方法布局 child 的时候传递了两个参数,第一个为 child 的布局约束,而另外一个参数是 parentUserSize, 该参数如果设置为 false,则意味着 parent 不关心 child 选择的大小,这对布局优化比较有用;因为如果 child 改变了自己的大小,parent 就不必重新 layout 了。但是在我们的例子中,我们的需要把 child 放置在 parent 的右下角,这意味着如果 child 的大小(Size)一旦改变,则其对应的偏移量(Offset) 也会改变,这就意味着 parent 需要重新布局,所以我们这里传递了一个 true。


当 child.layout(…) 完成了以后,child 就确定了自己的 Layout Details。然后我们就还可以为其设置偏移量来将它放置到我们想放的位置。在我们的例子中为 右下角。


最后,和 child 根据 parent 传递过来的约束选择了一个尺寸一样,我们也需要为 Stingy 选择一个尺寸,以至于 Stingy 的 parent 知道如何放置它。类似于在 Android 中我们自定义 View 重写 onMeasure(…) 方法的时候需要调用 setMeasuredDimension(…) 一样。


运行效果如下:



2019-11-30 15:04658

评论

发布
暂无评论
发现更多内容

成功的在线视频流的主要特征是什么?

3DCAT实时渲染

实时云渲染 云流化

极狐GitLab CI/CD 变量黑魔法之自定义变量

极狐GitLab

DevOps gitlab 变量 CI/CD gitlab ci

聊点写作配图那点破事儿

6点无痛早起学习的和尚

写作 21 天技术人写作行动营

50mA、24V、超低 IQ、低压降稳压器

芯动大师

对大模型和AI的认识与思考

AIWeker

AI 总结 大模型 AIGC GPT

达芬奇DaVinci Resolve Studio 18:视频后期处理的全能神器

Rose

达芬奇18破解版 DaVinci Resolve Studio 达芬奇调色软件 DaVinci18密钥

如何在 Parallels Desktop for Mac 中备份、恢复或转移 Windows 虚拟机

Rose

Mac虚拟机下载 Parallels Desktop 19 Pd虚拟机破解版下载 Parallels 永久密钥

【云原生•监控】基于Prometheus的云原生集群监控(理论+实践)-02

Reactor2020

监控 Prometheus kubernetes 运维

JVM 小科普:Java 程序背后的老大哥

Java 工程师蔡姬

Java 程序员 JVM 21 天技术人写作行动营

Waves 14混音效果全套插件 附Waves mac破解补丁

Rose

Waves 14破解教程 Waves 14 最新版 Waves 14 Complete破解 后期混音

AlDente Pro激活码 v1.24 Macbook充电限制软件

Rose

AlDente Pro破解版 AlDente 激活码 Macbook充电限制软件

概念回顾:QUIC 和 HTTP/3

NGINX开源社区

TLS udp 数据流 QUIC HTTP/3

AI+无代码助力企业供应链优化

明道云

多链铭文聚合协议20Exchange将为Ordi、SATS持仓用户快照空投

BlockChain先知

文心一言 VS 讯飞星火 VS chatgpt (155)-- 算法导论12.3 2题

福大大架构师每日一题

福大大架构师每日一题

以太坊 NFT 市场激增:为什么要在 2024 年推出 NFT 市场?

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

C 语言函数:入门指南

小万哥

c c++ 程序员 后端 软件开发

FxFactory 8 Pro:打造专业视觉效果的利器

Rose

视频特效插件 FxFactory 8 pro

32 | 字符串匹配基础(上):如何借助哈希算法实现高效字符串匹配

鲁米

sublime text 中文破解版 附sublimetext注册码 Mac/win 文本代码编辑器

Rose

代码编辑器 mac软件下载 Sublime Text 4 破解版 Sublime Text注册码

【亲测有效】Sketch for mac v99.1中文一键安装破解版

Rose

mac软件下载 矢量绘图设计 Sketch 99 Sketch中文破解

多链铭文聚合协议20Exchange将为Ordi、SATS持仓用户快照空投

大瞿科技

【云原生•监控】基于Prometheus的云原生集群监控(理论+实践)-03

Reactor2020

监控 Prometheus kubernetes 运维

2023年11月文章一览

codists

beyondcompare4永久激活密钥 及Beyond Compare 4破解方法

Rose

mac软件下载 文件同步对比工具 Beyond Compare 4 注册版 Beyond Compare密钥

明道云在戴斯克,从业务整合到高效管理的秘诀

明道云

多链铭文聚合协议20Exchange将为Ordi、SATS持仓用户快照空投

石头财经

Axure RP 10中文授权版下载

Rose

原型设计 Axure RP 10 汉化版 Axure RP 10 下载 Axure RP 10授权

Navicat Premium 15永久激活教程 Mac/win 数据库管理软件

Rose

数据库管理工具 Navicat Premium破解版 Navicat Premium 15中文版 Navicat 数据库下载

【云原生•监控】基于Prometheus的云原生集群监控(理论+实践)-01

Reactor2020

监控 Prometheus kubernetes 运维

Flutter 的渲染逻辑,以及与 Native 通信(一)_文化 & 方法_声网_InfoQ精选文章