Flutter 常用"布局助手"一览表
组件 | 侧重点 | 典型用途 / 场景 | 性能开销*️⃣ |
---|---|---|---|
SizedBox | 纯尺寸约束(width / height 或 shrink / expand ) |
占位、留白、固定按钮宽高;最轻量,无额外装饰 | ★☆☆ |
Padding | 四周留白(不影响子尺寸) | 在列表或卡片内给子组件加外间距 | ★☆☆ |
Spacer | Flex 剩余空间分配 | Row / Column 中插空,让两端元素自动拉开 |
★☆☆ |
ConstrainedBox | 高级约束(min / max 尺寸范围) |
既要最小宽度又要最大高度等复杂版式,比 SizedBox 灵活 |
★★☆ |
Container | "万能盒子":尺寸 + 外边距 + BoxDecoration + 对齐 |
背景色、渐变、圆角、边框、阴影(非 Material)、对齐等一把梭 | ★★☆ |
Card | Material 纸片:圆角 + 阴影 + Ripple | 列表卡片、商品条目、瀑布流 Tile;一行代码搞定投影与主题色 | ★★☆(elevation 越高 GPU 压力越大) |
Material | 底层材质:自控 type / elevation / inkSplash |
需要手动调控阴影、分层、水波纹时;Card 的基础 |
★★☆ |
PhysicalModel | 裁剪 + 投影(无 Ink、无主题) | 仅需圆角裁剪 + 阴影的静态展示,如纯图片蒙版 | ★★☆ |
(辅助) Gap¹ | 语义化"间隔器" | 未来官方计划引入的 Gap(8) ,比 SizedBox(width: 8) 更直观 |
★☆☆ |
*️⃣ 星级说明 :★☆☆ = 极轻量(纯布局计算);★★☆ = 中等(含装饰/阴影/裁剪);★★★ = 较高(复杂阴影、多层裁剪)。
¹ Gap
仍在 PR 讨论阶段(2025 Q3),暂可用 SizedBox
代替。
📝 选型速记口诀
- "只定尺寸、不画东西":
SizedBox
- "只留外边距":
Padding
- "Flex 剩余空间":
Spacer
- "复杂 min/max 约束":
ConstrainedBox
- "要背景/圆角/渐变/边框":
Container + BoxDecoration
- "要阴影 + Material Ripple 一步到位":
Card
- "自己精准控制阴影与波纹细节":
Material
- "只裁剪 + 投影,无交互":
PhysicalModel
直接把整段
BoxWidgetsGallery
粘到项目里运行,即可在一页里一次看完所有效果;若只想单测某个组件,可把对应片段抽出来放进自己的Column
/Row
。
dart
import 'package:flutter/material.dart';
/// 展示常见"盒子"组件最小用法 ------ 带中文注释版
class BoxWidgetsGallery extends StatelessWidget {
const BoxWidgetsGallery({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 顶部应用栏
appBar: AppBar(title: const Text('Box & Layout Demo')),
// 使用 ListView 确保内容超出屏幕时可滚动
body: ListView(
padding: const EdgeInsets.all(16),
children: [
/* ────────────────── 1️⃣ SizedBox ────────────────── */
const Text('1. SizedBox ------ 占位 20×20'),
Row(
children: const [
Icon(Icons.circle, size: 16),
SizedBox(width: 20, height: 20), // 间隔或占位
Icon(Icons.circle_outlined, size: 16),
],
),
const Divider(),
/* ────────────────── 2️⃣ Padding ────────────────── */
const Text('2. Padding ------ 四周留白 12'),
const Padding(
padding: EdgeInsets.all(12), // 边距 12
child: Text('Hello with padding'),
),
const Divider(),
/* ────────────────── 3️⃣ Spacer ────────────────── */
const Text('3. Spacer ------ Flex 剩余空间分配'),
Row(
children: const [
Text('Left'),
Spacer(), // 占据剩余空间,把 Right 推到最右侧
Text('Right'),
],
),
const Divider(),
/* ────────────────── 4️⃣ ConstrainedBox ────────────────── */
const Text('4. ConstrainedBox ------ minWidth / maxHeight'),
ConstrainedBox(
// 最小宽 200,最大高 60
constraints: const BoxConstraints(minWidth: 200, maxHeight: 60),
child: Container(
color: Colors.orange,
alignment: Alignment.center,
child: const Text('min 200 / max 60'),
),
),
const Divider(),
/* ────────────────── 5️⃣ Container + BoxDecoration ────────────────── */
const Text('5. Container + BoxDecoration ------ 背景/圆角/阴影'),
Container(
height: 80,
alignment: Alignment.center,
decoration: BoxDecoration(
// 渐变背景
gradient: const LinearGradient(colors: [Colors.purple, Colors.red]),
borderRadius: BorderRadius.circular(12), // 圆角
boxShadow: const [ // 阴影
BoxShadow(
color: Colors.black26,
blurRadius: 4,
offset: Offset(0, 2),
)
],
),
child: const Text('Fancy Container', style: TextStyle(color: Colors.white)),
),
const Divider(),
/* ────────────────── 6️⃣ Card ────────────────── */
const Text('6. Card ------ Material 圆角 + 阴影 + Ripple'),
Card(
elevation: 2, // 阴影高度
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // 自定义圆角
),
child: const ListTile(
leading: Icon(Icons.photo),
title: Text('Card Title'),
subtitle: Text('Subtitle'),
),
),
const Divider(),
/* ────────────────── 7️⃣ Material ────────────────── */
const Text('7. Material ------ 手动控制阴影 & 水波纹'),
Material(
color: Colors.teal, // 背景色
elevation: 4, // 阴影
borderRadius: BorderRadius.circular(8),
child: InkWell( // 实现点击水波纹
borderRadius: BorderRadius.circular(8),
onTap: () {}, // 点击回调
child: const Padding(
padding: EdgeInsets.all(12),
child: Text('Material with Ink', style: TextStyle(color: Colors.white)),
),
),
),
const Divider(),
/* ────────────────── 8️⃣ PhysicalModel ────────────────── */
const Text('8. PhysicalModel ------ 裁剪 + 投影,无 Ink'),
PhysicalModel(
color: Colors.white, // 填充颜色
elevation: 6, // 阴影
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias, // 开启裁剪
child: Image.network( // 被裁剪/投影的内容
'https://picsum.photos/200',
width: 200,
height: 120,
fit: BoxFit.cover,
),
),
const Divider(),
/* ────────────────── 9️⃣ Gap(候选) ────────────────── */
const Text('9. Gap (use SizedBox) ------ 语义化间隔'),
Row(
children: const [
Text('A'),
SizedBox(width: 8), // 用 SizedBox 模拟 Gap(8)
Text('B'),
SizedBox(width: 8),
Text('C'),
],
),
],
),
);
}
}
- 运行方式 :把该文件作为
home:
,或在任意路由Navigator.push
出去即可预览。 - 结构说明 :用
ListView
避免屏幕过小时内容溢出;Divider
只是视觉分割,删除不影响组件效果。

