4-Flutter基础知识

Flutter 的绘制流程

  1. 我们的写的代码是 Widget 。Widget 是配置(描述了 UI 应该是什么样子的),不做布局和绘制,他会被 inflate(填充)到 Element
  2. Element 是桥梁,持有 Widget 和 RenderObject,负责管理 Widget 的生命周期、Diff、更新。
  3. RenderObject 负责布局和绘制
  4. Widget Element 是多对一的关系,但 Element 对 RenderObject 是一一对应
  5. BuildContext 就是 Widget 对应的 Element,可以访问树结构和 RenderObject
  6. 在第一次创建 Widget 的时候,会对应创建一个 Element,然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已

Widget 与 Element 的关系

  • 多对一(很多 Widget 可以对应同一个 Element):
    • 其实更准确的说法是:一个 Element 会持有它当前对应的 Widget
    • 当你调用 setState 或刷新 UI 时,Flutter 并不会每次都创建新的 Element。它会复用现有 Element,只更新 Element 持有的 Widget 配置。
    • 所以一个 Element 在生命周期中可以绑定不同的 Widget(不同的配置),但同一时间只对应一个 Widget。
    • Widget 是不可变对象,每次 build 都会生成新 Widget。如果没有 Element,每次更新 UI 都要销毁旧的 RenderObject,再创建新的 RenderObject → 开销大(性能差)。

为什么不直接用 Widget?
Widget 本身不可变,而且 Widget 很轻量,每次刷新可能会生成很多新的 Widget。
Element 才是“长久驻扎”的实体,它会保存状态、管理生命周期和 RenderObject。

key

在使用 Flutter 时,我们经常会遇到一个叫做 Key 的东西。Key 是 Flutter 中几乎所有 widget 都具有的属性。

Flutter 将 Key 描述为 Widget、Element 和 SemanticNodes 的标识符。这是什么意思呢?这意味着 Key 是分配给 Widget 的唯一标识,通过 key 可以与其他 Widget 区分开来。对于 Widget 在 Widget 树中改变位置的情况,Key 帮助保留它们的状态。说明 Key 大多数情况下对于有状态的 Widget 而言更有用,而对于无状态的 Widget 则不太需要。

GlobalKey

  • 跨 Widget 树查找 Element/State。
  • 可以用 globalKey.currentStateglobalKey.currentContext 获取实例。
  • 一般用于 FormScaffoldNavigator 等场景。

Key 是 Widget 的身份标识,决定了 Widget/Element/RenderObject 是否复用。

  • 没有 Key:按位置复用。
  • 有 Key:按 Key 匹配,避免错位复用。

常用场景
- ListView / GridView 子节点(复用/顺序变化)。
- 动画组件(防止错乱)。
- GlobalKey 获取状态或上下文。

屏幕适配

  1. 使用屏幕尺寸的相对值:避免直接使用固定的像素值来定义尺寸,而是使用相对于屏幕尺寸的比例值。可以使用 MediaQuery. of (context). size 获取屏幕的宽度和高度,然后根据需求进行比例计算。
  2. 使用布局容器:使用诸如 Container、Expanded、Flexible 等布局容器来自动适应父容器的大小。这样可以确保界面在不同屏幕尺寸下的自适应性。
  3. 使用响应式布局:使用 Flutter 提供的响应式布局框架,如 LayoutBuilder 和 FractionallySizedBox,来根据屏幕尺寸动态调整布局。
  4. 使用自适应字体大小:根据屏幕尺寸和设备像素比例,使用 MediaQuery. of (context). textScaleFactor 来调整字体的大小,以确保在不同设备上字体的可读性和一致性。
  5. 使用屏幕方向适配:根据屏幕的方向(横向或纵向),调整布局和元素的摆放方式,以提供更好的用户体验。
  6. 使用 Flutter 插件:有一些 Flutter 插件和库可用于简化屏幕适配的工作,如 flutter_screenutil、sizer 等,它们提供了更方便的方法和工具来进行屏幕适配。

分层

Flutter 框架自下而上分为 Embedder、Engine 和 Framework 三层。

  • Embedder 是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配;
  • Engine 层负责图形绘制、文字排版和提供 Dart 运行时,Engine 层具有独立虚拟机,正是由于它的存在,Flutter 程序才能运行在不同的平台上,实现跨平台运行;
  • Framework 层则是使用 Dart 编写的一套基础视图库,包含了动画、图形绘制和手势识别等功能,是使用频率最高的一层。
    image.png|600

