目标
· 理解 Flutter 布局核心口诀:父传约束,子定尺寸,父定位置。
· 分清 Row / Column 的主轴与交叉轴,掌握 mainAxisAlignment 和 crossAxisAlignment。
· 掌握 Container、Padding、SizedBox、Expanded、Spacer 的用途。
· 能组合实现个人信息行、上中下布局、商品卡片、音乐播放器。
· 能识别并修复 RenderFlex overflowed 溢出错误。
· 通过"题目要求 → 答案 → 答案解读"的多元化习题完成练中学。
一、本课核心概念速览
Flutter 布局口诀:
父传约束,子定尺寸,父定位置。
常用布局 Widget:
Widget 作用
Row 水平排列
Column 垂直排列
Container 盒子:尺寸、颜色、边距、圆角、对齐
Padding 内边距
Margin 外边距,通常用 Container 的 margin
Expanded 按比例占满剩余空间
Flexible 灵活占空间,可松可紧
Spacer 占剩余空间,做间隔
SizedBox 固定宽高或间距
Align / Center 对齐 / 居中
MainAxisAlignment 主轴对齐
CrossAxisAlignment 交叉轴对齐
Row 的主轴是水平,交叉轴是垂直。
Column 的主轴是垂直,交叉轴是水平。
二、最小可运行示例
把 lib/main.dart 改成:
dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Text('左'),
Text('中'),
Text('右'),
],
),
),
),
);
}
}
按 r 热重载,把 spaceEvenly 改成 spaceBetween、center、end,观察变化。
三、多元化习题
练习0:热重载体验布局
题目要求
运行上面的 Row 示例,依次把 mainAxisAlignment 改成:
· MainAxisAlignment.start
· MainAxisAlignment.center
· MainAxisAlignment.end
· MainAxisAlignment.spaceBetween
· MainAxisAlignment.spaceAround
· MainAxisAlignment.spaceEvenly
记录每个值的效果。
答案
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('左'),
Text('中'),
Text('右'),
],
)
各值效果:
· start:靠左。
· center:整体居中。
· end:靠右。
· spaceBetween:两端贴边,中间等距。
· spaceAround:每个子项两侧等距。
· spaceEvenly:所有间隔完全相等,包括两端。
答案解读
mainAxisAlignment 控制主轴方向的对齐。Row 的主轴是水平,所以这些值控制水平分布。Column 的主轴是垂直,同样的值会控制垂直分布。热重载可以立刻看到差异,是练布局最快的方式。
练习1:选择题
题目要求
在 Column 中,crossAxisAlignment 控制的是哪个方向?
A. 垂直方向
B. 水平方向
C. 文字颜色
D. 内外边距
答案
B。
答案解读
Column 的主轴是垂直,交叉轴是水平。所以 mainAxisAlignment 控制垂直对齐,crossAxisAlignment 控制水平对齐。Row 刚好相反。
练习2:判断题
题目要求
判断对错,并说明理由:
- Expanded 必须直接放在 Row、Column 或 Flex 的子级中。
- Container 的 padding 是外边距,margin 是内边距。
- Spacer 可以用在 Row 和 Column 中。
- SizedBox 只能用来显示文字。
答案
- 对。
- 错。
- 对。
- 错。
答案解读
Expanded 依赖 Flex 父级分配剩余空间,所以不能随便放在普通 Container 里。padding 是内边距,margin 是外边距。Spacer 本质是 Expanded(child: SizedBox()),可用在 Row/Column。SizedBox 是尺寸盒子,常用于占位和间距,不显示文字。
练习3:连线题
题目要求
将左侧 Widget 与右侧作用连线:
左侧 右侧
mainAxisAlignment 交叉轴对齐
crossAxisAlignment 主轴对齐
Expanded 固定尺寸或间距
Spacer 按比例占剩余空间
SizedBox 占剩余空间形成间隔
Padding 内边距
答案
· mainAxisAlignment --- 主轴对齐
· crossAxisAlignment --- 交叉轴对齐
· Expanded --- 按比例占剩余空间
· Spacer --- 占剩余空间形成间隔
· SizedBox --- 固定尺寸或间距
· Padding --- 内边距
答案解读
这些是布局系统最常用的成员。先分清主轴和交叉轴,再理解剩余空间怎么分配,布局就不容易乱。
练习4:排序题
题目要求
实现"页面居中显示一个圆角盒子,盒子里有内边距,文字居中"。按从外到内的嵌套顺序排列:
Text、Center、Container、Padding
答案
Center → Container → Padding → Text
答案解读
Center 负责整体居中,Container 负责尺寸、颜色、圆角,Padding 负责文字与盒子边缘的距离,Text 是最内层内容。实际开发中,Container 自带 padding,也可以省掉单独的 Padding。
练习5:填空题
题目要求
补全代码,让三个文字"左、中、右"分别贴在两端和中间:
dart
Row(
mainAxisAlignment: ______,
children: const [
Text('左'),
Text('中'),
Text('右'),
],
)
答案
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('左'),
Text('中'),
Text('右'),
],
)
答案解读
spaceBetween 会把第一个和最后一个推到两端,中间的子项等距分布。做顶部栏、底部栏、左右分布时非常常用。
练习6:代码预测题
题目要求
阅读代码,预测屏幕显示效果:
dart
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text('上'),
SizedBox(height: 20),
Text('下'),
],
),
)
答案
"上"和"下"垂直排列,整体在屏幕中间,两者之间有 20 像素间距。
答案解读
Center 让 Column 在页面居中。Column 的 mainAxisAlignment.center 让子项在垂直方向整体居中。SizedBox(height: 20) 提供固定垂直间距。
练习7:改错题
题目要求
下面代码会导致布局溢出,找出来并修正:
dart
Row(
children: const [
Text('这是一段非常非常长的文字,可能会撑爆屏幕'),
Text('这也是一段非常非常长的文字,也可能撑爆屏幕'),
],
)
答案
修正方式之一:
dart
Row(
children: const [
Expanded(
child: Text(
'这是一段非常非常长的文字,可能会撑爆屏幕',
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Text(
'这也是一段非常非常长的文字,也可能撑爆屏幕',
overflow: TextOverflow.ellipsis,
),
),
],
)
答案解读
Row 主轴方向给子项无限宽度约束,长文字会按自身宽度撑开,超出屏幕就报 RenderFlex overflowed。用 Expanded 让文字按剩余空间分配宽度,再用 overflow: TextOverflow.ellipsis 显示省略号。这是实际开发中最常见的布局修复之一。
练习8:补全代码题
题目要求
补全代码,做一个 120×60 的蓝色圆角盒子,内部有 12 内边距,外边距 8,文字白色居中:
dart
Container(
width: 120,
height: 60,
padding: const EdgeInsets.all(______),
margin: const EdgeInsets.all(______),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(______),
),
alignment: Alignment.______,
child: const Text(
'按钮',
style: TextStyle(color: Colors.white),
),
)
答案
dart
Container(
width: 120,
height: 60,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: const Text(
'按钮',
style: TextStyle(color: Colors.white),
),
)
答案解读
padding 是盒子内部留白,margin 是盒子外部留白。BoxDecoration 用来设置背景色和圆角。alignment: Alignment.center 让 child 在盒子内居中。注意:使用 decoration 时,颜色要写在 BoxDecoration 里,不能同时写 color。
练习9:动手编码题:个人信息行
题目要求
用 Row 做一行个人信息:左边一个圆形头像,中间显示姓名和职业,右边一个按钮。要求中间区域用 Expanded 包裹,防止文字太长溢出。
答案
dart
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: const Text(
'张',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'张三',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text('Flutter 开发者'),
],
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: () {},
child: const Text('关注'),
),
],
),
),
)
答案解读
Row 水平排列头像、信息、按钮。中间用 Expanded 占剩余宽度,避免长文字溢出。Column 的 crossAxisAlignment.start 让姓名和职业左对齐。mainAxisSize: MainAxisSize.min 让 Column 高度尽量小。圆形头像用 BoxShape.circle。
练习10:动手编码题:上中下布局
题目要求
用 Column 和 Expanded 做上中下布局:顶部固定高度 60,中间占满剩余空间,底部固定高度 60。三个区域用不同颜色区分。
答案
dart
body: Column(
children: [
Container(
height: 60,
color: Colors.blue,
alignment: Alignment.center,
child: const Text('顶部', style: TextStyle(color: Colors.white)),
),
Expanded(
child: Container(
color: Colors.grey.shade200,
alignment: Alignment.center,
child: const Text('中间占满剩余空间'),
),
),
Container(
height: 60,
color: Colors.green,
alignment: Alignment.center,
child: const Text('底部', style: TextStyle(color: Colors.white)),
),
],
)
答案解读
Column 垂直排列。顶部和底部用固定高度,中间用 Expanded 吃掉剩余高度。这是 App 页面最经典的骨架:顶部栏 + 内容区 + 底部栏。
练习11:综合挑战:商品卡片
题目要求
做一个商品卡片:外层圆角阴影,内部有内边距。左边一个商品图标,中间显示商品名和描述,右边显示价格和购买按钮。要求使用 Container、Padding、Row、Column、Expanded、SizedBox。
答案
dart
body: Center(
child: Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: const Icon(Icons.shopping_bag, color: Colors.orange, size: 32),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Flutter 实战课',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 6),
Text(
'从零开始,练中学布局系统',
style: TextStyle(color: Colors.grey),
),
],
),
),
const SizedBox(width: 12),
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'¥99',
style: TextStyle(
fontSize: 18,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {},
child: const Text('购买'),
),
],
),
],
),
),
)
答案解读
外层 Container 负责卡片边距、圆角、阴影。内部 Row 分三列:图标、信息、价格按钮。中间信息列用 Expanded 占剩余宽度。右侧价格按钮用 Column 垂直排列。这个练习把本课大部分布局知识综合了起来。
练习12:开放式挑战:音乐播放器界面
题目要求
用 Column、Expanded、Row、Container、Icon 设计一个简单的音乐播放器界面。要求:
· 顶部显示歌曲名;
· 中间 Expanded 区域显示专辑封面占位;
· 底部一排控制按钮:上一首、播放、下一首。
答案示例
dart
body: SafeArea(
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Text(
'正在播放:练中学',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
Expanded(
child: Container(
margin: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.deepPurple.shade100,
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
child: const Icon(
Icons.music_note,
size: 80,
color: Colors.deepPurple,
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.skip_previous, size: 36),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.play_circle_fill, size: 64),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.skip_next, size: 36),
),
],
),
),
],
),
)
答案解读
整体用 Column:顶部标题、中间 Expanded 封面、底部控制栏。中间 Expanded 占满剩余空间。底部 Row 用 spaceEvenly 均匀排列按钮。开放题没有唯一答案,重点是布局结构清晰、能运行、能热重载修改。
练习13:错误观察挑战
题目要求
故意制造一次 RenderFlex overflowed 错误:在 Row 中放两个很长的 Text,不要用 Expanded。运行后观察控制台错误,记录关键错误词。
答案
示例错误代码:
dart
Row(
children: const [
Text('这是一段非常非常非常非常非常非常非常长的文字'),
Text('这也是一段非常非常非常非常非常非常非常长的文字'),
],
)
控制台常见错误:
text
A RenderFlex overflowed by 123 pixels on the right.
答案解读
RenderFlex 是 Row/Column 的渲染对象。当子项总宽度超过可用宽度时,就会溢出。右侧溢出会提示 on the right,Column 溢出会提示 on the bottom。解决方法:用 Expanded、Flexible、SingleChildScrollView 或 TextOverflow.ellipsis。学会看这个错误,布局能力会提升很快。
四、本课小结
这一课你练了:
- Row 和 Column 的主轴、交叉轴;
- mainAxisAlignment 和 crossAxisAlignment;
- Container、Padding、Margin、SizedBox;
- Expanded、Spacer 分配剩余空间;
- 用 Row/Column 组合出个人信息行、上中下布局、商品卡片、音乐播放器;
- 通过故意制造溢出,学会看 RenderFlex overflowed 错误。
下一课可以进入 滚动与列表:ListView、SingleChildScrollView、GridView,继续用"练中学"的方式做可滚动页面。