BoxDecoration 的作用
BoxDecoration
是 Flutter 中专门用来给 矩形盒子类 Widget (如 Container
、DecoratedBox
、BoxDecoration
与 BoxConstraints
等)添加视觉装饰的对象。
简单说:它负责"这块区域长什么样" ------颜色、圆角、阴影、边框、背景图都归它管。
1. 作用概览
能力 | 关键字段 | 示意 |
---|---|---|
背景填充 | color |
单一纯色 |
渐变色 | gradient |
线性、径向、扫射渐变 |
圆角 / 圆形 | borderRadius / shape |
任意圆角矩形或正圆 |
边框 | border |
四边可独立宽度/颜色/样式 |
阴影 | boxShadow |
多重阴影、模糊半径、偏移 |
背景图 | image |
支持 fit 、repeat 、对齐 |
混合模式 | backgroundBlendMode |
背景色与图片混合效果 |
⚠️
BoxDecoration
只负责「画」;要生效必须配合 具备decoration
属性的 Widget (最常用Container
)。
2. 常见用法示例
① 纯色 + 圆角 + 阴影
dart
Container(
width: 120,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
);
② 线性渐变 + 圆形
dart
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Color(0xffFF6B6B), Color(0xffFF8E53)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
);
③ 背景图 + 半透明遮罩
dart
Container(
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(bgUrl),
fit: BoxFit.cover,
),
gradient: LinearGradient(
colors: [
Colors.black.withOpacity(0.6),
Colors.black.withOpacity(0.1),
],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
backgroundBlendMode: BlendMode.darken, // 图像与渐变混合
),
);
3. 与其他概念的区别
对象 | 用途 | 典型 Widget | 备注 |
---|---|---|---|
BoxDecoration |
背景、圆角、阴影... | Container ,DecoratedBox |
只能用于矩形/圆形盒子 |
ShapeDecoration |
各类自定义形状 | DecoratedBox |
可绘制路径 (Path) 轮廓 |
DecorationImage |
背景图 | BoxDecoration.image |
image , fit , alignment 等 |
BoxConstraints |
尺寸约束 | Layout 阶段 | 与视觉无关 |
4. 性能与最佳实践
const
优化 :纯参数常量可写const BoxDecoration(...)
,编译期复用。- 合并装饰 :同一层 Widget 只需要一个
BoxDecoration
;避免多层Container
叠加。 - 圆角裁剪 :若需要子内容也跟随圆角,额外用
ClipRRect
、ClipPath
或PhysicalModel
。 - 图像缓存 :对网络图片背景,加
cached_network_image
或precacheImage
减少闪烁。 - 不要在动画每帧重建复杂渐变/阴影 ,可用
TweenAnimationBuilder
或提前生成静态资源。
总结
BoxDecoration
就是 Flutter 的"盒子外观调色板" :
- 颜色、渐变、圆角、阴影、边框、背景图,你想到的常用视觉装饰它都能搞定。
- 它自身不渲染,需放进
Container
/DecoratedBox
才会被绘制。- 掌握
BoxDecoration
= 掌握"如何快速让一个区域看起来更好看"。
Text 与 TextField
维度 | Text | TextField |
---|---|---|
组件定位 | 只负责显示 静态文字 | 可交互的输入框(用户可编辑) |
渲染阶段 | 直接在 paint 阶段把文字绘制到屏幕,无光标、无焦点 | 内部维护 RenderEditable ,包含光标、选区、软键盘交互、剪贴板 |
典型属性 | style 、maxLines 、overflow 、textAlign 、softWrap |
controller 、keyboardType 、decoration 、focusNode 、onChanged 、obscureText |
状态管理 | 无状态 (StatelessWidget)------内容一旦传入即不可改动,需 setState 重建 |
有内部状态(StatefulWidget)------随用户输入实时更新 |
性能开销 | 轻量,通常只有一个 RenderParagraph |
略重,包含光标动画、文本布局、输入法通道等 |
交互能力 | 无 | ✓ 点击聚焦 ✓ 键盘输入 ✓ 复制/粘贴/长按菜单 |
事件回调 | 无 | onChanged 、onSubmitted 、onEditingComplete 、onTap 等 |
样式装饰 | 只能通过外部容器或 DefaultTextStyle 控制 |
自带 InputDecoration ,可轻松加下划线、label、icon、hint |
可替代方案 | RichText(富文本)、SelectableText(可选中) | CupertinoTextField、TextFormField(带表单校验) |
1. 什么时候用 Text
- 纯展示:标题、正文、说明文案、按钮文字等。
- 富文本可用 RichText 或 Text.rich。
- 需要复制但不输入,用 SelectableText 可选中但不可编辑。
dart
Text(
'请输入手机号登录',
style: TextStyle(fontSize: 16, color: Colors.black87),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
2. 什么时候用 TextField
- 需要用户输入:登录框、搜索框、评论框等。
- 如果要表单校验,可用 TextFormField +
Form
/FormField
。 - iOS 风格前端,用 CupertinoTextField。
dart
TextField(
controller: _phoneCtrl,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: '请输入手机号',
prefixIcon: Icon(Icons.phone),
border: OutlineInputBorder(),
),
onChanged: (v) => setState(() => _phone = v),
)
3. 常见注意事项
Text
- 避免层级过深 :
Text
+ 多重Container
会触发多次布局。 - 长文本截断 :
overflow: TextOverflow.ellipsis
+maxLines
组合最省心。 - 多样式片段 :别用多个
Text
拼,直接上RichText
。
TextField
- 自动释放 :在
State
中用TextEditingController
要记得dispose()
。 - 键盘遮挡 :可用
SingleChildScrollView
或resizeToAvoidBottomInset
解决。 - 性能 :大量
TextField
(如群聊气泡里)要做懒加载或复用,注意ListView.builder
。 - 安全输入 :密码框
obscureText: true
,或自定义inputFormatters
限制内容。
4. 总结口诀
Text:看就完了;TextField:改得了、回调多。
只展示 →
Text / RichText / SelectableText
要输入 →
TextField / TextFormField / CupertinoTextField
SizedBox的原理与使用
1 SizedBox 是什么?
SizedBox
是 Flutter 最轻量级 的尺寸限制组件之一。
它本质上只是把传入的 width
、height
(或来自便捷构造函数的固定策略)转换成 BoxConstraints
,再把这些约束 套给子组件 。自身并不会绘制任何像素,也不做布局计算以外的事情,因此 零渲染开销 ,调试时在 Repaint Rainbow
下也是透明的。
构造函数 | 等价约束 | 典型用途 |
---|---|---|
SizedBox(width, height) |
同时 给宽高 tight = true 的约束 |
精确占位、固定按钮/图标尺寸 |
SizedBox.expand() |
BoxConstraints.expand() → 占满父组件能给的空间 |
Stack /Flex 中拉满剩余空间 |
SizedBox.shrink() |
BoxConstraints.tight(Size.zero) |
做「占位但不占空间」的节点 |
SizedBox.fromSize() |
按传入 Size 生成 tight 约束 |
根据图片宽高等动态尺寸占位 |
2 内部原理(简版)
-
布局阶段
-
若有子组件 → 直接把
tight
或loose
约束 拷贝给它:dart// RenderConstrainedBox.performLayout() child!.layout(constraints.enforce(_additionalConstraints), ...); size = constraints.constrain(_additionalConstraints.smallest);
-
若 无子组件 ,则自己根据约束确定
size
,因此SizedBox(width, height)
也能单独占位。
-
-
绘制阶段
- 自身不 Paint;有子组件时把
paint()
直接透传给 child。
- 自身不 Paint;有子组件时把
简言之:它只是包了一层 BoxConstraints
。这也是为什么 SizedBox
体积几乎恒为 0 B(仅简单字段)。
3 常见用法与案例
3.1 固定按钮宽高
dart
SizedBox(
width: 120,
height: 40,
child: ElevatedButton(
onPressed: () {},
child: const Text('登录'),
),
);
ElevatedButton
内部会先"拿"到一个 紧约束 (tight) ,所以无论文字多长,都被裁切或自动缩放到 120×40。
3.2 Row
/ Column
中的空隙
dart
Row(
children: const [
Icon(Icons.star),
SizedBox(width: 8), // 简洁替代 EdgeInsets.only(left)
Text('评分 4.8'),
],
);
理由:
- 创建一个无子组件的
SizedBox
,宽度固定8
,高度随父约束自动适配。 - 比
Container(margin: ...)
少一次深层布局,语义更直观。
3.3 Stack
中撑满
dart
Stack(
children: [
const SizedBox.expand(child: ColoredBox(color: Colors.black54)),
Center(child: Text('暂停中', style: TextStyle(fontSize: 32, color: Colors.white))),
],
);
SizedBox.expand()
→ 给子组件一个 尽可能大的 tight 约束,即占满父组件可用空间。- 这里用于半透明遮罩,等效于
Positioned.fill
但写法更短。
3.4 作为「空占位」
dart
return hasData
? ListView(children: items)
: const Center(child: CircularProgressIndicator());
/* ...某处... */
SizedBox.shrink(), // 想要"什么都不显示"又需要一个 Widget 占位时用
sizedBox.shrink()
大小为 (0, 0),性能优于 Container()
或 SizedBox(width: 0, height: 0)
,同时避免空 Container
报 "无约束" 警告。
3.5 动态尺寸占位
dart
// 假设想按网络图片真实宽高占位以避免布局跳动
final ImageProvider provider = NetworkImage(url);
final Completer<Size> completer = Completer();
provider.resolve(const ImageConfiguration()).addListener(
ImageStreamListener((info, _) {
completer.complete(Size(
info.image.width.toDouble(),
info.image.height.toDouble(),
));
}),
);
return FutureBuilder<Size>(
future: completer.future,
builder: (_, snapshot) {
if (!snapshot.hasData) return const SizedBox.shrink();
return SizedBox.fromSize(
size: snapshot.data!,
child: Image(image: provider),
);
},
);
4 易错点
场景 | 现象 | 解决方案 |
---|---|---|
把 SizedBox 放进 Column 且忘记指定 height |
报错 "Vertical viewport was given unbounded height" | 给 height 、flex 或外层 Expanded |
在 ListView.builder 的 itemBuilder 里直接 return SizedBox(height: xxx) |
期望 item 有内容却为空白 | 别忘了把内容当作 child 或创建其他组件 |
用 SizedBox.expand() 嵌套在 Row /Column |
如果主轴方向同向 → 可能溢出 | 改用 Expanded(child: ...) 更安全 |
5 总结
- 本质 :把给定宽高转成
BoxConstraints
,再传递给 child;自己不绘制。 - 常用:占位、空隙、撑满空间、0 × 0 缩成空节点。
- 性能 :零绘制、极低布局成本,比
Container
更轻;能用SizedBox
就别用Container
。
掌握了 SizedBox
等同于熟悉了 Flutter 布局约束思想的 50 %,在日常 UI 开发中几乎无处不在。
Container
1 | Container 是什么?
- Container 也是一个 Widget 。
在 Flutter 中,所有可组合的 UI 节点(哪怕只是一个空占位)都是 Widget;Container
只是官方提供的"瑞士军刀"式组合-Widget ,把常见布局/装饰功能(padding
、margin
、alignment
、constraints
、decoration
、transform
...)打包到一个类里。 - 它自身并不直接渲染,而是 在
build()
里拆成一串更原始的 Widget (Padding
→Align
→ConstrainedBox
→DecoratedBox
...),再由这些底层 Widget 对应的 RenderObject 完成布局与绘制。
2 | Android ViewGroup 概念回顾
- ViewGroup 是 Android View 层次结构中的"容器类",能持有 多子 View。
- 典型子类:
LinearLayout
、RelativeLayout
、FrameLayout
、ConstraintLayout
等。 - 自带 3 pass 生命周期:
measure()
➜layout()
➜draw()
,并通过LayoutParams
与父节点沟通尺寸需求。
3 | 相同点
维度 | Container | ViewGroup |
---|---|---|
角色 | "装饰+布局"容器 | "布局"容器 |
功能 | 决定子组件尺寸、位置并可添加背景/圆角/阴影等视觉效果 | 决定子 View 尺寸与位置,部分子类也能绘制背景 |
嵌套 | 可以无限嵌套 Widgets | 可以无限嵌套 View/ViewGroup |
零开销原则 | 参数为空时直接 返回 child,不会造成多余层级 | 空 ViewGroup 仍旧会参与 3-pass 流程 |
4 | 关键差异
维度 | Flutter Container | Android ViewGroup |
---|---|---|
子节点数量 | 只能 1 个 child(想多子需用 Column、Row、Stack...) | 可以任意多个子 View |
创建成本 | 仅创建一个无状态 Widget;真正开销在其拆分出的 RenderObject;空属性时零额外 RenderObject | 创建实际 Java/Kotlin 对象并多一次 measure/layout/draw |
声明式 vs 命令式 | UI = 函数(state);重新 build 时重新生成 Widget 树 | 在 view 对象上 imperative 调用 setXxx() 改属性 |
线程模型 | Flutter UI 全在 Dart UI 线程(除 raster 线程) | View 及其 3-pass 运行在 Android 主线程 |
渲染管线 | Skia ➜ layer ➜ GPU;无需桥接 JNI | Skia (Canvas) ➜ JNI ➜ HWUI ➜ GPU |
组合思路 | 小颗粒,"一事一 Widget",靠组合拼装 | 常见功能集中进一个布局类(如 ConstraintLayout) |
性能调优 | 去掉多余 Container ⇒ 无新 RenderObject | 减少层级、使用 ConstraintLayout、开启剪裁等 |
5 | 为什么 Container 只能 1 个 child?
Container
旨在装饰 或约束 某个"盒子",语义上对应 CSS 里的 .box { padding / margin / background / transform }
。
要放多子,一般用:
Row / Column
:等同 AndroidLinearLayout (H/V)
Stack
:等同FrameLayout
Flex
:更接近ConstraintLayout
的弹性特性(配合Expanded
/Flexible
)
6 | 实战示例对照
dart
// Flutter:水平 8dp 间距、蓝色背景 + 圆角
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: const Text('Hello'),
)
xml
<!-- Android XML:效果相近 -->
<LinearLayout
android:padding="8dp"
android:background="@drawable/rounded_blue_bg">
<TextView android:text="Hello"/>
</LinearLayout>
7 | 总结
Container = 单子版万能布局 ; ViewGroup = 多子版布局树节点
- 如果 只需要给一个 Widget 加装饰或简单约束 ,
Container
最方便。 - 如果 要同时排布多个子节点 ,直接选
Row / Column / Stack / Flex / CustomMultiChildLayout
,就像在 Android 用不同 ViewGroup 子类一样。 - Flutter 的 Widget 树 ≈ Android 的 XML layout,但 构建方式声明-函数式,背后渲染/布局管线完全不同,也让多层组合成本更低。