Flutter 中的布局
Flutter 布局体系详实汇报(概览 · 构建布局 · 列表&网格 · 滚动)
概览:Flutter 布局与渲染哲学
- 一切皆为 Widget:UI 的声明式描述。
Widget仅承载配置,不直接参与布局与绘制。 - 三棵树(Widget/Element/RenderObject)
- Widget:不可变配置,描述“长什么样/如何布局”。
- Element:Widget 在树中的实例,管理生命周期与父子关系,是“桥”。
- RenderObject:执行底层布局与绘制(如
RenderBox、RenderSliver)。
- 布局三定律:
- 约束自父而下(Constraints go down)
- 尺寸自子而上(Sizes go up)
- 位置由父决定(Parent sets position)
- 两大布局模型
- Box:矩形盒子模型(
Row/Column/Stack/Container等)。 - Sliver:可滚动区域内的惰性片段(
SliverList/SliverGrid/SliverAppBar等)。
- Box:矩形盒子模型(
- 渲染流水线:Build(构建)→ Layout(布局)→ Paint(绘制)→ Compositing(合成图层)→ Semantics(无障碍)→ HitTest(命中测试)。
- 适配与边距:
MediaQuery(环境信息)、SafeArea(安全区)、LayoutBuilder(基于父约束自适应)。 - 性能基线:优先惰性构建(
ListView.builder/Sliver 系列)、使用const、适度放置RepaintBoundary、避免频繁使用Intrinsic*、减少深层嵌套。
术语速览
- Constraints:父给子最小/最大宽高边界条件(
BoxConstraints)。 - Tight/Loose 约束:Tight = 固定尺寸(min = max),Loose = 仅给出上限(子可更小)。
- Intrinsic 尺寸:子控件的理想/本征尺寸,计算代价高。
- RepaintBoundary:重绘边界,限制重绘传播、利于缓存。
- Viewport:滚动视口,仅布局可见区域内的 Sliver。
- Sliver:滚动语义下的“片段式布局”单元,惰性且高性能。
构建布局:从骨架到细节
- 页面骨架
Scaffold:提供appBar、body、drawer、bottomNavigationBar、floatingActionButton等。- 常见组合:
Scaffold+AppBar+TabBar/TabBarView+BottomNavigationBar。
- 常用容器/约束
Container(便捷组合:padding/margin/decoration/constraints)、Padding、Align/Center、SizedBox、ConstrainedBox/LimitedBox、AspectRatio、FractionallySizedBox、FittedBox。
- 线性布局(Flex 家族)
Row/Column:主轴/交叉轴控制mainAxisAlignment、crossAxisAlignment、mainAxisSize。Expanded:按权重填满剩余空间(tight 约束)。Flexible:可伸缩但不强制填满(loose 约束)。Spacer:基于Expanded的空白占位。
- 叠放/定位
Stack+Positioned:绝对定位;Alignment/FractionalOffset控制相对位置;IndexedStack保持子树状态只显示其一。
- 换行/流式
Wrap(自动换行,主/交叉轴间距控制)、Flow(自定义布局,极致性能/复杂度高)。
- 自适应布局
LayoutBuilder(基于父约束做分支布局)、MediaQuery(屏幕与安全区)、OrientationBuilder(横竖屏)。
- 自定义布局
CustomSingleChildLayout/CustomMultiChildLayout(自定义布局算法)、RenderBox(自绘/自布局的终极扩展)。
- 示例:三栏权重布局
Row(
children: const [
Expanded(flex: 2, child: ColoredBox(color: Colors.red)),
SizedBox(width: 8),
Expanded(flex: 3, child: ColoredBox(color: Colors.green)),
SizedBox(width: 8),
Expanded(flex: 1, child: ColoredBox(color: Colors.blue)),
],
)
名词解释
- Main/Cross Axis:主轴/交叉轴(
Row主轴水平,Column主轴垂直)。 - Aspect Ratio:宽高比;
AspectRatio通过比例反推尺寸。 - Tightness(紧致度):约束收紧程度,影响子是否可自定尺寸。
列表 & 网格:惰性构建与可扩展组件
- ListView 构造选择
ListView(children: ...):小量静态项。ListView.builder(itemBuilder: ...):大量/未知数量,惰性构建。ListView.separated(...):带分隔的大量列表。ListView.custom(sliverDelegate: ...):完整控制(SliverChildDelegate)。
ListView.separated(
itemCount: items.length,
itemBuilder: (_, i) => ListTile(title: Text(items[i])),
separatorBuilder: (_, __) => const Divider(height: 0),
)
- GridView 构造与代理
GridView.count(...):固定列数。GridView.extent(...):每列最大像素宽度。GridView.builder(...):惰性构建 +gridDelegate控规则。- 代表性代理:
SliverGridDelegateWithFixedCrossAxisCount与SliverGridDelegateWithMaxCrossAxisExtent。
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 3 / 4,
),
itemBuilder: (_, i) => Card(child: Center(child: Text('$i'))),
)
- 高阶列表组件
ListTile(标准行模板)、Dismissible(滑动删除)、ReorderableListView(拖拽排序)、ExpansionTile/ExpansionPanelList(折叠)、ListWheelScrollView(3D 滚轮)。
- 惰性与缓存
addAutomaticKeepAlives、addRepaintBoundaries、addSemanticIndexes(通过ListView.custom或 Sliver 系列细粒度控制)。PageStorageKey:跨页面栈恢复滚动位置。
- 分页加载
- 使用
ScrollController监听position.pixels与maxScrollExtent;或NotificationListener<ScrollNotification>在ScrollEndNotification触底时触发加载。
- 使用
名词解释
- Delegate:委托对象,提供“如何构建/度量”的策略(如
SliverChildBuilderDelegate)。 - Child Extent:子项主轴尺寸;
SliverFixedExtentList/SliverPrototypeExtentList可减少布局开销。
滚动体系:单滚动 vs 多 Sliver 组合
- 单子项滚动
SingleChildScrollView:包裹单一大块内容;一次性布局全部,长内容性能差,适合内容有限的场景。
- 多子项/惰性滚动
ListView/GridView:常见场景,内部封装了CustomScrollView + SliverList/Grid。CustomScrollView:以 Sliver 为单位自由拼装复杂滚动区。
CustomScrollView(
slivers: [
const SliverAppBar(
floating: true,
snap: true,
pinned: false,
expandedHeight: 160,
flexibleSpace: FlexibleSpaceBar(title: Text('标题')),
),
SliverPadding(
padding: const EdgeInsets.all(8),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => ListTile(title: Text('Item $i')),
childCount: 100,
),
),
),
],
)
- 常用 Sliver 种类
- 结构:
SliverList、SliverGrid、SliverToBoxAdapter、SliverFillRemaining、SliverPadding。 - 头部:
SliverAppBar、SliverPersistentHeader(可吸顶/伸缩)。
- 结构:
- 滚动物理与行为
ScrollPhysics:BouncingScrollPhysics(iOS 回弹)、ClampingScrollPhysics(Android 贴边)、NeverScrollableScrollPhysics(禁滚)。- 全局
ScrollConfiguration可定制滚动行为(如去除水波/回弹)。
- 控制与监听
ScrollController:jumpTo/animateTo、监听position、与PrimaryScrollController配合(如Scaffold内默认主控制器)。NotificationListener<ScrollNotification>:监听ScrollStart/Update/End、UserScrollNotification(含ScrollDirection)。
- 嵌套滚动与吸顶
NestedScrollView:外层和内层滚动协调,常与SliverAppBar+TabBar/TabBarView实现吸顶头部与页签联动。SliverAppBar关键参数:pinned(吸顶)、floating(下拉即显)、snap(与floating配合瞬时吸附)。
- 滚动状态保持
- 使用
PageStorageKey记忆每个列表的滚动位置;拆分页面时注意 Key 的唯一性与层级。
- 使用
- 可访问性与语义
- 使用
Semantics、MergeSemantics优化阅读器体验;列表可通过addSemanticIndexes提升有序性。
- 使用
- 常见坑与最佳实践
SingleChildScrollView内再放ListView会产生无界高度冲突;需给定显式高度(如SizedBox+ 固定高)或使用shrinkWrap: true(但注意性能)。- 在
Column中放ListView需用Expanded/Flexible约束高度。 - 避免在长列表中使用
shrinkWrap: true与NeverScrollableScrollPhysics,会牺牲惰性布局性能。 - 大量复杂子项时使用
RepaintBoundary或分片组件,减少重绘范围。
小结
- Flutter 以“父传约束、子报尺寸、父定位置”为核心,区分 Box 与 Sliver 两套布局模型。
- 构建页面先搭骨架(
Scaffold),再用 Flex/定位/自适应组合细节,长内容优先使用 Sliver 惰性方案。 - 列表/网格选择合适的构造与代理,配合滚动控制器、通知与语义,兼顾性能与可访问性。