FrameWork 层和 Engine 层,以及它们的作用

  • Flutter的FrameWork层是用Drat编写的框架(SDK),它实现了一套基础库,包含Material(Android风格UI)和Cupertino(iOS风格)的UI界面,下面是通用的Widgets(组件),之后是一些动画、绘制、渲染、手势库等。这个纯 Dart实现的 SDK被封装为了一个叫作 dart:ui的 Dart库。我们在使用 Flutter写 App的时候,直接导入这个库即可使用组件等功能。
  • Flutter 的 Engine 层是 Skia 2D 的绘图引擎库,其前身是一个向量绘图软件,Chrome 和 Android 均采用 Skia 作为绘图引擎。Skia 提供了非常友好的 API,并且在图形转换、文字渲染、位图渲染方面都提供了友好、高效的表现。Skia 是跨平台的,所以可以被嵌入到 Flutter 的 iOS SDK 中,而不用去研究 iOS 闭源的 Core Graphics / Core Animation。Android 自带了 Skia,所以 Flutter Android SDK 要比 iOS SDK 小很多。

Context

BuildContext 是一个将上下文信息与特定的 Widget 相关联的对象,与该 Widget 一起构成了 Flutter Widget 树的一部分。

  • 每个 BuildContext 对象只从属于一个 Widget。这意味着每个 Widget 都有一个与之关联的 BuildContext 对象,用于表示该 Widget 在 Widget 树中的位置和上下文信息
  • 查找父级 Widget:通过 BuildContext,可以访问当前 Widget 在 Widget 树中的父级 Widget,并进一步访问父级 Widget 的属性和方法。查找子级 Widget
  • 访问主题(Theme):BuildContext 可以用于获取当前 Widget 所在的主题数据,例如颜色、字体样式等。

StatefulWidget

