flutter组件学习之Stack 组件详解

Flutter Stack 组件详解

Stack 组件是 Flutter 中用于叠加布局的核心组件,可以将多个子组件按照层级堆叠在一起,类似于 CSS 中的绝对定位。

基本概念

1. Stack 的基本结构

dart 复制代码
Stack(
children: <Widget>[
// 底层组件
Container(
width: 200,
height: 200,
color: Colors.red,
),

// 上层组件
Container(
width: 150,
height: 150,
color: Colors.green,
),

// 更上层组件
Container(
width: 100,
height: 100,
color: Colors.blue,
),
],
)

2. Stack 的核心属性

dart 复制代码
Stack(
alignment: Alignment.center,// 子组件对齐方式
fit: StackFit.loose,// 非定位子组件的尺寸约束
clipBehavior: Clip.hardEdge,// 溢出内容的裁剪行为
children: <Widget>[
// 子组件
],
)

子组件定位

1. 使用 Positioned 定位

dart 复制代码
Stack(
children: <Widget>[
// 底部红色方块
Container(
width: 200,
height: 200,
color: Colors.red,
),

// 右上角绿色方块
Positioned(
top: 10,// 距离顶部 10
right: 10,// 距离右侧 10
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),

// 左下角蓝色方块
Positioned(
bottom: 10,// 距离底部 10
left: 10,// 距离左侧 10
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),

// 居中黄色方块
Positioned(
top: 50,// 距离顶部 50
left: 50,// 距离左侧 50
right: 50,// 距离右侧 50
bottom: 50,// 距离底部 50
child: Container(
color: Colors.yellow,
),
),
],
)

2. Positioned 的约束关系

dart 复制代码
Stack(
children: <Widget>[
// 固定宽度和高度
Positioned(
top: 10,
left: 10,
width: 100,// 固定宽度
height: 100,// 固定高度
child: Container(color: Colors.red),
),

// 横向拉伸(左10,右10)
Positioned(
top: 120,
left: 10,
right: 10,// 距离右侧10,会拉伸宽度
height: 50,// 固定高度
child: Container(color: Colors.green),
),

// 纵向拉伸(上200,下10)
Positioned(
top: 200,
bottom: 10,// 距离底部10,会拉伸高度
left: 10,
width: 50,// 固定宽度
child: Container(color: Colors.blue),
),

// 四个方向都指定(充满整个区域)
Positioned.fill(// 等同于 top:0, left:0, right:0, bottom:0
child: Container(
color: Colors.black.withOpacity(0.3),
),
),
],
)

Stack 的对齐方式

1. alignment 属性

dart 复制代码
// 顶部居中
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 100, height: 100, color: Colors.green),
],
)

// 底部右侧
Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 100, height: 100, color: Colors.green),
],
)

// 自定义对齐
Stack(
alignment: Alignment(0.5, 0.5),// x: 0.5, y: 0.5
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 100, height: 100, color: Colors.green),
],
)

2. alignment 与 Positioned 的区别

dart 复制代码
Stack(
alignment: Alignment.center,// 只影响非Positioned的子组件
children: <Widget>[
// 非Positioned,受alignment影响
Container(
width: 200,
height: 200,
color: Colors.red,
),

// Positioned,不受alignment影响
Positioned(
top: 10,
left: 10,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
],
)

Stack 的尺寸约束

1. fit 属性

dart 复制代码
// StackFit.loose - 宽松约束(默认)
Stack(
fit: StackFit.loose,
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
],
)

// StackFit.expand - 扩展填充
Stack(
fit: StackFit.expand,
children: <Widget>[
Container(color: Colors.red),// 会填充整个Stack
],
)

// StackFit.passthrough - 传递约束
Stack(
fit: StackFit.passthrough,
children: <Widget>[
Container(color: Colors.red),
],
)

2. Stack 尺寸计算示例

