4.2 KiB
4.2 KiB
【Demo】Stack + Positioned 层叠布局
源文件:
lib/pages/demo/stack_demo.dart| 分类:基础布局 一句话:UI 重叠 = CSS 的position: absolute,本页附赠一个真实踩过的"全 Positioned 尺寸崩溃"大坑。
一、这个 Demo 解决什么问题
做"头像 + 右上角角标"、"卡片 + 半透明蒙层"、"悬浮按钮"这类元素重叠的 UI。
Flutter 用 Stack 实现:
Stack= 图层容器(后写的子组件盖在上面);Positioned= 给子组件做绝对定位(只能直接放在 Stack 的 children 里)。
二、怎么运行 / 演示内容
runApp(const MaterialApp(home: StackDemoPage()));
页面演示 5 块内容:
- 基础重叠:蓝色盒子 + 右上角悬空角标 + 居中文字;
Positioned.fill半透明蒙层;- 坑①复现:全 Positioned 时 Stack 尺寸异常;
- 修复方案 1:SizedBox 固定宽高;
- 修复方案 2:非 Positioned 底层组件撑尺寸。
三、核心知识点(新手向)
1. Stack 的两个核心参数
Stack(
alignment: Alignment.topLeft, // 对【没被 Positioned 包裹】的子组件做默认对齐
clipBehavior: Clip.none, // 允许子组件绘制超出 Stack 边界(默认 Clip.hardEdge 会裁剪)
children: [...],
)
2. Positioned 的用法
Positioned(
top: 10, // 距 Stack 上边
right: -10, // 负值 = 超出边界(配合 clipBehavior: Clip.none 才能显示)
child: ...,
)
Positioned.fill( // 填满整个 Stack —— 做蒙层最方便
child: Container(color: Colors.black54),
)
3. 布局规则一句话
Stack 的尺寸 = 非 Positioned 子组件决定的(取其中最大的那个); Positioned 组件只是"贴"在上面,不参与 Stack 自身尺寸计算。
四、关键代码剖析(含大坑)
坑①:全部子组件都是 Positioned → Stack 尺寸异常
// 真实行为:Stack 尺寸不由内容决定,直接取父约束最大值(constraints.biggest)
Stack(
children: [
Positioned(top: 10, left: 10, child: Text("A")),
],
)
- 放在有界约束里(如 SizedBox 300×60)→ Stack 占满整个 300×60,而不是收缩到文字大小;
- 裸放在 Column 等无界高度里 → debug 模式直接断言崩溃:
A Stack requires bounded constraints,整个 body 都画不出来(只看到标题栏)。
修复方案 1:给 Stack 套 SizedBox 固定宽高
SizedBox(
width: 300.w,
height: 80.h,
child: Stack(children: [Positioned(top: 10, left: 10, child: Text("A"))]),
)
修复方案 2:放一个非 Positioned 的底层组件撑起大小
Stack(
children: [
ColoredBox( // 非 Positioned 底层组件:由它决定 Stack 尺寸
color: Color(0xFFE8E8E8),
child: SizedBox(width: 300.w, height: 80.h),
),
Positioned(top: 10, left: 10, child: Text("A")),
],
)
经验:Stack 里永远留一个"地基"(非 Positioned 的底层组件或外层 SizedBox), 既避免崩溃,也方便后续加 Positioned 不担心尺寸突变。
五、踩坑记录
- Positioned 只能直接作为 Stack 的 children,中间套了其他 Widget 会失效(编译警告)。
- 做悬浮角标务必
clipBehavior: Clip.none,否则负偏移部分被裁掉看不见。 - 全 Positioned 的 Stack 在无界约束下崩溃(见上),这是本页作者真实遇到过的 bug:页面只剩标题。
Container在 Flutter 3.44 已不是 const 构造,外层Center别写const(编译报错)。- 想让内容很短也能拉动滚动(演示页效果),用
physics: BouncingScrollPhysics()/AlwaysScrollableScrollPhysics()。
六、扩展思考
- 蒙层进阶:
Positioned.fill+ 半透明色 + 点击手势 = 简易弹窗/引导层; - 角标进阶:红点角标(Badge 组件)本质也是 Stack + Positioned 的封装;
- 与 list_demo.md 对照:列表右下角的"回到顶部"FAB 就是
Stack + Positioned实现的; - 与 smooth_sheet_demo.md 对照:地图背景 + 底部面板就是"Stack 背景层 + 面板层"的组合。