mcpField
C
compose-visuals-footguns

compose-visuals-footguns

github.com/maxrave-dev设计与 UI1.7k90 次安装1 周前更新
代码仓库

关于 compose-visuals-footguns

面向使用 Kotlin 和 Compose Multiplatform 的开发者,提供主题、视觉效果、动画和图表方面的参考。索引整理了来自生产应用的常见问题模式、可用做法及验证步骤。

主要功能

  1. 01按主题索引的 Compose 参考
  2. 02实际失败模式说明
  3. 03视觉代码的可用模式
  4. 04针对代码库的验证命令

适用场景

  1. 01排查渐变接缝或颜色问题
  2. 02检查主题或效果的实现
  3. 03在代码库中验证 Compose 修复

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill compose-visuals-footguns

安装后在 Claude Code 中输入/compose-visuals-footguns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ambient-tone-layer-behind-flat-pages.md
angled-gradient-modifier.md
animated-gradient-border-ring.md
artwork-palette-theming.md
artwork-seeded-dynamic-scheme.md
chrome-drawn-outside-the-theme-scope.md
compose-capture-to-share-image.md
crossfade-container-sizes-to-the-visible-child.md
delta-absent-not-infinite.md
deterministic-title-placeholder-painter.md
dont-pre-animate-a-self-animating-property.md
dont-slice-one-circle-between-unrelated-measures.md
draw-outside-bounds-particle-modifier.md
equal-buckets-or-no-buckets.md
fit-icon-glyphs-to-the-live-area.md
flat-twin-shares-geometry-not-material.md
force-dark-immersive-subtree.md
gate-optional-effect-at-the-shared-primitive.md
hoist-the-flag-not-the-animation.md
image-fallback-url-retry-in-composition.md
liquid-glass-backdrop.md
material-symbols-icon-system.md
mosaic-arrangements-must-be-hole-free.md
overflow-tilted-browse-card.md
palette-state-parks-on-loading.md
partial-chart-must-say-so.md
self-normalised-axes-need-a-second-shape.md
semantic-color-tokens-compositionlocal.md
shell-background-is-not-scheme-background.md
shimmer-skeleton-loaders.md

详细介绍

Compose 视觉开发陷阱:主题、效果与图表

从一个生产环境中的 Kotlin 和 Compose Multiplatform 应用中整理出的 36 个陷阱,每个文件都简短,并位于 references/ 下。每个文件都介绍可行模式、陷阱(实际使用中具体的失效方式及原因),以及可通过命令在用户自己的代码树中运行的验证方法部分。

如何使用此索引。 将准备编写的代码或正在排查的症状与下方条目对应起来:每个条目都标明主题,以及应引导你查阅该条目的症状。提出代码或修复方案前,使用 Read 工具阅读所有可能适用的文件;每个文件都不超过 150 行。如果验证命令中包含源项目的路径,请替换为用户代码树中的对应路径。