dart 复制代码
// 示例1:Stack 尺寸由最大子组件决定
Container(
width: 300,
height: 300,
color: Colors.grey,
child: Stack(
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
Container(width: 200, height: 200, color: Colors.green),
Container(width: 150, height: 150, color: Colors.blue),
],
),
)

// 示例2:使用Positioned扩展Stack尺寸
Container(
color: Colors.grey,
child: Stack(
children: <Widget>[
Positioned(
right: -50,// 超出右侧边界
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
],
),
)

裁剪行为

1. clipBehavior 属性

dart 复制代码
// Clip.none - 不裁剪
Stack(
clipBehavior: Clip.none,
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
Positioned(
left: 80,// 部分超出Stack边界
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
],
)

// Clip.hardEdge - 硬边裁剪(默认)
Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
Positioned(
left: 80,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
],
)

// Clip.antiAlias - 抗锯齿裁剪
Stack(
clipBehavior: Clip.antiAlias,
children: <Widget>[
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20),
),
),
Positioned(
left: 80,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(20),
),
),
),
],
)

2. 边界条件处理

dart 复制代码
Container(
width: 200,
height: 200,
color: Colors.grey,
child: Stack(
clipBehavior: Clip.none,// 允许子组件超出边界
children: <Widget>[
// 超出Stack边界
Positioned(
top: -20,
left: -20,
child: Container(
width: 50,
height: 50,
color: Colors.red,
),
),

// 超出Stack边界
Positioned(
bottom: -20,
right: -20,
child: Container(
width: 50,
height: 50,
color: Colors.blue,
),
),
],
),
)

实战应用示例

1. 图片叠加文字

dart 复制代码
Container(
width: 200,
height: 150,
child: Stack(
children: <Widget>[
// 背景图片
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 150,
fit: BoxFit.cover,
),

// 底部渐变遮罩
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.8),
Colors.transparent,
],
),
),
),
),

// 标题文字
Positioned(
bottom: 20,
left: 10,
child: Text(
'图片标题',
style: TextStyle(
color: Colors.white,
fontSize: 16,
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,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
)

2. 头像叠加状态指示器

dart 复制代码
Stack(
children: <Widget>[
// 头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage('https://example.com/avatar.jpg'),
fit: BoxFit.cover,
),
),
),

// 在线状态指示器
Positioned(
right: 5,
bottom: 5,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),

// VIP标识
Positioned(
top: 0,
left: 0,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Colors.yellow,
shape: BoxShape.circle,
),
child: Icon(
Icons.star,
size: 16,
color: Colors.orange,
),
),
),
],
)

3. 进度条叠加

dart 复制代码
Container(
width: 200,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey[300],
),
child: Stack(
children: <Widget>[
// 进度条背景
Container(
width: 200,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey[300],
),
),

// 进度条前景
Container(
width: 120,// 进度60%
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
gradient: LinearGradient(
colors: [Colors.blue, Colors.lightBlue],
),
),
),

// 进度文本
Positioned.fill(
child: Center(
child: Text(
'60%',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
)

4. 浮动操作按钮

dart 复制代码
Container(
width: 300,
height: 500,
color: Colors.grey[200],
child: Stack(
children: <Widget>[
// 主要内容
ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text('项目 $index'),
);
},
),

// 右下角浮动按钮
Positioned(
bottom: 20,
right: 20,
child: FloatingActionButton(
onPressed: () {
print('按钮被点击');
},
child: Icon(Icons.add),
backgroundColor: Colors.blue,
),
),
],
),
)

5. 卡片叠加效果

dart 复制代码
Container(
width: 200,
height: 300,
child: Stack(
children: <Widget>[
// 底层卡片
Positioned(
top: 20,
left: 10,
child: Container(
width: 180,
height: 280,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
offset: Offset(2, 2),
),
],
),
),
),

