写点什么

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:04786

评论

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

闭关修炼21天,“啃完”283页pdf,我终于4面拿下字节跳动offer

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

高防服务器大数据时代下的最佳应用途径

九河云安全

贡献者,是衡量开源项目的金指标

API7.ai 技术团队

开源 网关 APISIX

5招教你实现多线程场景下的线程安全

华为云开发者联盟

Java 线程 多线程 线程安全

耗时3年,从小厂逆袭,坐上美团L8技术专家(面经+心得)

Java 编程 程序员 架构 面试

知乎李大海对话阿里云贾扬清:透视AI应用难题与未来趋势

阿里云大数据AI技术

Redis扩展数据类型详解

码农参上

redis 8月日更

数据中台——数据汇聚存储技术解析

用友BIP

数据中台 数据存储

当容器应用越发广泛,我们又该如何监测容器?

阿里巴巴云原生

云计算 容器 云原生 监控 中间件

高防服务器,企业成长安全控制有效性的关键工具

九河云安全

差点跳起来了!全靠这份999页Java面试宝典,我刚拿到美团offer

Java~~~

Java 架构 面试 微服务 多线程

Asop 之 消息处理机制

Qunar技术沙龙

android Linux 消息队列 安卓 epoll

连续霸榜丨EasyDL到底有多强?

百度大脑

人工智能 EasyDL

直击美团“远程面试”现场,面试官竟反问:你真懂数据库事务吗?

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

读完这份JVM高级笔记,彻底玩转Java虚拟机,面试再也不用“虚”

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

FastApi-11-模板渲染

Python研究所

FastApi 8月日更

高防云服务器服务器的价值会随着时间而扩展,从"成本效率"扩展到"新服务和技术"

九河云安全

想聊天?自己搭建个聊天机器人吧!

百度开发者中心

人工智能 最佳实践 方法论 飞桨 语言 & 开发

对象存储手把手教四 | Bucket 生命周期管理

QingStor分布式存储

对象存储 分布式存储 生命周期 数据管理

云服务器市场改变了行业市场的发展规模

九河云安全

拒绝内卷!Github连夜封杀的阿里全套Spring Security高级笔记

Java 编程 架构 面试 程序人生

阿里(钉钉部门)远程面,三面坐上“直通车”,拿下offer没问题

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

2年5个月13天,从外包到拿下阿里offer,没想到屌丝也能有今天

Java~~~

Java spring 架构 面试 微服务

Go 效率工具集合

潇洒哥 - 老苗

Go 语言

大数据集群跨多版本升级、业务0中断,只因背后有TA

华为云开发者联盟

大数据 FusionInsight

跟我读论文丨ACL2021 NER BERT化隐马尔可夫模型用于多源弱监督命名实体识别

华为云开发者联盟

BERT 弱监督 隐马尔可夫 CHMM HMM模型

7金5银,中国跳水梦之队背后的"黑科技"是什么?

百度大脑

人工智能 黑科技 跳水队

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Java~~~

Java 架构 面试 微服务 多线程

数据库的简述与常用操作指南

行者AI

数据库

企业在运营过程中需要解决的五项网络安全项目

九河云安全

YYDS《剑指Offer》再续新篇,百万程序员人手一册

博文视点Broadview

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