Files
2026-09-04 15:22:20 +08:00

4.2 KiB
Raw Permalink Blame History

【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 块内容:

  1. 基础重叠:蓝色盒子 + 右上角悬空角标 + 居中文字;
  2. Positioned.fill 半透明蒙层;
  3. 坑①复现:全 Positioned 时 Stack 尺寸异常;
  4. 修复方案 1:SizedBox 固定宽高;
  5. 修复方案 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 不担心尺寸突变。


五、踩坑记录

  1. Positioned 只能直接作为 Stack 的 children,中间套了其他 Widget 会失效(编译警告)。
  2. 做悬浮角标务必 clipBehavior: Clip.none,否则负偏移部分被裁掉看不见。
  3. 全 Positioned 的 Stack 在无界约束下崩溃(见上),这是本页作者真实遇到过的 bug:页面只剩标题。
  4. Container 在 Flutter 3.44 已不是 const 构造,外层 Center 别写 const(编译报错)。
  5. 想让内容很短也能拉动滚动(演示页效果),用 physics: BouncingScrollPhysics() / AlwaysScrollableScrollPhysics()。

六、扩展思考

  • 蒙层进阶:Positioned.fill + 半透明色 + 点击手势 = 简易弹窗/引导层;
  • 角标进阶:红点角标(Badge 组件)本质也是 Stack + Positioned 的封装;
  • 与 list_demo.md 对照:列表右下角的"回到顶部"FAB 就是 Stack + Positioned 实现的;
  • 与 smooth_sheet_demo.md 对照:地图背景 + 底部面板就是"Stack 背景层 + 面板层"的组合。