掌握层叠布局与空间分配,构建更复杂的UI界面。
你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们学习了Container、Row和Column这三个基础布局Widget。今天我们将继续探索另外三个极其重要的布局Widget:Stack、Expanded和SizedBox,它们将帮助您解决更复杂的布局需求。
一、层叠布局:Stack与Positioned
Stack 允许您将多个子Widget重叠在一起。这在需要创建重叠效果、浮动按钮、徽章(badge)等场景下非常有用。
1.1 Stack 的基本用法
dart
Stack(
children: <Widget>[
// 底层:背景图片
Container(
width: 300,
height: 200,
color: Colors.grey[300],
),
// 中层:文字内容
Positioned(
bottom: 20,
left: 20,
child: Text(
'Flutter开发',
style: TextStyle(
fontSize: 24,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
// 上层:徽章
Positioned(
top: 10,
right: 10,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'NEW',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
),
],
)
1.2 Stack 的对齐方式
Stack 通过 alignment 属性来控制未定位子Widget的默认对齐方式:
dart
Stack(
alignment: Alignment.center, // 所有未定位的子Widget都会居中对齐
children: <Widget>[
Container(
width: 200,
height: 200,
color: Colors.blue,
),
// 这个Text没有使用Positioned,所以会遵循Stack的alignment
Text(
'居中文本',
style: TextStyle(color: Colors.white, fontSize: 18),
),
],
)
1.3 Positioned 的精确控制
Positioned 用于在Stack中精确控制子Widget的位置:
dart
Positioned( top: 20, // 距离顶部的距离 left: 30, // 距离左侧的距离 right: 40, // 距离右侧的距离 bottom: 50, // 距离底部的距离 width: 100, // 宽度(如果同时指定left和right,width会被忽略) height: 80, // 高度(如果同时指定top和bottom,height会被忽略) child: YourWidget(), )
重要提示:在 Positioned 中,您可以组合使用 top/bottom 和 left/right 来实现灵活的定位。
1.4 实战:用户头像叠加效果
dart
class AvatarStack extends StatelessWidget {
const AvatarStack({super.key});
@override
Widget build(BuildContext context) {
return Stack(
children: [
// 第一个头像
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage('https://example.com/avatar1.jpg'),
),
// 第二个头像,偏移显示
Positioned(
left: 20,
child: CircleAvatar(
radius: 30,
backgroundImage: NetworkImage('https://example.com/avatar2.jpg'),
),
),
// 第三个头像,更多偏移
Positioned(
left: 40,
child: CircleAvatar(
radius: 30,
backgroundColor: Colors.grey[300],
child: Text('+3', style: TextStyle(color: Colors.black)),
),
),
],
);
}
}
二、空间分配:Expanded 与 Flexible
当在 Row 或 Column 中需要子Widget按比例分配空间时,Expanded 和 Flexible 就派上用场了。
2.1 Expanded - 充满剩余空间
Expanded 让子Widget填充主轴上的剩余可用空间。
dart
Row(
children: <Widget>[
Container(
width: 80,
height: 60,
color: Colors.red,
child: Center(child: Text('固定宽度')),
),
Expanded( // 这个Container会填充Row中剩余的所有宽度
child: Container(
height: 60,
color: Colors.blue,
child: Center(child: Text('自适应宽度')),
),
),
],
)
2.2 按比例分配空间
多个 Expanded 可以通过 flex 属性按比例分配空间:
dart
Row(
children: <Widget>[
Expanded(
flex: 2, // 占2份
child: Container(
height: 60,
color: Colors.red,
child: Center(child: Text('2/6')),
),
),
Expanded(
flex: 3, // 占3份
child: Container(
height: 60,
color: Colors.green,
child: Center(child: Text('3/6')),
),
),
Expanded(
flex: 1, // 占1份
child: Container(
height: 60,
color: Colors.blue,
child: Center(child: Text('1/6')),
),
),
],
)
2.3 Flexible 与 Expanded 的区别
Expanded 实际上是 Flexible 的一个特例(flex: 1, fit: FlexFit.tight)。
-
Expanded:强制子Widget填充满分配的空间 -
Flexible:允许子Widget不填满分配的空间(默认行为)
dart
Row(
children: <Widget>[
Flexible(
child: Container(
height: 60,
color: Colors.red,
// 这个Container只会占用实际需要的宽度,而不是全部可用宽度
child: Center(child: Text('Flexible - 按需宽度')),
),
),
Expanded(
child: Container(
height: 60,
color: Colors.blue,
child: Center(child: Text('Expanded - 充满宽度')),
),
),
],
)
三、尺寸控制:SizedBox
SizedBox 是一个简单的盒子,它可以用来:
-
设置固定的尺寸
-
强制子Widget具有特定尺寸
-
在两个Widget之间添加间距
3.1 作为固定尺寸容器
dart
SizedBox(
width: 100,
height: 100,
child: ElevatedButton(
onPressed: () {},
child: Text('按钮'),
),
)
3.2 作为间距控件
这是 SizedBox 最常见的用法之一:
dart
Column(
children: <Widget>[
Text('第一行文本'),
SizedBox(height: 20), // 添加20像素的垂直间距
Text('第二行文本'),
SizedBox(height: 30), // 添加30像素的垂直间距
Text('第三行文本'),
],
)
3.3 占位空间
使用 SizedBox.shrink() 创建一个零尺寸的占位Widget,或者用固定尺寸占位:
dart
Row(
children: <Widget>[
Text('开始'),
SizedBox(width: 50), // 占位50像素宽度
Text('结束'),
],
)
// 或者使用 Expanded + SizedBox 实现灵活的占位
Row(
children: <Widget>[
Text('左侧内容'),
Expanded( // 这个SizedBox会填充所有剩余空间
child: SizedBox.shrink(), // 零尺寸,但占用空间
),
Text('右侧内容'),
],
)
四、综合实战:构建一个复杂的卡片布局
现在让我们综合运用今天学到的所有Widget,构建一个复杂的电商产品卡片:
dart
class ComplexProductCard extends StatelessWidget {
const ComplexProductCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(16),
width: 280,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图片区域 - 使用Stack实现叠加效果
Stack(
children: [
// 产品图片
Container(
height: 180,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
image: DecorationImage(
image: NetworkImage('https://example.com/product.jpg'),
fit: BoxFit.cover,
),
),
),
// 折扣标签
Positioned(
top: 12,
left: 12,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'30% OFF',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
// 收藏按钮
Positioned(
top: 12,
right: 12,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
blurRadius: 4,
),
],
),
child: Icon(Icons.favorite_border, size: 18),
),
),
],
),
SizedBox(height: 12),
// 内容区域
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'高端无线蓝牙耳机',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'沉浸式音效,超长续航,智能降噪',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
SizedBox(height: 12),
// 价格和评分行 - 使用Expanded实现自动对齐
Row(
children: [
Expanded(
child: Text(
'\$199.99',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
Row(
children: [
Icon(Icons.star, color: Colors.orange, size: 16),
SizedBox(width: 4),
Text('4.8', style: TextStyle(fontSize: 14)),
],
),
],
),
SizedBox(height: 16),
// 按钮行 - 使用Expanded实现等宽按钮
Row(
children: [
Expanded(
flex: 2,
child: OutlinedButton(
onPressed: () {},
child: Text('加入购物车'),
),
),
SizedBox(width: 12),
Expanded(
flex: 3,
child: ElevatedButton(
onPressed: () {},
child: Text('立即购买'),
),
),
],
),
SizedBox(height: 16),
],
),
),
],
),
);
}
}
布局技巧总结
-
使用
Stack时:注意子Widget的叠加顺序,后面的Widget会覆盖前面的Widget。 -
使用
Expanded时:确保父Widget(Row/Column)有明确的主轴尺寸约束。 -
使用
SizedBox时:相比于使用空的Container添加间距,SizedBox性能更好且语义更清晰。 -
组合使用:
Expanded+SizedBox可以创建灵活的间距,Stack+Positioned可以创建复杂的重叠效果。
结语
恭喜!通过本讲的学习,你已经掌握了Flutter中另外三个关键的布局Widget。现在你已具备了构建绝大多数常见UI界面所需的核心布局能力:
-
Stack用于创建层叠布局 -
Expanded和Flexible用于空间分配 -
SizedBox用于精确尺寸控制和间距
在下一讲中,我们将进行一个完整的实战项目——构建一个登录页面,将前七讲学到的所有知识综合运用到一个实际场景中。
987

被折叠的 条评论
为什么被折叠?