// 中层卡片
Positioned(
top: 10,
left: 5,
child: Container(
width: 180,
height: 280,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
offset: Offset(2, 2),
),
],
),
),
),

// 顶层卡片
Container(
width: 180,
height: 280,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
offset: Offset(2, 2),
),
],
),
child: Column(
children: [
Container(
height: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
),
image: DecorationImage(
image: NetworkImage('https://example.com/image.jpg'),
fit: BoxFit.cover,
),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Text(
'卡片标题',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
)

6. 图标徽章

dart 复制代码
Stack(
children: <Widget>[
// 图标
IconButton(
icon: Icon(Icons.notifications),
iconSize: 30,
onPressed: () {
print('通知按钮被点击');
},
),

// 消息数量徽章
Positioned(
top: 5,
right: 5,
child: Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: BoxConstraints(
minWidth: 18,
minHeight: 18,
),
child: Center(
child: Text(
'5',
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
),
],
)

7. 加载状态叠加

dart 复制代码
class LoadingOverlay extends StatelessWidget {
final bool isLoading;
final Widget child;

LoadingOverlay({
required this.isLoading,
required this.child,
});

@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
// 主要内容
child,

// 加载遮罩
if (isLoading)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
),
),
],
);
}
}

// 使用示例
LoadingOverlay(
isLoading: true,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text('主要内容'),
),
),
)

性能优化和注意事项

1. 避免过度使用 Stack

dart 复制代码
// 错误示例:过度嵌套Stack
Stack(
children: [
Container(),
Stack(
children: [
Container(),
Stack(
children: [
Container(),
],
),
],
),
],
)

// 正确示例:使用单个Stack
Stack(
children: [
Container(),
Positioned(child: Container()),
Positioned(child: Container()),
],
)

2. 使用 IndexedStack 替代多个 Stack

dart 复制代码
// 多个Stack切换
IndexedStack(
index: _currentIndex,// 当前显示的子组件索引
children: <Widget>[
Container(color: Colors.red),// index 0
Container(color: Colors.green),// index 1
Container(color: Colors.blue),// index 2
],
)

// 对比多个Stack的方案
Stack(
children: <Widget>[
// 需要管理多个Visibility
Visibility(
visible: _currentIndex == 0,
child: Container(color: Colors.red),
),
Visibility(
visible: _currentIndex == 1,
child: Container(color: Colors.green),
),
Visibility(
visible: _currentIndex == 2,
child: Container(color: Colors.blue),
),
],
)

3. 使用 ConstrainedBox 控制尺寸

dart 复制代码
// 使用ConstrainedBox限制Stack尺寸
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 200,
maxHeight: 200,
),
child: Stack(
children: [
// 子组件
],
),
)

// 使用SizedBox固定Stack尺寸
SizedBox(
width: 200,
height: 200,
child: Stack(
children: [
// 子组件
],
),
)

4. 使用 OverflowBox 处理溢出

dart 复制代码
// OverflowBox允许子组件超出父容器
Container(
width: 100,
height: 100,
color: Colors.grey,
child: OverflowBox(
maxWidth: 150,// 允许最大宽度
maxHeight: 150, // 允许最大高度
child: Stack(
children: [
Positioned(
left: -25,
top: -25,
child: Container(
width: 150,
height: 150,
color: Colors.red,
),
),
],
),
),
)

常见问题解决方案

1. Stack 子组件不显示

dart 复制代码
// 问题:子组件没有设置尺寸或位置
Stack(
children: [
// 没有设置尺寸的Container不会显示
Container(color: Colors.red),
],
)

// 解决方案1:设置尺寸
Stack(
children: [
Container(
width: 100,
height: 100,
color: Colors.red,
),
],
)

// 解决方案2:使用Positioned
Stack(
children: [
Positioned.fill(
child: Container(color: Colors.red),
),
],
)

2. Stack 尺寸计算错误

