用途
该技能用于处理 Jetpack Compose 中一个常见但容易被忽视的布局问题:浮动在滚动列之上的控件(如返回按钮、玻璃图标按钮)在布局树中是滚动内容的同级节点,而不是叠在其上的父节点。因此它不参与任何测量、排列或滚动范围计算,滚动内容必须显式预留出被它覆盖的那一条条带(strip)。
主要能力
技能围绕“浮动控件不预留空间”这一事实,给出可复用的布局模式与排查清单:
- 用
BoxWithConstraints同时容纳滚动Column与浮动控件,控件通过align(Alignment.TopStart)对齐到角落。 - 在滚动列顶部放置
Spacer(Modifier.height(BACK_BUTTON_STRIP))作为预留条带。 - 说明
BACK_BUTTON_STRIP应为控件自身偏移量加尺寸(示例中为8.dp + 48.dp),并刻意不包含状态栏高度,因为滚动列已通过innerPadding、控件已通过windowInsetsPadding各自消费了 inset。 - 解释浮动也是保持控件可达性的原因:若放在滚动列内,它会随页面滚出屏幕;对于折射或模糊背景的控件,其背景源也必须是同级节点,否则会形成反馈循环。
使用方式
技能以代码示例加陷阱说明的形式呈现。核心结构如下:
BoxWithConstraints(Modifier.fillMaxSize()) {
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(innerPadding)) {
Spacer(Modifier.height(BACK_BUTTON_STRIP)) // 按钮覆盖的条带
TitleBlock()
/* … */
}
// 列的兄弟节点:对齐而非堆叠,不影响下方任何内容
GlassIconButton(
modifier = Modifier
.align(Alignment.TopStart)
.windowInsetsPadding(WindowInsets.statusBars)
.padding(start = 12.dp, top = 8.dp)
.size(48.dp),
onClick = onBack,
)
}
技能还提供一组 grep 命令用于定位候选屏幕、交错查看滚动器与预留、检查命名条带常量的算术、统计固定宽度 Spacer 是否成对出现,并建议通过临时给条带 Spacer 加背景色来验证预留位置,以及在长状态栏设备上检查 inset 是否被错误计入。
适用场景
- 页面中存在滚动内容,同时有角落对齐的浮动控件。
- 浮动控件为半透明、玻璃或模糊效果,需要独立背景源。
- 需要保证浮动控件在滚动过程中始终可达。
- 多布局分支(窄屏/宽屏、并排滚动器)下需要精确配对预留与控件。
限制与注意事项
技能明确列出多项陷阱:
- “对齐”意味着控件不影响任何布局,且不会有任何警告;首行内容会落在控件下方,看起来像 padding 缺陷。
- 不要在容器(Box 或 scaffold)上预留,因为控件也在同一容器内对齐,padding 会连带移动控件,碰撞依旧存在;预留应属于滚动内容。
- 条带常量是求和,系统 inset 通常不属于其中;两处都计入 inset 会产生恰好一个状态栏高度的空隙,且依赖设备。
- 每个滚动分支各自预留,且只预留控件覆盖的那一个分支;未被覆盖的分支不应预留,否则会出现空洞。
- 标题行可作为预留,但需要成对的宽度 Spacer;单个 Spacer 会使标题槽位偏移半个按钮宽度。
- 用
Box+align(Center)居中标题会以整个宽度居中,长标题会直接跑到控件下方。 - 内容从控件下方穿过是设计决策:控件小且半透明时正确,控件不透明且下方内容重要时应改用参与布局的固定栏。
- 控件不继承 scaffold padding,必须自带
windowInsetsPadding(WindowInsets.statusBars),否则在某些设备上会落到状态栏下。 - 预留滚走后,控件会落在任意内容上,因此需要自带背景(玻璃、遮罩、填充形状)以保持可读性。
- 声明顺序与遍历顺序是两回事,需转储语义树或运行无障碍扫描器确认返回控件的朗读顺序。
验证
技能建议滚动到底部再返回,确认控件不移动、静止时没有内容被永久遮挡;给条带 Spacer 临时加背景色检查其是否恰好位于控件下方;在每个布局分支和长状态栏设备上验证;对居中标题形状加载最长标题,检查是否出现单侧省略号。
相关主题包括:stacked-bars-double-consume-window-insets(条带不应重复计入的 inset)、liquid-glass-backdrop(折射控件为何必须是背景源的兄弟节点)、shell-background-is-not-scheme-background(背景源实际绘制的内容)、empty-state-must-keep-its-navigation(控件需在无内容页面中存活)。