1. 鸿蒙适配后,Row和Column溢出为何集中爆发
这阵子我一直在忙公司 Flutter 项目向鸿蒙设备的移植适配,踩得最狠的坑就是 Row 和 Column 的溢出问题。其实这个问题的出现,在纯 Flutter 开发里并不新鲜:你给 Row 塞了三个 Text,屏幕一窄就黄黑条纹满天飞;Column 里插了个固定高度的图片,底部按钮直接被顶出屏幕。但让我意外的是,同样的代码在 Android 和 iOS 上跑得挺正常,一旦切到鸿蒙模拟器或真机上,溢出就开始集中爆发。不是一两个页面,而是登录取证、个人中心、数据面板这些承载大量 Row/Column 的常用页面,全冒出来了。
先说结论:这不是 Flutter 在鸿蒙上稳定性差,而是 Flutter 在鸿蒙上的运行环境和屏幕形态,与你在 Android/iOS 上默认假设的窗口场景不一样。你在 Android 上默认竖屏、窄屏、手机控件宽度,这些假设换到鸿蒙的平板、折叠屏、PC 窗口、甚至开发板外接屏上,全部失效。Row 和 Column 的溢出,本质上是布局约束和实际可用空间之间的矛盾,而鸿蒙恰好把这个问题从"偶尔冒出来"放大到了"遍地都是"。
所以要真正解决溢出,不能只盯着 Row 和 Column 本身,还要理解 Flutter 的布局约束机制在鸿蒙设备上会怎样变化。下面我会先把溢出的根因讲透,再给几套能直接落地的解决方案,然后专门说鸿蒙特有的场景,最后带大家完整过一遍排查日志的流程。适合刚从 Android/iOS 往鸿蒙迁移 Flutter 项目的团队,也适合在鸿蒙模拟器里被溢出问题折磨得想放弃的新手。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 溢出的根因:Flex布局的约束决策链路
2.1 从约束到尺寸:Flutter布局的核心逻辑
Flutter 的布局模型跟 CSS 有本质区别:CSS 是子元素主动声明自己的尺寸,父容器尽量去配合;Flutter 是父组件向下传递 BoxConstraints(minWidth、maxWidth、minHeight、maxHeight),子组件在这个约束范围内决定自己的尺寸,然后向上返回。
这意味着 Row 和 Column 本身不是"我有多大就装多少",而是先接收父级给它的约束,把可用空间拆给子组件。如果子组件想要的总宽度或总高度超过了父级给的可用空间,多余的尺寸就会溢出。这个"父级约束"从哪来?往上看,可能是屏幕宽度减去 SafeArea、可能是 Padding 之后剩下的宽度、可能是另一个 Flex 已经分配给自己的一等份。往下看,Row 和 Column 在水平/垂直方向上的绘制范围,直接决定了子组件是否会被压扁。
调试时经常会遇到一个现象:明明给 Row 里的 Text 设置了 fontSize: 16,但运行后文字被截断或者报 overflow。原因是 Text 在收到一个非常窄的约束时,并不会自动省略或缩小字号,而是选择"按实际文本宽度绘制",一旦实际宽度超过约束,就只能溢出。很多人以为 Text 会自动换行,其实 Text 只在有 softWrap 且受约束宽度足够时才会换行。英文单词、数字、URL 这种长内容,默认情况下宁可突破边界也不会自动断行。
2.2 RenderFlex的决策逻辑:谁在决定溢出
Row 和 Column 是 Flex 的子类,它们对应的 RenderFlex 对象,在布局阶段会根据主轴方向做两套完全不同的计算路径:
- 主轴方向无界(unbounded):比如 Row 被放在 Horizontal 的 SingleChildScrollView 里,或者 Column 被放在 Vertical 的 ListView 里。这种情况下 Flex 会先按子组件的固有尺寸(Intrinsic Dimensions)计算出子组件需要的总长度,如果有子组件设置了 flex(Expanded 这种),直接报错:
RenderFlex children have non-zero flex but incoming width constraints are unbounded. - 主轴方向有界(bounded):比如 Row 放在固定宽度的 Container 里。这时 RenderFlex 会先把可用空间均分给设置了 flex 的子组件,再让没有 flex 的子组件按自身尺寸去占位。若所有子组件都不设置 flex,那么 Row 会把所有子组件在主轴方向上的尺寸简单相加,如果总和超过可用宽度,就逐像素溢出。
实际开发中 90% 的溢出都发生在"有界"场景。还有一个容易被忽视的细节:RenderFlex 处理溢出时,并不是按照"空间不够就全部溢出"的方式,而是按子组件逐个排布,排到某个子组件时空间不够了,才开始显示黄色条纹。这条信息在排查时非常重要——条纹出现的位置,往往就是第一个放不下的子组件,而不是最后一个。
2.3 溢出上报的判定数值与视觉特性
当溢出发生时,Flutter 的 Debug 模式会在控制台输出类似这样的日志:
code复制A RenderFlex overflowed by 22 pixels on the right.
这个数值是怎么算出来的?RenderFlex 在布局时记录了可用主轴长度和实际绘制内容的长度之差,如果差值大于 0.01 像素,就会记为 overflow。实际在屏幕上看到的,是黄黑相间的条纹,条纹区域的宽度就是溢出像素数。
但注意,溢出报错不一定等于页面完全崩溃。在很多场景下,你只是看到某一行右侧被裁掉了一部分,或者底部按钮被顶出可视区域,应用还能继续运行。这个"还能运行"恰恰是最坑的地方——从用户视角看,就是界面局部坏了;从开发视角看,又能在日志里找到线索,但线索非常分散。鸿蒙的调试日志里,如果同时存在大量第三方插件的输出,这条 overflow 信息很容易被淹没。我的经验是开发阶段直接把 Debug 的 overflow 检查开着,别关,宁可慢一点,也要让问题暴露在测试期。
3. 按场景选方案:五种主流解法的取舍逻辑
3.1 Expanded与Flexible:空间分配的两个层级
Row 和 Column 溢出最直接的解法就是给子组件加 Expanded。Expanded 的本质是让子组件占据剩余空间的一部分,它的实现基于 Flex 的 flex 机制,默认 Fit.tight,意思是:分配到的空间,必须用完,不能用不完。
举个例子,一个典型的登录页布局:
dart复制Row(
children: [
const Text('手机号'),
const SizedBox(width: 8),
Expanded(
child: TextField(
keyboardType: TextInputType.phone,
decoration: const InputDecoration(hintText: '请输入11位手机号'),
),
),
],
)
Text 不设置 flex,TextField 被 Expanded 包裹,Row 分配空间时先给 Text 留出固定宽度,剩下的全部给 TextField。这是最安全的写法,前提是 Text 自身的宽度不能超过可用宽度。如果手机号前面的提示文本很长,比如"请输入您的手机号",那 Text 也可能撑爆,这时需要给 Text 也加上 Flexible + ellipsis。
Flexible 与 Expanded 的区别在于默认的 Fit 类型。Flexible 默认 Fit.loose,意思是:可以占满分配空间,但如果子组件自身不需要那么大,可以只占一部分。比如:
dart复制Row(
children: [
Flexible(
child: Text(longText),
),
const Text('固定尾部'),
],
)
这段代码里,如果 longText 很短,Row 会优先给尾部 Text 留出空间;如果 longText 很长,Flexible 会收缩其分配空间,把多余空间让给尾部。这个特性在"左侧文本 + 右侧操作按钮"的模式里非常实用,比直接给左侧加 Expanded 更灵活,因为 Expanded 会把所有剩余空间都塞给左侧,导致右侧按钮被挤到最右边,视觉上不够紧凑。
3.2 Spacer与比例拆分:让布局更接近设计稿
很多时候布局设计稿是"左边占 30%,右边占 70%",或者"三个按钮等比排列"。Expanded 的 flex 参数可以解决这个问题:
dart复制Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.red)),
SizedBox(width: 8),
Expanded(flex: 3, child: Container(color: Colors.blue)),
],
)
这里 flex: 2 和 flex: 3,意味着在去掉中间的 SizedBox 后,两个容器按 2:3 的比例分配剩余宽度,无论屏幕多宽都不会溢出。这个写法的好处是:不需要在代码里硬编码具体的像素宽度,适配不同屏幕比例时会自动缩放。
Spacer 是 Expanded 的一种语法糖,本质是 Expanded(child: SizedBox.shrink())。它不渲染任何东西,只是占用空间。比如居中偏右的布局:
dart复制Row(
children: [
const Text('左文案'),
const Spacer(),
const Text('右文案'),
],
)
两个文本之间会有一条弹性间距。实测下来,Spacer 和 Expanded 在 flex 分配逻辑上完全一致,区别只是可读性。当你只是想要"两端对齐"时,用 Spacer 会比手动加 SizedBox(width: double.infinity) 更干净。
但需要提醒一点:Expanded、Flexible、Spacer 都必须放在主轴有界的 Flex 里。如果你把它们放在 SingleChildScrollView 里的 Row/Column 中,也就是主轴无界场景,Flutter 会直接抛异常。这个坑我踩过,当时是想做横向滚动标签栏,给每个标签加了 Expanded,一运行就报 non-zero flex,后来改成用 ListView 横滑,或者用 Align/Wrap 才解决。
3.3 FittedBox:让内容自己找合适尺寸
FittedBox 是一个很有意思的组件,它会先给子组件一个无限大的约束,让子组件按照自身自然尺寸去布局,然后再把得到的尺寸缩放或拉伸到可用空间里。
适用场景有两种:
- 当你不知道文本会有多长,但希望无论怎么变都不溢出时。
- 当你有一张固定比例的图表或图片,需要自适应父容器尺寸时。
比如一段动态返回的错误信息:
dart复制SizedBox(
width: 200,
height: 40,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text('这是一段可能超出容器宽度的错误提示'),
),
)
BoxFit.scaleDown 表示:如果内容比容器小,就保持原始大小;如果比容器大,才等比缩小。这样既不会在小文本时放大失真,也不会在大文本时溢出。但要注意,如果文本太小,FittedBox 会把它保持原始尺寸,此时可能需要配合 alignment: Alignment.centerLeft 控制对齐方式。
FittedBox 的局限在于它只能缩放,不能换行。多行文本直接塞进 FittedBox,效果会很糟糕,因为 FittedBox 会把整个多行文本当成一个矩形来缩放,导致字号异常小。所以它适合短文本、单行场景。多行溢出还是得用 Column 内嵌 Text + maxLines + ellipsis。
3.4 裁剪:不换行、不缩放,只切掉
有些场景下,我们不希望内容缩放,也不希望布局弹性变化,只希望超出部分被裁掉。这种思路可以用 ClipRect 来实现:
dart复制ClipRect(
child: OverflowBox(
alignment: Alignment.topLeft,
maxWidth: double.infinity,
child: Container(width: 300, height: 50, color: Colors.red),
),
)
OverflowBox 允许子组件超出父级约束,ClipRect 再把超出部分裁掉。但老实说,这种写法在业务代码里用得不多,因为裁掉的内容用户看不到,体验不好。真正常用的是 Text 自身的裁剪能力:
dart复制Text(
'很长的文本内容,需要限制为单行并省略',
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
设置 maxLines: 1 + overflow: ellipsis 后,Text 在宽度不足时会在末尾显示省略号,而不是溢出。这比把文本包进 Flexible 更精确,因为 Flexible 只解决占用空间问题,不解决文本内容的展示策略。
还有一类裁剪是图片。Image 在 Row 或 Column 里默认使用原始尺寸,如果图片宽度超过父容器,就会溢出。正确写法是给 Image 外层设置 SizedBox 并指定 fit: BoxFit.cover 或 BoxFit.contain:
dart复制Row(
children: [
SizedBox(
width: 80,
height: 80,
child: Image.network('https://example.com/avatar.png', fit: BoxFit.cover),
),
const SizedBox(width: 12),
Expanded(child: Text('用户昵称')),
],
)
这也是很多新手容易漏掉的点。图片不设置 fit 时,加载完成后会按照图片真实分辨率渲染,网络图动辄几千像素宽,直接塞进 Row 里不溢出才怪。
3.5 滚动:让内容在无界空间里自由伸展
当内容总长度确实超过可用尺寸,而且无法通过缩放和裁剪来解决时,滚动是最后的兜底方案。
横向 Row 溢出时,最标准的做法是横向 SingleChildScrollView:
dart复制SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (var i = 0; i < 20; i++)
Padding(
padding: const EdgeInsets.all(8),
child: Text('标签 $i'),
),
],
),
)
因为 SingleChildScrollView 在主轴方向给子组件的是无界约束,所以 Row 可以无限延伸,不会触发布局报错。这个方案适用于横向标签栏、横向卡片列表、表格列数过多等场景。
但滚动方案有几个需要记住的副作用:
- 滚动容器内的 Row/Column 无法再使用 Expanded,因为主轴无界,你每个子组件都按内容尺寸排布,一旦内容多,整个 Row 会无限宽,而不是填充屏幕宽度。
- 嵌套滚动手势会与页面级滚动冲突,需要合理设置
NeverScrollableScrollPhysics或ClampingScrollPhysics。
如果页面里同时有横向滚动和纵向滚动,比如纵向 ListView 里套横向 SingleChildScrollView,性能会相对差一些,但在少量数据下完全可接受,优先保证布局正确。
3.6 LayoutBuilder与动态约束:提前感知边界
有时候布局不是简单的"剩余空间",而是"当空间小于某个阈值时,我要换一种布局"。LayoutBuilder 可以拿到父级给它的约束,然后根据约束条件动态构建不同的 Widget:
dart复制LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 320) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(Icons.warning, size: 48),
SizedBox(height: 8),
Text('空间不足,请横屏查看'),
],
);
}
return Row(
children: const [
Icon(Icons.warning, size: 24),
SizedBox(width: 8),
Text('数据展示正常'),
],
);
},
)
这个方法在鸿蒙的折叠屏、平板多窗口适配里价值非常大。你可以不用写死屏幕宽度,而是根据实际可用空间动态切换布局模式。比如业务数据面板在窄屏显示为纵向排列,宽屏切换为横排多列,都能用 LayoutBuilder 实现在同一个 Widget 里。
与之配套的还有 ConstrainedBox、AspectRatio、FittedBox 这些约束相关组件。ConstrainedBox 可以限制子组件的最大/最小尺寸,AspectRatio 强制宽高比。它们和 LayoutBuilder 配合,基本能应对 90% 的动态尺寸需求。
4. 鸿蒙特有场景的窗口、键盘与字体差异处理
4.1 窗口尺寸与分屏:过去很少考虑的动态变化
Android 和 iOS 手机虽然也有旋转、分屏,但绝大多数情况下你面对的是固定竖屏尺寸。鸿蒙设备则完全不同,尤其是 HarmonyOS NEXT 和开源鸿蒙在平板、PC、开发板上的形态差异非常大。同样的代码,在手机上是全屏竖屏,到了平板上可能是分屏窗口,到了 PC 上又可以随意拖拽窗口大小。
Column 的溢出在分屏场景下特别明显。比如一个聊天页面,底部是输入框,中间是消息列表,顶部是标题栏。如果你把输入框用 position: fixed 的思路写在 Column 的最下面,并且消息列表用了 Expanded,那么窗口高度变化时,输入框还能自动跟着调整。但如果你在消息列表里写了一个固定高度的 Container 来兜底,比如 height: 300,那么窗口缩小到 500 高度时,300 的固定高度加上输入框、标题栏,就可能直接把底部按钮挤出屏幕,报 vertical overflow。
我在鸿蒙 PC 窗口里调试时,经常是拖窄窗口的瞬间,黄黑条纹就出现。这个场景在 Android 模拟器里几乎无法复现,因为 Android 模拟器默认模拟的是标准手机尺寸,不容易拉到那么极端。所以如果你在做鸿蒙适配,一定要在窗口大小差距大的设备上测试,不要只盯着默认模拟器尺寸。
4.2 输入法、键盘避让与底部按钮区域
鸿蒙的软键盘弹出逻辑与 Android 有差异。Android 默认 adjustResize 在某些主题下不会把窗口缩小,而是直接顶起界面;鸿蒙的窗口管理有自己的避让策略,Flutter 引擎接入后的同步方式也取决于适配层实现。
Column 底部按钮被键盘顶出屏幕或者被遮挡,是典型问题。解决方案有两种:
- 用
SafeArea包裹 Column,配合MediaQuery.of(context).viewInsets.bottom动态调整底部 padding:
dart复制Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
children: [
Expanded(child: ListView(...)),
BottomButton(),
],
),
)
- 使用
Scaffold的resizeToAvoidBottomInset属性。鸿蒙 Flutter 适配层对键盘事件的支持程度不一样,我测试过的版本里,键盘弹出时窗口是否缩小,跟windowSoftInputMode和 Flutter 引擎的插件实现都有关。建议在真机上多测几轮,模拟器里键盘弹出行为经常跟真机不一致。
还有一种情况是输入框聚焦后,整个 Column 组件因为空间不够而产生溢出。这种场景下,最稳妥的写法是把 Column 改成 ListView,让内部内容可以滚动。虽然 ListView 在内容不足时不会填充整个屏幕,但你可以通过 physics: AlwaysScrollableScrollPhysics() 强制允许滚动,避免溢出。
4.3 字体缩放、多语言与文本宽度
鸿蒙系统默认字体是 HarmonyOS Sans,它的字符宽度和字形渲染特征与 Android 的 Roboto、iOS 的 SF Pro 有一些差异。同一个字符串,在不同系统字体下的实际像素宽度可能不同。如果原来的布局在 Android 上是刚刚好,没有余量,到了鸿蒙上就可能差几个像素,从而触发溢出。
另一个变量是系统字体缩放。鸿蒙设置里支持字体显示大小调整,用户调大后,Text 的字体尺寸会按比例放大。如果你在 Row 里放了两个文本,没有设置 flex 或者没有留足够余量,字体一放大就立刻溢出。
实测经验:在 Row 里放文本时,至少保留 4-8 像素的安全边距,最好用 Flexible 包裹可能变长的文本。千万不要把 Row 的宽度写到刚好等于内容宽度,这是最脆弱的布局。
多语言场景更要注意。中文文案通常能自然换行,因为每个汉字之间可以断行;但英文、法语、德语里的单词默认不断行,一个长 URL 或者长用户名在 Row 里会直接溢出。应对方式是给 Text 设置 textWidthBasis: TextWidthBasis.longestLine 或者手动把长字符串用零宽空格处理,更推荐的是配合 Flexible + ellipsis,让文本在空间不足时优雅省略。
4.4 模拟器、真机与PC运行环境的差异
鸿蒙的模拟器目前主要跑 ARM64 镜像,但到了 PC 上,很多场景是 x86_64 架构运行,这就意味着 Flutter 引擎在不同架构下的渲染行为可能会有细微差异。布局引擎本身是纯 Dart 层逻辑,理论上不会因架构不同而产生不同的布局结果,但字体渲染、像素密度、窗口缩放这些环节,在不同架构和图形栈下会有不同表现。
我在 x86_64 的鸿蒙 PC 环境中遇到过一个问题:同一段 Column 布局,在 ARM64 模拟器上显示正常,到了 PC 上因为系统字体渲染的 DPI 缩放设置不同,底部按钮溢出。排查到最后发现,问题出在 MediaQuery.of(context).textScaler 上,PC 环境的系统字体缩放比默认大,导致 Column 内文本高度增加,总高度超过屏幕。后来我给底部按钮区域单独加了一个固定高度,并把文本区域的 Expanded 改成 Flexible,才彻底解决。
这种跨架构差异很难提前预判,最好的办法就是在项目适配的早期就准备多套运行环境:ARM64 模拟器、x86_64 模拟器、真机。在真机上复现问题后,再用模拟器调整布局,反复对比。如果你只依赖一种环境,很容易出现"测试全过、上线全崩"的局面。
5. 溢出问题排查复盘与防御式写法规避
5.1 从条纹到报错日志:先确认是布局问题还是数据问题
调试溢出问题时,第一件事是打开控制台看日志。溢出报错长这样:
code复制The following assertion was thrown during layout:
A RenderFlex overflowed by 22 pixels on the right.
The relevant error-causing widget was
Row
光看这一行日志还不够,因为 22 像素可能是某一侧的 Tab 栏,也可能是某个文本。需要往上翻日志,找到具体是哪个 Row 或 Column。日志里通常会列出出错 Widget 的类型和它所在的文件路径。如果文件路径和代码能对应上,直接在代码里搜对应的 Row 即可。
但鸿蒙适配层会包一层原生容器,有些 overflow 报错来自引擎内部的渲染,并不会直接指向你的业务 Widget,这时排查就会变得复杂。我常用的办法是:先把出错页面所有的 Row/Column 逐个替换成安全版本(比如给所有 Text 加 Flexible),如果问题消失,再逐个回退找出具体出错的组件。这个方法虽然粗暴,但在嵌套层级深、代码复杂的页面里非常高效。
5.2 排查链路:从复现到定位的完整步骤
以我最近处理的一个"Column 底部按钮溢出"问题为例,完整排查链路是这样的:
第一步:复现。在鸿蒙真机上打开目标页面,复现条件是"用户使用系统大字体 + 窗口高度小于 700 像素"。复现后截图并记录日志中的 overflow 数值。
第二步:简化。把页面里的业务逻辑全部去掉,用一个只包含标题栏、中间区域、底部按钮的最小 Column 来复现。如果最小 Column 能复现,说明问题在布局结构本身;如果最小 Column 不能复现,就往里面逐步添加业务组件,直到问题出现。
第三步:定位。在可能出现溢出的地方加日志,输出 constraints 和 size。比如在 Column 的 build 方法里:
dart复制print('Column constraints: ${constraints.toString()}');
print('Column size: ${size.toString()}');
这样能直观看到约束的实际最大高度和溢出前后的尺寸变化。
第四步:修复。根据定位结果选择合适的方案。如果中间区域内容可能增长,使用 Expanded + ListView;如果底部按钮高度固定,就单独抽出为固定高度的 SizedBox;如果标题栏可能因字体放大而变高,就改用 Flexible。
第五步:验证。在复现条件相同的设备上验证修复效果。同时把窗口高度逐渐调小,比如从 900 依次调到 500,观察溢出是否在所有尺寸下都消失。
这五步走下来,绝大多数溢出问题都可以在半小时内定位并修复,不需要凭空猜测。
5.3 防御式写法与团队规范
熬夜解决了一堆溢出问题之后,我还是建议从源头减少这类问题的出现。下面这几条规则,我已经写进团队 Flutter 开发规范里了。
在 Row 或 Column 中,凡是可能容纳动态文本的子组件,优先用 Flexible + TextOverflow.ellipsis 包裹,不要直接裸露 Text。
dart复制// 不推荐
Row(children: [Text(title), Text(subtitle)]);
// 推荐
Row(children: [
Flexible(child: Text(title, overflow: TextOverflow.ellipsis)),
const SizedBox(width: 8),
Text(subtitle),
]);
固定宽度或高度的组件(图片、图标、按钮)单独抽到 SizedBox 中,并显式设置尺寸,避免依赖内容自然尺寸。Image 必须设置 fit,不要在 Row 里裸用网络图。
在 Flex 中禁止使用以像素为单位的硬编码剩余空间。比如 SizedBox(width: MediaQuery.of(context).size.width - 100) 这种写法非常危险,一旦屏幕宽度变化就会溢出,应该用 Expanded 或 LayoutBuilder 替代。
凡是可能被系统字体缩放影响的高度,都留出至少 8-16 像素的缓冲。Row 或 Column 的 crossAxisAlignment 不要滥用 stretch,因为它会让子组件强制填满交叉轴空间,在字体放大时更容易触发溢出。
产品设计阶段就明确"溢出时的兜底策略"。是截断、省略、缩放、还是滚动,最好在 UI 评审时确定,而不是等开发到一半再临时决定。
6. 写在最后一次真机调试之后
鸿蒙适配这几个月,我对 Flutter 布局的理解比之前几年加起来都要深。以前总觉得 Row 和 Column 溢出嘛,无脑加 Expanded 就完了,实际投入生产环境才发现,每一处溢出背后都对应着一个具体的设备场景或用户行为。鸿蒙这套设备矩阵(手机、平板、折叠屏、PC、开发板)把原有的布局假设全部打碎,逼着我把"这个组件在什么约束下、以什么尺寸渲染"等问题重新想了一遍。
如果只留一条建议给正在做 Flutter 鸿蒙适配的同行:不要相信"在模拟器里看起来没问题"。把窗口尺寸拖到极端值,把系统字体调到最大,开分屏,横竖屏来回切换,把键盘弹起来又收回去,这些都在真机上完整过一遍。溢出问题不会消失,但你可以把它关进笼子里。
