Flutter 中的布局

114 阅读4分钟

Flutter 中的布局

Flutter 布局体系详实汇报(概览 · 构建布局 · 列表&网格 · 滚动)

概览:Flutter 布局与渲染哲学

  • 一切皆为 Widget:UI 的声明式描述。Widget 仅承载配置,不直接参与布局与绘制。
  • 三棵树(Widget/Element/RenderObject)
    • Widget:不可变配置,描述“长什么样/如何布局”。
    • Element:Widget 在树中的实例,管理生命周期与父子关系,是“桥”。
    • RenderObject:执行底层布局与绘制(如 RenderBoxRenderSliver)。
  • 布局三定律
    • 约束自父而下(Constraints go down)
    • 尺寸自子而上(Sizes go up)
    • 位置由父决定(Parent sets position)
  • 两大布局模型
    • Box:矩形盒子模型(Row/Column/Stack/Container 等)。
    • Sliver:可滚动区域内的惰性片段(SliverList/SliverGrid/SliverAppBar 等)。
  • 渲染流水线: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:提供 appBarbodydrawerbottomNavigationBarfloatingActionButton 等。
    • 常见组合:Scaffold + AppBar + TabBar/TabBarView + BottomNavigationBar
  • 常用容器/约束
    • Container(便捷组合:padding/margin/decoration/constraints)、PaddingAlign/CenterSizedBoxConstrainedBox/LimitedBoxAspectRatioFractionallySizedBoxFittedBox
  • 线性布局(Flex 家族)
    • Row/Column:主轴/交叉轴控制 mainAxisAlignmentcrossAxisAlignmentmainAxisSize
    • 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 控规则。
    • 代表性代理:SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent
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 滚轮)。
  • 惰性与缓存
    • addAutomaticKeepAlivesaddRepaintBoundariesaddSemanticIndexes(通过 ListView.custom 或 Sliver 系列细粒度控制)。
    • PageStorageKey:跨页面栈恢复滚动位置。
  • 分页加载
    • 使用 ScrollController 监听 position.pixelsmaxScrollExtent;或 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 种类
    • 结构:SliverListSliverGridSliverToBoxAdapterSliverFillRemainingSliverPadding
    • 头部:SliverAppBarSliverPersistentHeader(可吸顶/伸缩)。
  • 滚动物理与行为
    • ScrollPhysicsBouncingScrollPhysics(iOS 回弹)、ClampingScrollPhysics(Android 贴边)、NeverScrollableScrollPhysics(禁滚)。
    • 全局 ScrollConfiguration 可定制滚动行为(如去除水波/回弹)。
  • 控制与监听
    • ScrollControllerjumpTo/animateTo、监听 position、与 PrimaryScrollController 配合(如 Scaffold 内默认主控制器)。
    • NotificationListener<ScrollNotification>:监听 ScrollStart/Update/EndUserScrollNotification(含 ScrollDirection)。
  • 嵌套滚动与吸顶
    • NestedScrollView:外层和内层滚动协调,常与 SliverAppBar + TabBar/TabBarView 实现吸顶头部与页签联动。
    • SliverAppBar 关键参数:pinned(吸顶)、floating(下拉即显)、snap(与 floating 配合瞬时吸附)。
  • 滚动状态保持
    • 使用 PageStorageKey 记忆每个列表的滚动位置;拆分页面时注意 Key 的唯一性与层级。
  • 可访问性与语义
    • 使用 SemanticsMergeSemantics 优化阅读器体验;列表可通过 addSemanticIndexes 提升有序性。
  • 常见坑与最佳实践
    • SingleChildScrollView 内再放 ListView 会产生无界高度冲突;需给定显式高度(如 SizedBox + 固定高)或使用 shrinkWrap: true(但注意性能)。
    • Column 中放 ListView 需用 Expanded/Flexible 约束高度。
    • 避免在长列表中使用 shrinkWrap: trueNeverScrollableScrollPhysics,会牺牲惰性布局性能。
    • 大量复杂子项时使用 RepaintBoundary 或分片组件,减少重绘范围。

小结

  • Flutter 以“父传约束、子报尺寸、父定位置”为核心,区分 Box 与 Sliver 两套布局模型。
  • 构建页面先搭骨架(Scaffold),再用 Flex/定位/自适应组合细节,长内容优先使用 Sliver 惰性方案。
  • 列表/网格选择合适的构造与代理,配合滚动控制器、通知与语义,兼顾性能与可访问性。