dart 复制代码
// 问题:Stack没有明确的尺寸约束
Container(
color: Colors.grey,
child: Stack(
children: [
Container(color: Colors.red),
Container(color: Colors.green),
],
),
)

// 解决方案:提供明确的尺寸约束
Container(
width: 200,
height: 200,
color: Colors.grey,
child: Stack(
children: [
Container(color: Colors.red),
Container(color: Colors.green),
],
),
)

3. Positioned 定位不正确

dart 复制代码
// 问题:Positioned没有设置定位属性
Stack(
children: [
Positioned(
child: Container(// 缺少定位属性
width: 100,
height: 100,
color: Colors.red,
),
),
],
)

// 解决方案:设置至少一个定位属性
Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
],
)

最佳实践

1. 封装可复用的 Stack 组件

dart 复制代码
class OverlayImage extends StatelessWidget {
final String imageUrl;
final String title;
final String badgeText;

OverlayImage({
required this.imageUrl,
required this.title,
required this.badgeText,
});

@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 150,
child: Stack(
children: [
// 背景图片
Image.network(
imageUrl,
width: 200,
height: 150,
fit: BoxFit.cover,
),

// 底部渐变遮罩
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.8),
Colors.transparent,
],
),
),
),
),

// 标题
Positioned(
bottom: 10,
left: 10,
child: Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: 16,
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(
badgeText,
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}

// 使用示例
OverlayImage(
imageUrl: 'https://example.com/image.jpg',
title: '图片标题',
badgeText: 'NEW',
)

2. 响应式 Stack 布局

dart 复制代码
class ResponsiveStack extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;

return Container(
width: screenWidth,
height: 200,
child: Stack(
children: [
// 背景
Container(
color: Colors.blue,
),

// 内容
Positioned(
left: screenWidth > 600 ? 50 : 20,
top: 50,
child: Container(
width: screenWidth > 600 ? 400 : 200,
child: Text(
'响应式标题',
style: TextStyle(
color: Colors.white,
fontSize: screenWidth > 600 ? 24 : 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}

总结

Stack 组件是 Flutter 中非常强大的布局工具:

优点:

  • 灵活的子组件定位能力
  • 支持多层叠加效果
  • 可以实现各种复杂UI效果
  • 性能较好,适合简单叠加场景

适用场景:

  • 图片叠加文字
  • 浮动按钮
  • 徽章、标签
  • 加载遮罩
  • 卡片堆叠效果

注意事项:

  • 需要明确指定 Stack 的尺寸或使用约束
  • Positioned 子组件需要设置定位属性
  • 过度使用 Stack 会影响性能
  • 注意子组件的层级顺序

建议:

  • 优先使用 Row、Column 等线性布局
  • 只在需要叠加效果时使用 Stack
  • 使用 IndexedStack 管理多个页面
  • 封装常用叠加效果为可复用组件
相关推荐
程序员Ctrl喵2 小时前
分层架构的协同艺术——解构 Flutter 的心脏
flutter·架构
Hello.Reader2 小时前
Flutter IM 桌面端消息发送、ACK 回执、SQLite 本地缓存与断线重连设计
flutter·缓存·sqlite
Hello.Reader2 小时前
Flutter IM 桌面端项目架构、聊天窗口布局与 WebSocket 长连接设计
websocket·flutter·架构
梦里1米82 小时前
大模型的使用和Prompt-Tuning学习笔记
笔记·学习·prompt
前端不太难2 小时前
Flutter Web / Desktop 为什么“能跑但不好用”?
前端·flutter·状态模式
前端不太难2 小时前
Flutter 国际化和主题系统如何避免后期大改?
flutter·状态模式
小雨凉如水2 小时前
flutter 基础组件学习
学习·flutter
云边散步2 小时前
godot2D游戏教程系列二(11)
笔记·学习·游戏·游戏开发
试试勇气2 小时前
Linux学习笔记(十六)--进程信号
linux·笔记·学习