关于 compose-screens-footguns
面向 Kotlin 和 Compose Multiplatform 开发者的参考技能,聚焦布局、导航和交互陷阱。条目按症状索引,说明常见屏幕实现问题的可行模式与验证步骤。
主要功能
- 0132 篇主题明确的参考文件
- 02布局与导航实现模式
- 03交互问题及其原因说明
- 04针对项目的验证命令
- 05按症状索引并提供交叉引用
适用场景
- 01适配调整尺寸后的窗口布局
- 02排查导航标签缺失或状态不一致
- 03检查滚动布局中的浮层和系统边距
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ maxrave-dev/ kotlin-footguns --skill compose-screens-footguns安装后在 Claude Code 中输入/compose-screens-footguns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/collapsing-parallax-toolbar.mdcommit-a-text-field-on-an-explicit-action.mdcontrol-range-must-cover-stored-values.mdcustom-modal-sheet-family.mdcustom-thin-media-slider.mddesktop-mini-player-window.mdedit-a-shape-as-a-shape.mdempty-state-must-keep-its-navigation.mdevent-not-state-edge-for-one-shot-effects.mdexpandable-and-linkified-text.mdfloating-overlay-reserves-its-own-strip.mdfullscreen-video-gesture-overlay.mdhidden-setting-does-not-clear-the-stored-value.mdlayered-video-backdrop-pager.mdlazy-item-grouping-beats-arrangement-gap.mdlazy-list-drag-reorder.mdlazy-scroll-helper-kit.mdnav-tab-registration-drift.mdnowplaying-pager-no-feedback-loop.mdprogress-indicator-as-scrubber.mdresponsive-gate-size-not-platform.mdscoped-composable-shadows-the-top-level-one.mdscreen-shell-content-split.mdselection-mode-state-holder.mdself-hiding-child-cannot-hide-its-slot.mdsettings-value-round-tripped-through-its-label.mdstacked-bars-double-consume-window-insets.mdstory-reel-auto-pager.mdswipe-action-list-row.mdtype-safe-nav-graph-organization.md详细介绍
Compose 屏幕陷阱:布局、导航与交互
从生产环境中的 Kotlin 和 Compose Multiplatform 应用中挖掘出的 32 个陷阱,位于 references/ 下,每个文件都很简短。每个文件都介绍可行模式、陷阱(实际会导致故障的具体情况及其原因),以及包含可在用户自己的代码库中运行的命令的验证方法部分。
如何使用此索引。 将即将编写的代码或正在排查的症状与下方条目对应起来:每个条目都标明了主题,以及出现哪种症状时应查看该条目。使用 Read 工具阅读所有可能适用的文件,然后再提出代码或修复方案;每个文件都少于 150 行。如果验证命令中使用了源项目的路径,请替换为用户代码库中的对应路径。
交叉引用。 文件中出现但未列在此处的反引号括起的陷阱名称,位于相邻领域的技能中:使用 ../*/references/<name>.md glob 查找并打开该文件。
屏幕、导航与自适应布局
responsive-gate-size-not-platform— 根据窗口自身的宽高比判断是否采用自适应布局,而不是依据平台;并一次性切换该判断所控制的所有几何值——框架尺寸、内容缩放和遮罩高度。适用于布局在手机上正常、在窄桌面窗口或调整大小后的桌面窗口中却不正常;将自适应标志强行设为某个值会导致其他平台的屏幕出问题;或宽窗口中的插图占满整个页面的情况。nav-tab-registration-drift— 顶层标签页必须在所有各自维护标签页列表的导航界面中注册——底部栏、侧边栏,以及维护两份列表的样式化栏——还要注册到导航图和控制其显示的标志中,否则标签页虽然存在于代码里,却永远不会渲染。同样的遗漏也会影响由多个顶部应用栏承载的状态入口:缺失的徽标会让人以为“没有任何内容正在运行”,而不是发现这是个 bug。本条目涵盖:为什么标签页的序号代表身份而非位置;当标签页在用户操作时消失,条件标签页需要什么处理;以及徽标圆点为什么需要使用页面颜色的描边环。适用于新添加的标签页或徽标只在某个界面中显示、重新排序后选中状态高亮了错误标签页,或多出一个标签页后标签栏溢出的情况。type-safe-nav-graph-organization— 组织类型安全的 Navigation-Compose 导航图,使目标列表增长后仍然易于阅读:每个文件放一个可序列化路由对象,并按区域分组;区域导航图作为图构建器的扩展函数;宿主中只设置一组转场;每个路由的主题则通过将屏幕包装在其自身条目中来应用。适用于导航文件已增长到数百行、路由参数开始需要自定义类型,或不同目标之间的转场或主题无故不一致的情况。collapsing-parallax-toolbar— 在同一个盒子中用五个同级元素构建可折叠标题栏:其中四个共用一个滚动状态,另一个由布尔值驱动;插图通过图形图层以一半的滚动速度移动;标题沿两段曲线插值进入固定栏;并使用派生的翻转点将悬浮返回按钮替换为真正的顶部栏。适用于视差标题栏滚动时抖动或重新测量、折叠标题偏离预期路径,或调整窗口大小后固定栏出现在错误滚动偏移位置的情况。fullscreen-video-gesture-overlay— 全屏视频屏幕的手势层和控件层:点击任意位置切换控件,双击任一半屏幕进行跳转;每次交互都会重置自动隐藏计时器;画中画(PiP)保护逻辑会移除这两个图层。适用于单击只在半个屏幕上有效、用户拖动进度条时控件消失、进度滑块在手指下方跳回、双击没有涟漪效果或涟漪效果一直不消失,或应用控件仍绘制在小型悬浮窗口中的视频上方的情况。nowplaying-pager-no-feedback-loop— 构建既跟随播放器又能驱动播放器的正在播放插图分页器,同时避免两者互相写入形成循环:滚动进行中标志同时覆盖程序化动画和拖动;仅在页面稳定后分派跳转;页面差异判断则是在 UI 运行时之外的纯函数。适用于滑动后又弹回原页面、一次滑动跳过两首曲目、快速滑动后分页器不再跟随播放器、向后滑动时重启当前曲目而非返回上一首,或随机播放时远距离滑动落在错误歌曲上的情况。layered-video-backdrop-pager— 构建可滑动的正在播放页面,其中各图层——彩色背景、全幅循环视频或图片,以及居中方形封面——都属于同一个分页器;每页颜色取自该页实际绘制的位图,遮罩则覆盖整个页面。适用于滑动时背景与封面错位滑出、一首曲目的视频在滑动中途渗入相邻页面、背景颜色属于错误曲目或在页面间闪黑、视频底部透过控件显示,或点击覆盖层完全阻止分页器拖动的情况。desktop-mini-player-window— 创建第二个始终置顶、无边框的桌面窗口来显示播放控件:在组合之外用一个布尔值维护其是否存在;与主窗口共用同一个状态对象;手动实现拖动,并锚定到指针的绝对坐标;使用以设备像素为单位的原生最小尺寸;并持久化窗口几何信息,同时避免频繁写入存储。适用于小窗口拖动时漂移或抖动、无法调整大小越过某个角、缩小后内容被截断、关闭主窗口后小窗口立即消失、小窗口打开后不可见,或小窗口状态与主窗口不一致的情况。screen-shell-content-split— 将一个屏幕拆分为负责所有跨样式关注点的外壳,以及只负责渲染的内容层,并通过两个持有器连接两者——状态快照和操作集合——这样添加第二种样式只需增加一个分支,而不用修改参数列表;涵盖持有器为何稳定但不可变、哪些内容必须留在外壳中,以及哪些内容不能留在外壳中。适用于屏幕新增第二种视觉样式、切换样式会重置滚动位置或插图页面、添加一种样式就意味着要编辑包含 50 个参数的函数签名,或“共享”辅助函数开始需要按样式添加if判断的情况。variant-layout-math-stays-in-the-variant— 当同一屏幕的两种样式都需要精确适配单屏的测量方式——测量固定区块、将剩余空间等分,并设置最小值——时,每种样式都应保留自己的实现,而不是将其提取为通用实现;涵盖区块需要的 effect key、必须与实际绘制比例保持一致的不可见间隔,以及哪些间隔可以动画。适用于主视觉元素上方或下方的间距沿用上一首曲目的尺寸、内容本应在首屏末尾结束却发生溢出或留下空白区域,或准备将两个屏幕中“相同”的布局计算提取到一个辅助函数中的情况。empty-state-must-keep-its-navigation— 用空状态消息替换已填充内容的标题时,不要删除该标题所包含的控件——只在原本拥有这些控件的分支中重新提供;将加载分支置于空状态分支之前;并停止为一行文本预留插图高度。适用于用户进入空闲时段、筛选结果为空或搜索结果为空后无法返回,空状态消息在每次重新加载时闪现,或同一控件被渲染两次的情况。stacked-bars-double-consume-window-insets— 内边距消耗会传递到可组合项的后代,而不会传递给同级项,因此同一列中堆叠的两个支持内边距的栏都会为系统栏预留空间,产生一条恰好与一个栏等高的空白带。本条目涵盖:如何使用框架默认值为栏的windowInsets参数化、如何一次性确定由谁消耗内边距,以及为什么同一个组件在覆盖层调用位置仍须保留默认值。适用于两个栏之间出现空白区域、该区域只在屏幕的一种模式下出现,或将内边距归零后栏的前置图标在横屏时被裁切的情况。floating-overlay-reserves-its-own-strip— 悬浮在滚动列上方的控件是一个不参与测量的同级项,因此该列必须明确为其覆盖的区域预留空间——可以使用前置间隔项,也可以使用标题行,并用两个对称间隔项确保居中标题在空缺区域上方保持居中。本条目涵盖:区域常量可以包含哪些内容、不能包含哪些内容;为什么每个滚动分支都需要单独处理;以及为什么只有采用悬浮方式,页面滚动后控件才能保持可访问。适用于悬浮返回按钮遮住第一行内容、居中标题偏移半个按钮宽度,或长页面中控件滚动到无法触及的位置的情况。
Components and interaction
lazy-list-drag-reorder— 为惰性组合的 Compose 列表提供完整的拖动重排状态持有器:累积指针偏移量、基于可见窗口计算目标索引、避免抬起动画与内置项目放置动画重叠、由调用方驱动的边缘自动滚动增量,以及与数据层约定的放下时提交行为。适用于构建重排功能,或排查拖动行弹回原位置、列表在其下方重新布局时发生抖动、提交了用户取消的移动,或滚动列表而非移动行的问题。swipe-action-list-row— 在同一列表行上同时处理三种手势:点击、长按选择和横向滑动执行操作,另有一个用于改变点击含义的模式标记。涵盖决定滑动检测器看到当前模式还是组合时捕获的模式的pointerInput键、在布局阶段平移行、锁定提交阈值,以及将相反方向的拖动交给父级处理。适用于排查多选开始后行仍可滑动、只有行碰巧重新组合后选择才开始生效,或滑动与下方的分页器或列表冲突的问题。selection-mode-state-holder— 用于 Compose 列表多选的小型@Stable状态持有器:按稳定 ID 而非列表位置标识项目;所有修改都汇集到同一个私有方法,确保无法绕过选择数量上限;并提供区分两种手势含义的切换、清除和全选语义。适用于为列表构建批量操作,或排查重排或加载页面后选择错位、选择数量上限对点击行有效但对全选无效的问题。expandable-and-linkified-text— 根据测量到的溢出而非字符数,将长文本截断为几行并显示展开/收起控件,同时让同一文本中的时间戳或 URL 可点击。适用于排查文本明明显示得下却出现“更多”、文本显示不下却不出现“更多”、“更多”按钮也被截断,或点击链接时展开文本块而不是打开链接的问题。lazy-scroll-helper-kit— 四个值得在不同应用间复用的惰性列表小工具:作为派生状态的滚动方向、先等待一帧再测量的滚动到项目中心、项目可见百分比,以及在可见窗口中查找项目,并说明每个工具隐藏的陷阱。适用于排查滚动时隐藏的栏闪烁或卡住、滚动到某个项目后它停在边缘或毫无反应,或视口计算返回错误项目的数值。custom-thin-media-slider— 基于 Material3 的Slider自定义轨道和滑块插槽,构建带缓冲进度轨道的纤细进度条;包括确保滑块不会钉在末端的“使用比例值而非自定义刻度”规则,以及防止传入的播放位置与拖动冲突的状态门控。适用于排查进度条无论位置如何都显示为满格或空格、拖动时滑块弹回、纤细控件无法再变细,或轨道上出现多余圆点和刻度的问题。custom-modal-sheet-family— 在 Material3 的ModalBottomSheet上实现统一的样式:透明容器加自定义表面、清零窗口内边距并显式添加末尾间隔、自定义拖动手柄,以及先隐藏再关闭,让工作表在离开组合前完成关闭动画。适用于排查工作表突然关闭而非滑动关闭、最后一行被导航栏遮挡、其中的文字不可见,或一组工作表的样式逐渐变得各不相同的问题。control-range-must-cover-stored-values— 根据控件实际会接收的值的真实分布(包括已存储、导入和迁移的值)设置滑块范围,而不是沿用相邻控件的范围;因为超出范围的值会让滑块停在轨道末端,而读数显示另一个数字,用户第一次触摸还会悄悄改写该值。适用于为可能来自滑块之外任意来源的值添加滑块。edit-a-shape-as-a-shape— 当要编辑的值是一条曲线时,应绘制可拖动曲线,而不是使用 N 个滑块;并将其嵌入设置列表,而非跳转到另一个屏幕。使用原始指针循环而非拖动手势辅助工具,让草稿在每次手势结束时只提交一次,并进行平滑处理且绝不超出用户放置的控制点。适用于构建多点编辑器,或排查曲线控件忽略点击、松开时弹回,或清除已保存值的问题。progress-indicator-as-scrubber— 使用进度指示器加透明指针层构建进度控制,而非使用滑块:命中区域比视觉元素更高、消费拖动以防祖先分页器抢走进度拖动、交互时显示拖动自身的比例值、自定义绘制滑块,以及单独决定是否绘制更靠近的元素而不移动其布局位置。适用于所需视觉效果没有对应滑块、进度拖动被滑动手势或工作表劫持、拖动中进度条跳回,或纤细控件难以点击的问题。scoped-composable-shadows-the-top-level-one— 在布局作用域内,同名的作用域扩展组合函数会优先于顶层组合函数,且不会给出提示,因为两者都能编译;因此,为普通版本编写的调用会静默使用作用域版本的默认值和布局行为。涵盖如何使用完全限定名强制调用顶层版本、用this@Scope.强制调用作用域版本,以及为什么嵌套布局内部仍会应用外层作用域。适用于排查出现/消失动画导致父级展开或收起而非原地淡入、组合函数移入列或行后行为改变,或调用解析到了并非预期的重载的问题。settings-value-round-tripped-through-its-label— 选择对话框如果将选中的本地化标签映射回存储值,那么一旦两个标签翻译成相同文字,就会出错:写入会被跳过或落到错误选项,且不会报错。涵盖改为传递 ID 而非文本、让映射失败显式报错,以及默认值同时在存储中和收集器初始值中声明这一相关隐患。适用于排查某个设置选项只在一种语言下无法保存、选择器写入相邻选项,或屏幕进入时短暂显示错误变体的问题。self-hiding-child-cannot-hide-its-slot— 功能不可用时不渲染任何内容的组件,无法移除其他代码包裹它的容器;应使用相同的可用性谓词控制插槽,在组件旁公开该谓词,并正确区分插槽的可点击和不可点击分支。适用于排查可选控件本应出现的位置有空单元格、空隙或多余分隔线、组的圆角端部落在错误项目上,或包装层吞掉了本应传给内部控件的点击事件的问题。event-not-state-edge-for-one-shot-effects— 应从导致一次性庆祝效果的点击触发该效果,而不是从点击产生的状态触发:监听布尔值从 false→true 边沿的效果无法区分用户点击与稍后到达的数据,因此切换到已标记的记录也会触发同一边沿。涵盖负责实时效果的@Stable状态持有器、在点击时判断事件含义、为何上一次值变量会成为第二个事实来源,以及如何让并发效果累加。适用于排查在记录间切换时效果自行播放、庆祝了用户未执行的操作,或返回屏幕后首次真实点击被悄悄吞掉的问题。weight-fill-false-to-center-a-cluster— 带权重的子项即使内容测量宽度较窄,也会占满整个位置,从而把相邻子项推到远端;weight(1f, fill = false)会将未使用的宽度还给行布局,让子项、间距和相邻项作为一个整体排列。涵盖为什么问题要看相邻项而非带权重的子项、为何需要两处修改而非一处,以及为何直接移除权重会产生不同布局。适用于排查栏在内容尺寸不同的情况下有时居中、有时歪斜、按钮紧贴屏幕边缘且旁边留有空洞,或末尾标签离其所属文本很远的问题。lazy-item-grouping-beats-arrangement-gap— 惰性列表的spacedBy排列会应用于每一对项目之间,并与每个项目自身的边缘内边距叠加。因此,必须作为一个整体显示的内容块应放在同一个项目中,并使用更紧凑的内部间距,而非拆成三个项目并依赖列表间距。涵盖为什么可见间距无法用单个常量解释、项目边界为何也是间距边界,以及组为条件显示时该如何处理。适用于排查某个内容块上方出现大片空白、缩小一组项目的间距却影响所有其他项目,或准备将标题拆成多个惰性项目的问题。commit-a-text-field-on-an-explicit-action— 在失去焦点时写入可编辑值,会导致对话框、屏幕旋转或意外点击移走焦点时,把尚未输入完整的内容保存下来;应将草稿保存在以存储值为键的独立状态中,并且只在用户明确要求时写入。涵盖提交时会规范化值,导致用于显示确认按钮的脏状态检查卡住的原因、草稿应存放的位置,以及其他提交路径需要满足哪些一致性要求。适用于排查设置保存了用户未输入的截断值、保存后“保存”按钮仍不消失,或外部变更的值未同步到输入框的问题。story-reel-auto-pager— 构建故事式轮播:分页器按每张卡片的计时器自动前进,带分段进度条、用于前后跳转的点击区域和按住暂停功能;所有卡片数据会在首帧之前一次性计算,而非逐张计算;分段条的数量则来自某些年份无法填满的卡片列表。涵盖无法通过长按累积进度的帧增量计时器、为何空的onLongPress回调能让长按后松开时恢复播放而非跳转、点击处理器中读取分页器的目标页面而非当前页面,以及固定捕获卡片的配色方案以确保其渲染效果不受查看者自身主题影响。适用于排查轮播进度条跳到错误分段、松开暂停状态后立即跳过卡片而非继续播放、故事中途出现加载指示器而非仅在第一张卡片前出现,或共享卡片因设备主题不同而显示效果不一致的问题。hidden-setting-does-not-clear-the-stored-value— 某种样式或效果选项因 OS 版本或能力限制而在设置选择器中正确隐藏,但它对应的效果仍然显示异常——扁平、没有模糊效果或完全错误——即使设备本不应允许用户选择该选项也是如此;因为选择器只限制用户接下来能选择什么,不会清除已存储的偏好值。适用于排查某个受版本限制的视觉功能虽然在一处做了能力检查,问题却仍可复现;或为某个修饰符添加expect/actual布尔值后,检查失败却没有抛出异常的问题。
评论
支持 Markdown