交叉引用。 文件中以反引号括起、但未列在此处的陷阱名称,位于相邻的领域技能中:使用 glob ../*/references/<name>.md 打开。

主题、颜色、渐变与遮罩层

  • smooth-scrim-gradient — 构建一种渐变遮罩,让作品自然融入页面背景,不留明显接缝:使用 smoothstep 缓动,让渐变两端都平缓;在 Kotlin 中插值颜色停靠点,而不是交给渲染器;并让透明停靠点沿用自定义 RGB,而非 Color.Transparent。适用于渐变叠层的起点或终点出现明显硬线、中间的淡化区域变成浑浊灰色或比两端更暗,或在一个平台上看起来正常的淡化效果在另一个平台上变成条纹的情况。
  • angled-gradient-modifier — 在 Compose 方框上以任意角度绘制线性渐变,让两个端点恰好落在方框边缘:介绍根据请求角度计算各象限端点的公式、旋转对角线为何会超出边界、钳制到最近边缘为何会扭曲角度,以及会让渐变消失的退化情况。适用于倾斜渐变在角落附近看起来褪色或被截断、可见角度与设置的角度不一致,或同一渐变在宽方框和高方框上效果不同的情况。
  • artwork-palette-theming — 根据作品驱动屏幕配色:介绍提取出的哪种色样适合用作强调色,哪种适合用作大面积页面背景;如何根据亮度自适应地加深颜色,让叠加文字在任何图像上都清晰可读;所需的十六进制解析辅助函数;以及提取结果为空或完全没有作品时的处理方式。适用于页面背景过于刺眼或亮到无法阅读、滚动列表时闪烁或变化,或屏幕渲染成透明或不可见而非带有色调的情况。
  • force-dark-immersive-subtree — 让 Compose 应用的某个部分(绘制在深色作品上的屏幕)始终以深色渲染,同时应用其余部分遵循用户的浅色主题:为该子树提供完整的深色配色方案以及自定义 token locals;说明仅设置文字颜色标记为何不够,并确认底部工作表和对话框会跨窗口边界继承这些 locals,而新建根组合则不会。适用于图片背景屏幕上的图标或按钮在浅色主题下变灰、难以辨认;从此类屏幕打开的工作表颜色不对;或强制深色的屏幕仍会询问系统当前是否为夜间模式的情况。
  • semantic-color-tokens-compositionlocal — 用通过 staticCompositionLocalOf 提供的 @Immutable token 类保存没有 Material 角色的颜色,例如喜欢状态的粉色、活动行高亮、闪光效果色调,以及叠加在作品上的颜色;说明 static 适合主题值的字节码层面原因,并给出哪些内容应放入 token 类、哪些应放入配色方案的规则。适用于十六进制字面量散落在各个 composable 中、某个颜色需要在浅色和深色模式下不同但又不属于配色方案角色,或需要在 staticCompositionLocalOf 和 compositionLocalOf 之间做选择的情况。
  • deterministic-title-placeholder-painter — 为没有作品的条目生成专属封面:将标题哈希为稳定渐变,再与测量后的文字和徽标一起封装成自定义 Painter,直接交给图像加载器的 placeholder、error 和 fallback 插槽;涵盖哈希真正保持确定性的条件、如何在布局阶段之外测量文字,以及如何报告固有尺寸。适用于没有封面的行看起来都一样、生成颜色在不同运行或平台间变化、占位文字溢出图块,或图像模型为 null 时留下空白方块的情况。
  • overflow-tilted-browse-card — 构建一张封面倾斜且越过裁剪角的浏览图块:介绍让对角线成为切口而非贴上去的方块的修饰符顺序、决定相邻元素需预留多少空间的旋转后边界框扩张,以及非方形图块为何不能仅根据宽度确定装饰尺寸。适用于旋转图像出现切角、文字与倾斜作品重叠或作品加载后立即重新排版,或旋转子项盖住相邻项而非被父项裁剪的情况。
  • artwork-seeded-dynamic-scheme — 根据从一幅作品中提取的种子生成完整的色调配色方案,并仅将其应用于对应子树,让所有控件继承对比度成对的角色,而非手动挑选色样;涵盖通过与种子初始化时使用的哨兵值比较来回退、对种子而非配色方案进行补间,以及每个动画帧都重新推导的成本。适用于屏幕应根据其作品自动变色、着色表面上的前景文字无法辨认,或颜色变化出现明显闪烁的情况。
  • palette-state-parks-on-loading — 调色板生成器会在自身的挂起点之前将状态切换为 Loading,导致每次生成期间都没有颜色;如果生成中途被取消,状态会停留在 Loading,且没有任何机制重新启动它,因此直接读取调色板的任何表面都会绘制 null 回退值。涵盖为何副作用必须仅以 bitmap 为键(或完全不设键)、在挂起之后赋值的“已完成”标记为何不能证明操作已完成,以及为何必须单独保存最后解析出的颜色。适用于根据作品着色的屏幕有时正常、有时变黑;同一项目有时访问时着色正确、有时却不对;或派生的配色方案悄无声息地停留在回退值的情况。
  • gate-optional-effect-at-the-shared-primitive — 将用户对可选装饰性表面效果的开关设置放在负责绘制该效果的唯一共享原语中,并通过主题以默认开启的 CompositionLocal 发布,而不是让每个调用点都检查标记。涵盖关闭时为何必须保留形状和点击目标、只改变绘制效果;为何默认值应为 true 而非 false;以及选择一种材质的调用点与选择另一个 composable 的调用点有何区别。适用于设置开关只影响所列部分表面、受开关控制的组件在预览中渲染成空白方框,或关闭效果时布局也随之移动的情况。
  • shell-background-is-not-scheme-background — 应用外壳将内容面板绘制成非 colorScheme.background 的颜色时,任何渐变、遮罩和淡化效果若以 colorScheme.background 为终点,都会留下明显接缝:装饰本身没有问题,只是目标颜色已经不在屏幕上。涵盖如何解析一个页面背景值并将其传递给所有装饰、两种有效方案(以外壳颜色为目标,或自行绘制底色),以及如何使用 matchParentSize 根据装饰内容来确定装饰尺寸,而非采用仅适用于一种外形尺寸的固定值。适用于渐变在屏幕中途停止并出现明显边缘、淡化效果在手机上正常但在桌面窗口中异常,或添加窗口框架样式后未曾改动的屏幕也出现问题的情况。
  • flat-twin-shares-geometry-not-material — 用户设置在同一控件的两种渲染方式之间切换时(一种是开销较大的装饰样式,另一种是简洁版本),两者的所有几何参数都必须保持一致(项宽、条高、指示器尺寸、内边距和宽度预算计算规则),这样切换只改变材质,不改变布局;编译器无法配对分别声明在两个文件中的常量。还涵盖如何重新着色以插槽 lambda 形式传入的图标,只有 CompositionLocalProvider(LocalContentColor) 能影响这种图标。适用于切换视觉设置也会移动元素、简洁版本的指示器偏离所属项,或插槽 lambda 图标忽略所有传入色调的情况。
  • chrome-drawn-outside-the-theme-scope — 如果自定义窗口标题栏、启动画面或崩溃对话框作为应用主题的同级 composable,而非位于主题内部,它读取的就是 MaterialTheme 的框架默认配色方案——始终为浅色,不受用户选择影响——且不会报错。涵盖如何将存储模式到布尔值的判断提取为一个共享 composable 函数,确保窗口框架样式和主题的判断一致;如何将颜色作为参数传入,而不是重新应用主题;以及哪些颜色应有意不遵循主题。适用于标题栏或对话框在深色模式下仍保持浅色、窗口框架样式的颜色比设置晚一次启动才更新,或准备在主题调用上方添加任何 composable 的情况。
  • ambient-tone-layer-behind-flat-pages — 可复用的顶部光晕图层,让没有自带图像的页面也拥有与图片背景屏幕相同的着色底色:将其作为导航目的地内容的第一个同级元素输出,无需包裹层,因为各目的地本身已经会堆叠。涵盖为何 tone 为 null 时应让渐变收拢为页面颜色,而非替换成主题颜色;为何滚动离开偏移应放在绘制阶段处理;为何第一个列表项必须高于图层;以及哪些屏幕适合保留自己的副本。适用于设置页或列表页的纯色背景与应用其余部分格格不入、应用闲置时无故显示光晕,或列表滚动时光晕突然错位的情况。

特效、动画和图像

  • material-symbols-icon-system — 将每个图标作为生成的 ImageVector 扩展属性放在同一个接收对象上,图标来自图标字体自带的 Compose 生成器,因此字节码收缩器可以移除从未引用的图标。涵盖生成器请求及其轴参数、将生成文件改为扩展属性所需的编辑、状态图标的填充/未填充配对,以及哪些图标必须保留为 drawable 资源。在添加或替换图标、某组图标的字重和圆角样式不一致,或类型错误提示需要 Painter 却传入了 ImageVector 时使用。
  • fit-icon-glyphs-to-the-live-area — 构建图标时,将每个字形适配到图标网格的 20×20 有效区域,让同尺寸图标看起来大小一致——符号字体自身的字形会溢出 2dp 内边距;根据字形最远点画圆来判断大小,会让任何带有一笔突出的字形缩得过小;而小型圆形图标按钮会将内容裁切到自身形状。在同尺寸绘制的图标并排看起来大小不一、某个图标的边角在小型圆形按钮中被裁掉,或调试轮廓显示字形越出边界时使用。
  • liquid-glass-backdrop — 使用 backdrop 库在 Compose 中构建折射的“液态玻璃”表面,并避免最浪费时间的三种故障——默认具有方向性的边缘高光在小型圆形按钮上几乎不可见;嵌套在玻璃内部的 backdrop 来源会形成渲染反馈循环,导致着色器停止工作;空的来源会让控件在纯色页面上显示为灰色圆片。涵盖来源/表面的分离、为纯色页面提供值得折射的背景而非直接舍弃效果、只适用于强制深色屏幕的白色默认色调、效果堆栈、让按压手势仅用于观察,以及通过交换实验区分几何问题与放置问题。在玻璃表面渲染成扁平圆角框或灰色圆片、边缘高光在宽胶囊按钮上可见但在圆形按钮上不可见、浅色主题下图标消失,或绘制过程在着色器内部崩溃时使用。
  • shimmer-skeleton-loaders — 提供自测量的 shimmer 修饰符和在列表加载时替代列表的骨架屏可组合项——修饰符如何得知自身尺寸、扫过效果下方的底色为何至关重要、clip 与 background 之间的修饰符顺序为何会改变圆角效果,以及骨架屏的惰性列表为何必须关闭滚动。在构建加载占位内容,或 shimmer 渲染成扁平色块、圆角设计下仍显示方角,或两次扫过之间出现明显停顿时使用。
  • animated-gradient-border-ring — 在卡片、标签或按钮周围绘制旋转的扫掠渐变环,由裁切框、全尺寸渐变和停在中间的不透明内嵌表面组成——整个机制取决于修饰符顺序,且不涉及混合模式——并说明为何 SrcIn 与离屏图层的做法无法绘制出环,以及该图层的开销。在渐变覆盖整个表面而非边框、圆环不可见、渐变溢出到控件后方,或一行此类控件导致滚动开销过大时使用。
  • hoist-the-flag-not-the-animation — 共享驱动淡入淡出的布尔值,而不是运行淡入淡出的 tween——每种外观都应派生自己的曲线,因此一种外观可以采用非对称效果(快速进入、缓慢退出),而不改变另一种外观的感受;涵盖明亮背景上的对称线性淡入淡出为何会让带容器背景的控件呈现错误颜色,以及为何共享的动画值仍必须保持为公开状态。在按钮仅在淡入淡出时看起来比相邻按钮更亮或更暗、一种视觉样式需要与另一种不同的时长,或准备将 animateFloatAsState 提升到共享状态持有对象中时使用。
  • dont-pre-animate-a-self-animating-property — 将原始目标值传给会自行对某个属性执行动画的组件——外部 tween 动画值会不断产生新目标,而这类组件通常会在自身动画仍在运行时忽略新目标,导致效果卡在中途。涵盖如何识别自行执行动画的属性、为何在两个端点间直接切换才是修复方法,以及如何从编译产物证明所有权。在动画组件卡在接近起始值的位置、过渡只播放一次便不再播放,或准备在组件输入外层包装 animateFloatAsState 时使用。
  • draw-outside-bounds-particle-modifier — 使用普通绘制修饰符绘制超出宿主自身边界的庆祝粒子爆发效果——绘制修饰符默认不裁切,因此必须放在修饰符链中每个 .clip(...) 之前;任何裁切祖先仍会裁掉越过其边缘的部分。涵盖如何根据宿主测量尺寸缩放起点和范围,让同一效果在 28dp 和 48dp 下看起来一致、如何让每帧 lambda 保持轻量,以及为何纯绘制比动画库更适合此处。在粒子效果被裁切到按钮轮廓、在圆角卡片或列表行内消失,或准备为了绘制一个效果而添加矢量动画依赖时使用。
  • image-fallback-url-retry-in-composition — 高分辨率图片缺失时,将记录图片的 URL 保存在组合状态中,并在 onError 中替换一次,以重试来源保证存在的图片变体。涵盖如何让磁盘缓存键跟随变更后的 URL、如何针对每条记录重置状态、如何让第二次替换不执行任何操作以防循环,以及一个隐蔽的代价——依赖 onSuccess 执行的所有逻辑(调色板以及由其派生的页面主题)都会悄无声息地无法运行。在某些记录对应的页面始终显示回退颜色、重试的图片每次访问都会重新获取,或图片请求看起来不断重试时使用。
  • text-brush-shimmer-sweep — 直接在 TextStyle 上设置移动渐变,让流动高光扫过标签——字形由画笔绘制,因此无需叠加层、裁切,也无需同步测量宽度。涵盖为何必须无条件声明无限过渡,避免每次标签出现时扫光都重新开始;为何扫光前端必须是纯色高对比颜色,而不能使用标签本身的颜色;为何渐变色标的单位是像素且必须移动到两端之外;以及画笔会完全替换文本颜色。在闪烁标签每次重新出现时都会跳回起点、光泽在标签本身的灰色背景下不可见,或设置画笔后精心挑选的文本颜色消失时使用。
  • touch-indication-bounds-and-alpha — 仅通过 alpha 值减弱应用全局触摸波纹,并为其设置正确边界——颜色根据本地内容颜色派生,因此在强制配色方案的子树中仍然正确;而固定一种颜色会让波纹比近黑色表面更暗。涵盖为何主题配置会传递到裸的 Modifier.clickable、为何 .clip(shape) 必须放在 .clickable 之前、为何卡片和其裁切必须使用同一个形状值、为何绝不能叠加两个可点击修饰符,以及唯一能设置 alpha 的已弃用构造函数。在点击圆角项时闪出方形波纹、深色主题下波纹看起来像一团煤灰,或可长按的项目出现两次波纹时使用。
  • crossfade-container-sizes-to-the-visible-child — 交叉淡入淡出的容器是一个左上对齐的 Box,其尺寸由当前组合中的最大子项决定,因此从较矮子项切换到较高子项时,过渡中较矮的子项会贴在顶部,并在较高子项退出时消失。涵盖如何用显式对齐方式将两个分支放入同一个固定框架、如何替换而非叠加轨道长度不同的两个进度渲染器,以及为何淡化整个交互控件会让它无法响应触摸。在状态变化后工具栏明显滑入位置、两个叠加轨道长度不同,或控件在淡入淡出期间停止响应时使用。
  • slide-transition-defaults-to-half-a-height — 垂直滑动过渡默认使用元素高度的一半,因此元素看起来仿佛已经完成一半移动,动画开头缺失——呈现出来像跳变而非滑动。涵盖如何传入完整高度、用于决定元素从哪一边进入的符号、如何搭配较短的淡入淡出和较长的滑动,让元素在稳定下来之前就变得不透明、如何让退出快于进入,以及如何将这组值作为共享值公开,以便每个屏幕保持一致。在工具栏或面板看起来像是跳入位置而非滑入、进入和退出的感觉不一致,或同一控件在两个屏幕上的动画表现不同时使用。
  • transparent-chip-selection-signals — 透明选择标签的问题——透明形状下方的投影会透出来,形成深色圆环;将静止状态的高度设为零也无法移除它,因为每种交互状态都有各自独立的令牌默认值。涵盖如何移除仅在选中时显示的前置图标,避免标签宽度增加 26dp,导致用户刚点击后手指下方的滚动行重新排布;如何在填充色出现时关闭轮廓;以及如何将角色颜色与其对应的“on”令牌配对。在透明标签出现按下或悬停时又会恢复的深色光晕、点击标签会让相邻标签左右移动,或选中标签的文本在其中一种主题下无法辨认时使用。
  • word-timed-karaoke-lyrics — 由滴答前进的时间源直接驱动的逐词卡拉 OK 擦除效果看起来会一顿一顿;用户向后拖动进度后,已经唱完的词仍保持亮起;或者将现有逐行歌词渲染器改为逐词高亮时,会出现不同步的区域,从头到尾都发白。在构建或调试逐词歌词高亮、同步字幕视图,或任何由播放时钟驱动的从左到右文本“填充”效果时使用。
  • compose-capture-to-share-image — 在屏幕外将可组合项渲染为 ImageBitmap,让按钮可以将其保存或分享为图片——把捕获原语移植到通用代码中,而不是引入仅适用于平台的依赖;用最大宽度上限代替固定输出尺寸;确保图片素材在按钮可供使用前已完成解析;以及通过权限限制 Android MediaStore 保存,而同一功能的 Desktop 保存无需该权限。在“分享为图片”功能导出空白或只绘制了一部分的图片、导出图片在不同设备上尺寸不一,或保存功能在某些 Android 版本上有效、在其他版本上静默失败时使用。

图表和统计信息展示

  • self-normalised-axes-need-a-second-shape — 构建雷达图/指纹图,坐标轴根据你自己的数据归一化到 0..1——无需外部数据集,每个坐标轴使用一个受保护的分母,并添加第二个多边形(上一周期),因为在自归一化坐标轴上,单独一个形状毫无意义。当“使用习惯画像”或“年度回顾”图表需要一个无法获取的参照时;当一个数据稀疏的周期导致某个坐标轴受保护后归零,而另外四个坐标轴有实际读数时;或者当某个坐标轴出现 NaN,导致整个多边形消失时使用。
  • dont-slice-one-circle-between-unrelated-measures — 将没有共同整体的多个指标放到同心弧上,而不是将一个圆切分为多个部分;将最大弧限制在不足 360°,并在完整圆环轨道上绘制数值扇区。当甜甜圈图/饼图即将用来表示总和毫无意义的计数时;当最大的圆环看起来和完整圆环一样时;或者当“几乎没有”的类别看起来像缺失的刻度时使用。
  • delta-absent-not-infinite — 与空基线或零基线相比时,将变化值完全隐藏——绝不显示“+100%”、无穷大或饱和值;同时保护用于比较的两个范围和除数。当新用户的首个周期相较于所有数值都显示大幅增长时;当下降的数值旁边显示“+0%”时;或者当屏幕上的所有变化值都显示为下降,原因却无人能解释时使用。
  • partial-chart-must-say-so — 显示分布实际能够分类的输入占比,并使用与构建分桶时完全相同的谓词计算;即使覆盖率为零,也要确保这行信息仍可见。当图表基于可空的联接结果或经过解析的文本列构建时;当柱形看似合理,但底部的总数对不上时;或者当最需要免责声明的情况恰好什么也无法渲染时使用。
  • equal-buckets-or-no-buckets — 将范围划分为宽度完全相同的分桶,并让余数留在范围之外;根据用户要阅读的行数选择分桶单位,绝不要将最新的不完整分桶按完整宽度绘制。当柱状图中最旧或最新的柱莫名其妙地过长或过短时;当一个范围产生 30 行、没人会读时;或者当一组范围中的某个范围朝与其他范围相反的方向渲染时使用。
  • mosaic-arrangements-must-be-hole-free — 构建排序马赛克(一块大块加若干小块),为所有可能的数量都提供一种布局,确保不会悄悄漏掉任何条目,也不会留下空矩形;并且只在整个区块外围裁剪一次,而不是对每个图块分别裁剪。当“前五名”只显示四项时;当网格在某些数量下出现明显空隙时;或者当针对“不支持”尺寸的提前返回看似无害时使用。

评论

支持 Markdown