Flutter Text 为什么“看起来不垂直居中”(baseline/metrics/line-height)
现象
在 ListWheelScrollView(滚轮选择器)里,数字会出现“看起来不垂直居中”的情况:中心数字与上下两行数字的间距不一致,即使外层已经 Center(...) 了。
一个典型片段(来自 lib/ui/pages/widgets/number_wheel_picker.dart):
return Center(
child: Text(
value.toString().padLeft(2, '0'),
textAlign: TextAlign.center, // 只管水平,不管竖直
style: TextStyle(
fontSize: fontSize, // 动态字号插值
color: color,
fontWeight: weight,
),
),
);
textAlign: center只影响水平对齐;竖直方向是由文本布局系统决定的。
根因:Flutter 文本布局以 baseline + 字体度量为核心
1) RenderObject 的 baseline 计算链路(可直接查官方源码实现)
RenderParagraph.computeDistanceToActualBaseline(框架层):
// RenderParagraph.computeDistanceToActualBaseline
return _textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic);
来源:https://api.flutter.dev/flutter/rendering/RenderParagraph/computeDistanceToActualBaseline.html
TextPainter.computeDistanceToActualBaseline(绘制层):
// TextPainter.computeDistanceToActualBaseline
return _layoutCache!.layout.getDistanceToBaseline(baseline);
来源:https://api.flutter.dev/flutter/painting/TextPainter/computeDistanceToActualBaseline.html
这条链路说明:段落的竖直度量是由 text layout 计算出来的 baseline/metrics 决定的,并不是“把 glyph 的视觉几何中心放到盒子中心”。
2) 默认 line height 来自字体 metrics,而不是 fontSize
TextStyle.height 文档明确说明:
- 当
height为kTextHeightNone(或未设置)时,行高由字体 metrics 决定; - 当
height被设置时,行高精确等于fontSize * height。
来源:https://api.flutter.dev/flutter/painting/TextStyle/height.html
因此:即使两个 Text 组件都在 Center 中,字形在行盒中的“上下留白”并不必然对称(取决于字体 ascent/descent/leading),在滚轮的透视/缩放下这种差异会被放大。
为什么 height / strutStyle 能“看起来更居中”
1) TextStyle(height: 1.0):把行高锁到 EM-square,减少字体自带 metrics 漂移
当你显式设置 height: 1.0 时,行高会变成 fontSize * 1.0。它不等于“默认高度”,而是用 EM-square 来定义一个更可控的行盒。
这能减少的不是 baseline 本身,而是:
- 行盒高度随字体 metrics 波动(leading/ascent/descent)的不确定性;
- 不同字号/不同 run 在同一段落里导致的“行盒变化”。
滚轮里我们常做字号插值(中心大、两侧小),如果不固定行高,就更容易出现“上一行顶端被裁 1~2dp”或“上下间距不一致”的视觉错觉。
2) StrutStyle(forceStrutHeight: true):强制所有行使用统一的 strut 度量
StrutStyle 是 paragraph 级别的最小行高约束。文档写得很明确:
when true, all lines will be laid out with the height of the strut.
All line and run-specific metrics will be ignored/overridden.
来源:https://api.flutter.dev/flutter/painting/StrutStyle-class.html
在滚轮里如果你想要“机械等距”的观感,forceStrutHeight: true 的效果通常是:
- 每一行的 ascent/descent 以 strut 为准;
- 即便字号变化,行盒也更稳定;
- glyph 在行盒中的位置波动更小,从而视觉中心更稳定。
3) 为什么这能改善“居中”
“不居中”本质上是行盒内部的 glyph 分布 + 滚轮透视共同造成的。height/strut 并不会改变“baseline 体系”,但会改变:
- 行盒高度(line box height)
- ascent/descent 的分配
- leading 的分配
当这些维度被固定后,glyph 的视觉中心更接近行盒中心,你就会感觉“居中变好了”,上下间距也更一致。
实战建议(滚轮/数字选择器场景)
- 优先保证足够的 itemExtent:itemExtent 太小,即使有 strut 也容易裁切。
- 固定行高:
TextStyle(height: 1.0)通常是第一步。 - 需要强一致时再上 strut:尤其是字号插值/多字体时。
- 不要把
textAlign当成竖直对齐工具:它只管水平方向。
附:你项目里的关联文件
lib/ui/pages/widgets/number_wheel_picker.dartlib/ui/pages/pillow_device_setting/PillowDeviceSettingPage.dart