![[1-Flutter分享#Stateful widgets]]

![[1-Flutter分享#生命周期]]

热重载

Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。

热重载的流程可以分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所需要的时间。

另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。

可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。

FlutterEngineGroup

FlutterEngineGroup 加快首次渲染的速度、还能降低内存占用
FlutterEngineGroup 是 Flutter 框架提供的一个类,用于管理多个 Flutter 引擎的组合。它提供了引擎的创建和销毁、引擎之间的通信、引擎的生命周期管理、资源共享和多引擎的并发执行等功能。通过 FlutterEngineGroup,可以灵活地管理和控制多个 Flutter 页面的运行和交互。

共享资源:FlutterEngineGroup 允许多个 Flutter 引擎共享一些资源,如字体、图像等。通过资源的共享,可以避免多次加载相同的资源,从而减少内存占用。
引擎的生命周期管理:通过 FlutterEngineGroup,可以灵活地管理引擎的生命周期,包括启动引擎、暂停和恢复引擎等。合理管理引擎的生命周期可以在不需要时释放资源,从而减少内存占用。

🔹 1. 背景:FlutterEngine 的问题

在混合开发里(原生 + Flutter),如果你需要在 App 里启动多个 Flutter 页面,有两种常见做法:

  1. 单引擎模式

    • 全 App 共用一个 FlutterEngine
    • 优点:内存占用低,通信方便(共享 Dart Isolate)。
    • 缺点:多个 Flutter 页面共享同一个 Navigator 和状态,路由管理复杂,不够隔离。
  2. 多引擎模式

    • 每个 Flutter 页面创建一个新的 FlutterEngine
    • 优点:页面完全隔离,互不影响。
    • 缺点:每个 Engine 都要单独初始化 Dart VM、Isolate,开销大(内存和启动时间)。

👉 问题就是:单引擎灵活性差,多引擎性能开销大。


🔹 2. FlutterEngineGroup 的作用

FlutterEngineGroup 是 Flutter 提供的一种折中方案(自 Flutter 1.22 起支持)。

它的核心点是:

  • 多个 FlutterEngine 可以共享资源(Dart VM、AOT/JIT 代码、Skia、字体、图片缓存等)
  • 每个 Engine 有独立的 Dart Isolate 和状态

👉 简单来说:

  • 内存占用 ≈ 介于单引擎和多引擎之间(比多引擎省很多)。
  • 灵活性 ≈ 多引擎(页面互不干扰)。

🔹 3. 好处总结

✅ 1. 多 FlutterEngine 的高性能支持

  • 相比传统的“多引擎模式”,减少了重复资源加载(VM、AOT/JIT snapshot、缓存)。
  • 多个 Flutter 页面可以快速启动,而不会因为每次都要初始化完整引擎而卡顿。

✅ 2. 每个页面独立隔离

  • 每个 FlutterEngine 都有独立的 Dart Isolate。
  • 页面之间不会共享全局变量,互不影响。
  • 比单引擎更容易做多业务模块(比如多个 Flutter 子应用)。

✅ 3. 保持 Flutter 页面体验一致

  • 支持多个 Flutter 页面并存,页面切换流畅。
  • 尤其在 混合开发场景 下,适合“原生多 Activity/多 VC + 多 Flutter 页面”结构。

✅ 4. 内存更可控

  • 相比完全多引擎模式,内存消耗降低(因为 Engine 之间共享了 VM 资源)。
  • 不会因为频繁开新 Engine 而导致 OOM。

🔹 4. 适用场景

  • 超级 App(SuperApp):比如淘宝、美团这类多业务大应用,每个业务模块可以有独立 FlutterEngine。
  • 多 Flutter 页面并存:比如你需要同时开多个 Flutter 界面(聊天窗口、活动页、个人页等)。
  • 多团队协作开发:每个业务团队可以独立维护自己的 Flutter 模块,互不干扰。

🔹 5. 总结一句话

FlutterEngineGroup 的好处是
在保留多引擎独立性的同时,最大限度地共享底层资源,兼顾性能和隔离性,非常适合多 Flutter 页面、混合开发、大型 App 场景。

约束

flutter 布局规则
首先,上层 widget 向下层 widget 传递约束条件
然后,下层 widget 向上层 widget 传递大小信息
最后,上层 widget 决定下层 widget 的位置。

Row 嵌套 Row

Row 和 Column 都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度
如果 Row 里面嵌套 Row,或者 Column 里面再嵌套 Column,那么只有最外面的 Row 或 Column 会占用尽可能大的空间,里面 Row 或 Column 所占用的空间为实际大小

🔹 1. Row / Column 的特点

Row = 水平方向布局
Column = 垂直方向布局

它们的特点是:
在主轴方向(Row 横向 / Column 纵向)会尽量占满父容器的剩余空间。
在交叉轴方向(Row 纵向 / Column 横向)会根据子元素大小来决定。

也就是说:
外层 Row/Column 默认是“扩展型” → 会尽可能拉伸填满父容器(取决于约束)。
内层 Row/Column 是“包裹内容” → 只会占自己孩子的大小(shrink-wrap)。

🔹 2. 举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Scaffold (
body: Row (
children: [
Container (width: 100, height: 100, color: Colors. red),
Row (
children: [
Container (width: 50, height: 50, color: Colors. blue),
Container (width: 50, height: 50, color: Colors. green),
],
),
],
),
);

运行效果:
外层 Row:会尝试在水平方向填满屏幕。
内层 Row:只会占用两个小方块的宽度(100px),不会自己把空间撑满。
👉 所以你看到的现象就是:最外层 Row 占满空间,里面的 Row 只占实际内容大小。

🔹 3. 为什么会这样?
这是 Flutter 的约束规则(Box Constraints):
父组件给子组件约束:最小宽度 / 最大宽度 / 最小高度 / 最大高度。
Row / Column 在主轴方向上会尽量满足父容器的最大空间。
但是在嵌套时,内层 Row/Column 处于外层 Row/Column 的交叉轴方向,所以它拿到的约束是 “可以小于最大值”,于是只会根据实际子内容大小来决定。

🔹 4. 怎么让内层也“拉伸”?
如果你希望里面的 Row/Column 也能拉满,需要用 Expanded 或 SizedBox. expand:
这样内层 Row 就会在外层 Row 的剩余空间里拉满。

🔹 5. 总结
“只有最外面的 Row/Column 会尽可能大,里面的 Row/Column 只占实际大小” 的意思是:
外层 Row/Column 在主轴方向会拉伸填满父容器。
内层 Row/Column 默认不会主动拉伸,而是根据子内容大小决定。
如果你想让内层也拉伸,需要用 Expanded / Flexible 来告诉 Flutter:我想要占满剩余空间。

ConstrainedBox

ConstrainedBox: 对子组件添加额外的约束。例如,如果你想让子组件的最小高度是80像素
UnconstrainedBox: 不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制, 一般用来去掉父约束

在 Flutter 中,Container 的大小通常由其父组件和子组件共同决定。如果 Container 没有父组件,或者它的父组件没有提供足够的约束,那么 Container 会尽可能地大。这就是为什么你看到的 Container 占据了整个屏幕。

如果你想让 Container 的大小为特定的值,你需要确保它的父组件提供了足够的约束。例如,你可以将 Container 放在一个 Center 组件中,这样 Container 就会被约束在屏幕中心,而不是占据整个屏幕。

Align

Column控件外层包裹一层Align控件,是为了让 Column 控件的高度仅包裹其子控件的高度,而不是填充整个屏幕。Align 控件可以将其子控件对齐到指定位置,并根据子控件的大小来调整自身的大小。当您将 Align 控件的 alignment 属性设置为Alignment. topCenter时,它会将其子控件垂直居中对齐到顶部,并根据子控件的高度来调整自身的高度。度。度。度。。
如果您希望让 Column 控件填充整个屏幕,那么您可以不使用 Align 控件,直接将 Column 控件作为 Container 控件的子控件即可。

unknown_filename.7


4-Flutter基础知识
http://peiniwan.github.io/2025/12/32b8b7f5fae8.html
作者
六月的雨
发布于
2025年12月16日
许可协议