compose-visuals-footguns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Compose visuals footguns: theming, effects and charts

Compose 视觉陷阱:主题、特效与图表

35 traps mined from a production Kotlin and Compose Multiplatform app, one short file each under
references/
. Every file gives the working pattern, the Traps (the specific ways it fails in practice, and why), and a Verifying it section with commands to run against the user's own tree.
How to use this index. Match the code about to be written, or the symptom being chased, against the entries below: each names its topic and the symptom that should send you to it. Read every file that plausibly applies with the Read tool before proposing code or a fix; each is under 150 lines. Where a Verifying command names the source project's paths, substitute the equivalent paths in the user's tree.
Cross-references. A backticked trap name inside a file that is not listed here lives in a sibling area skill: glob
../*/references/<name>.md
to open it.
从生产级 Kotlin 和 Compose Multiplatform 应用中提炼出的 35 个陷阱,每个陷阱在
references/
下对应一个简短文件。每个文件都提供可用的实现模式、陷阱(实际失败的具体方式及原因),以及 验证方法 一节,其中包含可针对用户自身项目目录运行的命令。
如何使用此索引。 将准备编写的代码或正在排查的症状与下方条目进行匹配:每个条目都列出其主题,以及应据此查阅该条目的症状。在提出代码或修复方案前,使用 Read 工具阅读所有可能适用的文件;每个文件都不到 150 行。如果验证命令引用了源项目中的路径,请在用户的项目目录中替换为对应路径。
交叉引用。 如果某个文件中出现了反引号包裹、但未在此处列出的陷阱名称,则它位于相邻领域的 skill 中:使用 glob
../*/references/<name>.md
打开它。

Theming, colour, gradients and scrims

主题、颜色、渐变与遮罩

  • smooth-scrim-gradient
    — Build a scrim that melts artwork into the page background without a visible seam — smoothstep easing so the ramp is flat at both ends, colour stops interpolated in Kotlin rather than left to the renderer, and a transparent stop that carries your own RGB instead of Color.Transparent. Use when a gradient overlay shows a hard line where it starts or ends, when the middle of a fade turns muddy grey or darker than either end, or when a fade that looks right on one platform bands into stripes on another.
  • angled-gradient-modifier
    — Draw a linear gradient at an arbitrary angle across a Compose box so both endpoints land exactly on the box edge — the per-quadrant endpoint formula from the requested angle, why rotating the diagonal overshoots and why clamping to the nearest edge distorts the angle, and the degenerate cases that collapse the ramp to nothing. Use when a tilted gradient looks washed out or cut off near the corners, when the visible angle does not match the angle you asked for, or when the same gradient looks different on a wide box than on a tall one.
  • artwork-palette-theming
    — Drive a screen's colours from its artwork — which extracted swatch to use for an accent versus for a large page background, luminance-adaptive darkening so overlaid text stays readable on any image, the hex parsing helper this needs, and what to do when extraction returns nothing or there is no artwork at all. Use when a page background comes out lurid or unreadably light, when it flashes or changes while scrolling a list, or when a screen renders transparent or invisible instead of tinted.
  • force-dark-immersive-subtree
    — Keep one part of a Compose app rendered dark — screens drawn over dark artwork — while the rest of the app follows the user's light theme, by providing a whole dark colour scheme plus your own token locals to that subtree; includes why a text-colour flag alone is not enough, and the verified fact that bottom sheets and dialogs inherit these locals across the window boundary while a freshly rooted composition does not. Use when icons or buttons on an image-backed screen turn grey and unreadable in light theme, when a sheet opened from such a screen comes out the wrong colour, or when a forced-dark screen still asks the system whether it is night.
  • semantic-color-tokens-compositionlocal
    — Hold the colours that have no Material role — a liked-state pink, an active-line highlight, shimmer tones, overlays that sit on artwork — in an @Immutable token class provided through staticCompositionLocalOf, with the bytecode-level reason static is the right choice for theme values and a rule for what belongs in the token class versus in the colour scheme. Use when hex literals are spreading through composables, when a colour has to differ between light and dark but is not a scheme role, or when you are choosing between staticCompositionLocalOf and compositionLocalOf.
  • deterministic-title-placeholder-painter
    — Give items with no artwork a cover of their own by hashing the title into a stable gradient and packaging it, plus measured text and a badge, as a custom Painter you can hand straight to an image loader's placeholder, error and fallback slots — covering what makes the hash actually deterministic, measuring text outside a layout pass, and reporting an intrinsic size. Use when coverless rows all look identical, when a generated colour changes between runs or platforms, when placeholder text spills outside its tile, or when a null image model leaves a blank square.
  • overflow-tilted-browse-card
    — Build a browse tile whose cover art is tilted and runs off the clipped corner — the modifier order that makes the diagonal a cut rather than a pasted square, the rotated bounding-box growth that decides how much room siblings must leave, and why a non-square tile cannot size its decoration off the width. Use when a rotated image shows sliced corners, when text collides with tilted art or reflows the moment that art loads, or when a rotated child covers its neighbours instead of being clipped by its parent.
  • artwork-seeded-dynamic-scheme
    — Derive a complete tonal colour scheme from one artwork-extracted seed and wrap only that subtree in it, so every control inherits contrast-paired roles instead of hand-picked swatches — covers falling back by comparing against the sentinel the seed was initialised with, tweening the seed rather than the scheme, and the cost of re-deriving per animation frame. Use when a screen should recolour itself to its artwork, when foreground text on a tinted surface comes out unreadable, or when a colour change lands as a visible flash.
  • palette-state-parks-on-loading
    — A palette generator that flips its state to Loading before its own suspension point reports no colour for the entire duration of every generation, and a generation cancelled part-way leaves it Loading with nothing to restart it — so any surface reading the palette directly paints its null fallback. Covers why the effect must be keyed on the bitmap alone (or on nothing at all), why a "already done" flag assigned after a suspension is not a record of done, and why the last resolved colour has to be held separately. Use when a screen tinted from artwork is sometimes right and sometimes black, when the same item tints correctly on one visit and not the next, or when a derived colour scheme silently sits on its fallback.
  • gate-optional-effect-at-the-shared-primitive
    — Put a user's on/off setting for an optional decorative surface treatment inside the one shared primitive that draws it — published from the theme as a CompositionLocal defaulting to on — instead of asking every call site to check the flag. Covers why the off path must keep the shape and the hit target and change only the paint, why the default is true rather than false, and the difference between a call site that picks a material and one that picks a different composable. Use when a settings toggle reaches only some of the surfaces it names, when a gated component renders as a bare box in previews, or when turning an effect off also moves the layout.
  • shell-background-is-not-scheme-background
    — The moment an app shell paints its content panels in anything other than colorScheme.background, every gradient, scrim and fade whose tail converges on colorScheme.background ends on a hard seam — the decoration is still correct, its destination colour is just no longer on screen. Covers resolving one page-background value and handing it to every decoration, the two valid answers (aim at the shell colour, or paint your own ground), and sizing a decoration with matchParentSize over the content it decorates rather than a constant measured on one form factor. Use when a gradient stops mid-screen with a visible edge, when a fade looks right on a phone and wrong in a desktop window, or when adding a window chrome breaks screens that were never touched.
  • flat-twin-shares-geometry-not-material
    — When a user setting picks between two renderings of the same control — an expensive decorative one and a plain twin — every geometry decision has to be mirrored between them (item width, bar height, indicator size, inset, and the rule that computes the width budget) so toggling changes the material and never the layout; the compiler cannot pair two constants declared in two files. Also covers recolouring an icon that arrives as a slot lambda, which only CompositionLocalProvider(LocalContentColor) can reach. Use when switching a visual setting also moves things, when the plain variant's indicator sits off its item, or when a slot-lambda icon ignores every tint you pass.
  • chrome-drawn-outside-the-theme-scope
    — A custom window title bar, splash, or crash dialog composed as a sibling of the app theme rather than inside it reads MaterialTheme's framework DEFAULT scheme — light, always, no matter what the user picked — and nothing errors. Covers extracting the stored-mode-to-boolean decision as one shared composable function so the chrome and the theme cannot answer differently, passing colours in as parameters instead of re-theming, and the colours that must deliberately not follow the theme at all. Use when a title bar or dialog stays light in dark mode, when chrome colours lag one launch behind the setting, or before adding any composable above the theme call.
  • ambient-tone-layer-behind-flat-pages
    — A reusable top-glow layer that gives pages with no imagery of their own the same tinted ground the image-backed screens get — emitted as the first sibling of a navigation destination's content, with no wrapper, because destinations already stack. Covers why a null tone must collapse the gradient into the page colour instead of substituting a theme colour, why the scroll-away offset belongs in the draw phase, why the first list item must be taller than the layer, and which screens are right to keep their own copy. Use when a flat settings or list page looks unrelated to the rest of the app, when an idle app shows a glow for nothing, or when a glow snaps out of place as the list scrolls.
  • smooth-scrim-gradient
    — 构建将插图融入页面背景且无可见接缝的遮罩:在两端保持平坦的 smoothstep 缓动、在 Kotlin 中而非渲染器中插值颜色停靠点,并让透明停靠点携带自己的 RGB,而不是使用 Color.Transparent。适用于渐变覆盖层起始或结束处出现硬边、淡出中段变成浑浊灰色或比任一端更暗,或在某个平台看似正常的淡出在另一平台出现条纹时。
  • angled-gradient-modifier
    — 在 Compose Box 上以任意角度绘制线性渐变,并让两个端点精确落在 Box 边缘——介绍基于请求角度的逐象限端点公式、为何旋转对角线会超出边界、为何将角度限制到最近边缘会扭曲角度,以及会让渐变塌缩为空的退化情况。适用于倾斜渐变显得褪色或在角落被截断、可见角度与指定角度不一致,或同一渐变在宽 Box 与高 Box 中表现不同时。
  • artwork-palette-theming
    — 使用插图提取的色板驱动界面颜色——哪个提取的 swatch 用作强调色、哪个用于大面积页面背景,基于亮度的自适应加深以确保叠加文字在任何图片上可读,所需的十六进制解析辅助方法,以及提取结果为空或完全没有插图时该怎么办。适用于页面背景艳丽刺眼或过亮导致无法阅读、滚动列表时背景闪烁或变化,或界面渲染为透明或不可见而不是带有色调时。
  • force-dark-immersive-subtree
    — 通过向子树提供完整的深色配色方案以及自己的 token locals,让 Compose 应用的一部分保持深色渲染——即绘制在深色插图之上的屏幕——而应用其余部分遵循用户的浅色主题;包括为何仅设置文字颜色标记不够,以及经验证,底部弹窗和对话框会跨窗口边界继承这些 locals,而新创建的根 composition 不会。适用于浅色主题下图片支撑屏幕中的图标或按钮变成灰色且无法阅读、从此类屏幕打开的弹窗颜色错误,或强制深色屏幕仍会询问系统当前是否为夜间模式时。
  • semantic-color-tokens-compositionlocal
    — 将没有 Material 角色的颜色——例如点赞状态粉色、当前行高亮、shimmer 色调、覆盖在插图上的叠加层——保存在通过 staticCompositionLocalOf 提供的 @Immutable token 类中;从字节码层面说明为何主题值应使用 static,并说明哪些内容应放入 token 类、哪些应放入配色方案。适用于十六进制字面量散落在各个 composable 中、某种颜色在浅色与深色主题下需要不同但不属于方案角色,或需要在 staticCompositionLocalOf 与 compositionLocalOf 之间选择时。
  • deterministic-title-placeholder-painter
    — 将标题哈希为稳定渐变,并把它与测量后的文本和徽章打包成自定义 Painter,直接传给图片加载器的 placeholder、error 和 fallback 插槽,从而为无插图项目生成专属封面——涵盖如何确保哈希真正稳定、如何在布局过程之外测量文本,以及如何报告固有尺寸。适用于所有无封面行看起来完全相同、生成的颜色在不同运行或平台间发生变化、占位文本溢出瓦片,或 null 图片模型留下空白方块时。
  • overflow-tilted-browse-card
    — 构建封面图倾斜并越过裁剪角的浏览卡片——modifier 顺序决定对角线是裁切效果而非后贴的方块,旋转后的边界框增长决定同级元素必须留出多少空间,以及为何非正方形卡片不能用宽度计算装饰尺寸。适用于旋转图片出现被切掉的角、文本与倾斜插图碰撞或在插图加载时重新排版,或旋转子项覆盖同级元素而未被父项裁剪时。
  • artwork-seeded-dynamic-scheme
    — 从一个插图提取的 seed 推导完整的色调配色方案,并仅将该子树包裹其中,让每个控件继承成对且具有对比度的角色,而不是手动挑选 swatch——涵盖通过与初始化 seed 时使用的 sentinel 比较来回退、只对 seed 而非方案做 tween,以及逐动画帧重新推导的成本。适用于界面需要根据插图重新着色、着色表面上的前景文字无法阅读,或颜色变化表现为可见闪烁时。
  • palette-state-parks-on-loading
    — 调色板生成器会在自身的挂起点之前把状态切换为 Loading,因此在每次生成期间都会报告无颜色,而生成中途取消还会让它停留在 Loading,且没有可用于重启的状态——于是任何直接读取调色板的界面都会绘制其 null 回退值。涵盖为何该 effect 必须仅以 bitmap 为 key(或完全不使用 key)、为何在挂起之后赋值的“already done”标记并不代表已完成,以及为何必须单独保存最后解析出的颜色。适用于界面有时能根据插图正确着色、有时却变成黑色、同一个项目首次访问时着色正确而后续访问不正确,或派生配色方案静默使用回退值时。
  • gate-optional-effect-at-the-shared-primitive
    — 将用户针对可选装饰性表面处理的开关设置放入绘制该效果的唯一共享 primitive 中——该设置由主题发布为默认开启的 CompositionLocal——而不是要求每个调用点检查标记。涵盖为何关闭路径必须保留形状和点击区域、只改变绘制内容,为何默认值应为 true 而不是 false,以及选择 material 的调用点与选择其他 composable 的调用点有何差别。适用于设置开关只影响部分指定表面、受控组件在预览中显示为裸方块,或关闭效果时同时移动了布局。
  • shell-background-is-not-scheme-background
    — 应用 shell 一旦使用 colorScheme.background 以外的颜色绘制内容面板,所有尾端收敛到 colorScheme.background 的渐变、scrim 和淡出都会以硬接缝结束——装饰本身仍然正确,只是其目标颜色已不在屏幕上。涵盖解析唯一的页面背景值并将其传给所有装饰的方案、两种有效答案(瞄准 shell 颜色,或自行绘制底色),以及用 matchParentSize 根据所装饰内容而非某一形态下测得的常量来调整装饰尺寸。适用于渐变在屏幕中途停止并出现可见边缘、淡出效果在手机上正常但在桌面窗口中异常,或添加窗口 chrome 后破坏从未修改过的界面时。
  • flat-twin-shares-geometry-not-material
    — 当用户设置要在同一控件的两种渲染方式之间切换——一种昂贵的装饰性实现和一种朴素版本——所有几何决策都必须在二者之间保持一致(项目宽度、条形高度、指示器尺寸、边距,以及计算宽度预算的规则),这样切换只会改变 material,绝不会改变布局;编译器无法自动配对两个文件中声明的两个常量。还涵盖对以 slot lambda 传入的图标重新着色,因为只有 CompositionLocalProvider(LocalContentColor) 才能影响它。适用于切换视觉设置时元素位置也发生变化、朴素版本的指示器偏离项目位置,或 slot lambda 图标忽略所有传入的 tint 时。
  • chrome-drawn-outside-the-theme-scope
    — 作为应用主题同级而非内部构成的自定义窗口标题栏、启动画面或崩溃对话框,会读取 MaterialTheme 的框架 DEFAULT 方案——无论用户选择什么始终为浅色——且不会报告任何错误。涵盖将“存储模式到布尔值”的判断提取为一个共享 composable 函数,使 chrome 与主题不会得出不同结果,以参数传入颜色而不是重新应用主题,以及哪些颜色必须刻意不跟随主题。适用于深色模式下标题栏或对话框仍为浅色、chrome 颜色比设置晚一次启动才更新,或在主题调用上方添加 composable 之前。
  • ambient-tone-layer-behind-flat-pages
    — 可复用的顶部辉光层,为本身没有图片的页面提供与图片支撑屏幕相同的着色底色——作为导航目标内容的第一个同级项发出,不添加包装器,因为目标本身已经构成堆叠。涵盖为何 null tone 必须让渐变塌缩为页面颜色而不是替换为主题颜色、为何滚动移除偏移应属于 draw 阶段、为何列表首个项目必须比该层更高,以及哪些屏幕适合保留自己的副本。适用于扁平的设置或列表页面与应用其他部分显得格格不入、空闲应用无缘无故显示辉光,或辉光在列表滚动时突然错位时。

Effects, animation and images

特效、动画与图片

  • material-symbols-icon-system
    — Ship every icon as a generated ImageVector extension property on one receiver object, fetched from the icon font's own Compose generator, so the bytecode shrinker can drop the ones you never reference. Covers the generator request and its axis parameters, the edits that turn a generated file into an extension property, the filled/unfilled pairing for state icons, and which icons must stay as drawable resources. Use when adding or replacing an icon, when a set has drifted into mismatched weights and corner styles, or when a type error reports an ImageVector where a Painter was expected.
  • liquid-glass-backdrop
    — Build refracting "liquid glass" surfaces in Compose with a backdrop library, and avoid the three failures that waste the most time — a rim highlight that is directional by default and so goes nearly invisible on small round buttons, a backdrop source nested inside the glass it feeds, which is a render-feedback loop that stops the shader, and a source with nothing in it, which renders the control as a grey coin over a flat page. Covers the source/surface split, giving a flat page a ground worth refracting rather than dropping the effect, the white default tint that only suits forced-dark screens, the effect stack, keeping the press gesture observe-only, and the swap experiment that tells a geometry problem from a placement problem. Use when a glass surface renders as a flat rounded box or a grey coin, when the rim shows on a wide pill but not on a circular button, when its glyph disappears at light theme, or when the draw pass crashes inside the shader.
  • shimmer-skeleton-loaders
    — A self-measuring shimmer modifier plus skeleton composables that stand in for a list while it loads — how the modifier learns its own size, why the base colour under the sweep is load-bearing, why the modifier order between clip and background changes what gets rounded, and why the skeleton's lazy lists must have scrolling switched off. Use when building loading placeholders, or when a shimmer renders as a flat block, has square corners under a rounded design, or pauses visibly between passes.
  • animated-gradient-border-ring
    — A rotating sweep-gradient ring around a card, chip or button, built from a clipped box, a full-size gradient and an opaque inset surface parked on its middle — where the modifier order is the whole mechanism and no blend mode is involved — plus why the SrcIn-inside-an-offscreen-layer version of this cannot draw a ring, and what that layer costs. Use when the gradient covers the whole surface instead of the border, when the ring is invisible, when it bleeds over whatever is behind the widget, or when a row of them makes scrolling expensive.
  • hoist-the-flag-not-the-animation
    — Share the boolean that drives a fade, never the tween that runs it — each look derives its own curve, so one can go asymmetric (fast in, slow out) without changing how the other feels; covers why a symmetric linear fade over a bright backdrop makes container-backed controls read as the wrong colour, and why the shared animated value must stay published anyway. Use when buttons look lighter or darker than their neighbours only while something is fading, when one visual style needs a different timing from another, or before pulling an
    animateFloatAsState
    up into a shared state holder.
  • dont-pre-animate-a-self-animating-property
    — Pass the raw target to a component that animates a property itself — an externally tweened value is a stream of new targets, and such components typically ignore new targets while their own animation is still running, which freezes the effect part-way. Covers how to recognise a self-animating property, why a hard flip between endpoints is the fix, and how to prove ownership from the compiled artifact. Use when an animated component sticks near its starting value, when a transition plays once and never again, or before wrapping a component's input in
    animateFloatAsState
    .
  • draw-outside-bounds-particle-modifier
    — A celebration burst drawn by a plain draw modifier that paints past its host's own bounds — draw modifiers are unclipped by default, so the modifier must sit before every .clip(...) in the chain, and any clipping ancestor still trims whatever leaves its edge. Covers scaling origin and reach off the host's measured size so one effect reads the same at 28dp and at 48dp, keeping the per-frame lambda cheap, and why pure drawing beats an animation library here. Use when a burst renders cut to the button's outline, when it vanishes inside a rounded card or list row, or before adding a vector-animation dependency to draw one.
  • image-fallback-url-retry-in-composition
    — Retry a record's image at a variant the source guarantees when the high-resolution one is missing, by holding the URL in composition state and swapping it once in onError. Covers making the disk-cache key follow the mutated URL, resetting the state per record, keeping the swap a no-op the second time so it cannot loop, and the invisible cost — everything hanging off onSuccess (a palette, and the page theme derived from it) silently never runs. Use when a page keeps its fallback colour for particular records, when a retried image is re-fetched on every visit, or when an image request appears to retry forever.
  • text-brush-shimmer-sweep
    — Sweep a travelling highlight through a label by putting a moving gradient on the TextStyle itself — the glyphs are painted by the brush, so there is no overlay, no clip and no measured width to keep in sync. Covers declaring the infinite transition unconditionally so the sweep does not restart every time the label appears, why the sweep head must be a pure high-contrast colour rather than the label's own, why the gradient stops are pixels and must travel past both ends, and that a brush replaces the text colour outright. Use when a shimmering label jumps back to the start whenever it reappears, when the gleam is invisible against the label's own grey, or when setting a brush makes a carefully chosen text colour vanish.
  • touch-indication-bounds-and-alpha
    — Soften an app-wide touch ripple by alpha alone and give it the right bounds — the colour derives from the local content colour and stays correct inside a forced-scheme subtree, while pinning one paints darker than a near-black surface. Covers why the theme's configuration reaches a bare Modifier.clickable, why .clip(shape) must come before .clickable, why a card and its clip need one shape value, why two clickable modifiers must never stack, and the deprecated constructor that is the only way to set alpha. Use when a tap on a rounded item flashes a square, when the ripple reads as a sooty smudge on a dark theme, or when a long-pressable item ripples twice.
  • crossfade-container-sizes-to-the-visible-child
    — A crossfade's container is a Box aligned to the top-start corner and sized to the largest child currently composed, so swapping a thin child for a taller one pins the thin one to the top mid-transition and drops it when the tall one leaves. Covers boxing both branches into one fixed frame with an explicit alignment, replacing rather than stacking two progress renderers whose track lengths differ, and why fading a whole interactive control makes it untouchable. Use when a bar visibly falls into place after a state change, when two stacked tracks are different lengths, or when a control stops responding for the length of a fade.
  • slide-transition-defaults-to-half-a-height
    — A vertical slide transition defaults to HALF the element's height, so it appears already halfway through its own movement and the first part is missing — which reads as a pop, not a slide. Covers passing the full height, the sign that decides which edge it comes from, pairing a shorter fade with a longer slide so the element is opaque before it settles, making exit quicker than enter, and publishing the pair as shared values so every screen matches. Use when a bar or panel seems to snap into place instead of sliding, when enter and exit feel mismatched, or when the same control animates differently on two screens.
  • transparent-chip-selection-signals
    — A see-through selection chip — a drop shadow under a transparent shape shows THROUGH it as a dark ring, and zeroing the resting elevation does NOT remove it, because each interaction state carries its own independent token default. Covers dropping the selection-only leading icon that widens the chip by 26dp and reflows a scrolling row under the finger that just tapped it, switching the outline off when the fill arrives, and pairing a role colour with its own "on" token. Use when a transparent chip has a dark halo that comes back on press or hover, when tapping a chip shifts its neighbours sideways, or when a selected chip's label is unreadable on one of the two themes.
  • word-timed-karaoke-lyrics
    — A per-word karaoke wipe driven straight off a ticking time source looks stepped instead of smooth, a word that was already fully sung stays lit after the user seeks backward past it, or skinning an existing line-level lyrics renderer for word-level highlighting leaves an unsynced sheet glowing white end to end. Use when building or debugging word-by-word lyric highlighting, a synced-transcript view, or any left-to-right text "fill" effect driven by a playback clock.
  • compose-capture-to-share-image
    — Render a composable off-screen into an ImageBitmap so a button can save or share it as a picture — a capture primitive ported into common code rather than pulled in as a platform-only dependency, a max-width ceiling standing in for a fixed output size, artwork that has to already be resolved before the button is even reachable, and an Android MediaStore save gated behind a permission that the same feature's Desktop save never needs. Use when a "share as image" feature exports a blank or half-drawn picture, when the exported picture comes out a different size on every device, or when saving works on some Android versions and fails silently on others.
  • material-symbols-icon-system
    — 将每个图标都作为单一接收器对象上生成的 ImageVector 扩展属性交付,并从图标字体自身的 Compose generator 获取,使字节码 shrinker 能删除从未引用的图标。涵盖生成器请求及其轴参数、把生成文件改为扩展属性所需的编辑、状态图标的 filled/unfilled 配对,以及哪些图标必须保留为 drawable 资源。适用于添加或替换图标、一组图标的字重和圆角样式逐渐不一致,或类型错误提示需要 ImageVector 却给出了 Painter 时。
  • liquid-glass-backdrop
    — 使用 backdrop library 在 Compose 中构建具有折射效果的“液态玻璃”表面,并避免最耗时的三种失败——默认具有方向性的边缘高光因而在小型圆形按钮上几乎不可见;嵌套在玻璃内部并作为其输入的 backdrop source 会形成渲染反馈循环,导致 shader 停止;空 source 会把控件渲染为浮在平面页面上的灰色硬币。涵盖 source/surface 拆分、为平面页面提供可供折射的底色而不是直接放弃效果、只适合强制深色屏幕的白色默认 tint、效果堆栈、保持按下手势仅作观察,以及用于区分几何问题与布局问题的替换实验。适用于玻璃表面呈现为扁平圆角方块或灰色硬币、宽胶囊按钮可见边缘高光但圆形按钮不可见、浅色主题下图标消失,或绘制过程在 shader 内崩溃时。
  • shimmer-skeleton-loaders
    — 自测量 shimmer modifier,加上加载时替代列表的 skeleton composable——modifier 如何获知自身尺寸、扫光下方的 base colour 为何至关重要、clip 与 background 之间的 modifier 顺序如何改变圆角,以及 skeleton 的 lazy list 为何必须关闭滚动。适用于构建加载占位符,或 shimmer 呈现为平面方块、在圆角设计下仍显示直角,或两次扫光之间出现明显停顿时。
  • animated-gradient-border-ring
    — 围绕卡片、chip 或按钮构建旋转的扫掠渐变环——通过裁剪 Box、全尺寸渐变和停放在中间的不透明内嵌表面实现——modifier 顺序本身就是全部机制,不涉及任何 blend mode;同时说明位于离屏图层内的 SrcIn 版本为何无法绘制圆环,以及该图层的成本。适用于渐变覆盖整个表面而不是边框、圆环不可见、溢出到控件后方,或一排圆环导致滚动开销过高时。
  • hoist-the-flag-not-the-animation
    — 共享驱动淡出动画的 boolean,绝不共享运行该动画的 tween——每种视觉呈现都派生自己的曲线,因此某一种可以采用非对称节奏(快进慢出),而不改变另一种的观感;涵盖为何明亮背景上的对称 linear fade 会让容器支撑的控件呈现错误颜色,以及共享 animated value 为何仍须保持对外提供。适用于淡出期间按钮比相邻控件显得更亮或更暗、某视觉样式需要与其他样式不同时序,或将 animateFloatAsState 上提到共享 state holder 之前。
  • dont-pre-animate-a-self-animating-property
    — 将原始目标值传给会自行对属性执行动画的组件——外部 tween 后的值是一连串新目标,而此类组件通常会在自身动画仍运行时忽略新目标,导致效果中途冻结。涵盖如何识别自行动画的属性、为何在两个端点之间硬切换是修复方式,以及如何从编译产物证明动画所有权。适用于动画组件停在接近起始值的位置、过渡只播放一次而不再重播,或在把组件输入包装进 animateFloatAsState 之前。
  • draw-outside-bounds-particle-modifier
    — 通过普通 draw modifier 绘制的庆祝爆发效果会绘制到宿主自身边界之外——draw modifier 默认不裁剪,因此该 modifier 必须置于链中所有 .clip(...) 之前,而任何裁剪祖先仍会裁掉超出边缘的部分。涵盖缩放原点与超出宿主测量尺寸的波及范围,使同一效果在 28dp 和 48dp 下观感一致,保持逐帧 lambda 轻量,以及为何这里纯绘制优于动画库。适用于爆发效果被裁成按钮轮廓、在圆角卡片或列表行中消失,或在为一个效果添加 vector-animation 依赖之前。
  • image-fallback-url-retry-in-composition
    — 当高分辨率图片缺失时,通过在 composition state 中保存 URL,并在 onError 中一次性切换到数据源保证存在的变体,重试记录的图片。涵盖让磁盘缓存 key 跟随变更后的 URL、按记录重置状态、确保第二次切换为空操作以避免循环,以及不可见成本——挂在 onSuccess 上的所有内容(调色板及由其推导的页面主题)都会悄无声息地不再运行。适用于某些记录始终使用回退颜色、重试图片每次访问都被重新获取,或图片请求看似无限重试时。
  • text-brush-shimmer-sweep
    — 通过把移动渐变直接设置在 TextStyle 上,让行进高光扫过标签——字形由 brush 绘制,因此无需 overlay、clip,也无需保持同步的测量宽度。涵盖无条件声明无限 transition,使扫光不会在标签每次出现时重启;扫光头部必须是纯高对比颜色,而不是标签自身的颜色;渐变停靠点以像素为单位,必须移动并越过两端;以及 brush 会彻底替换文字颜色。适用于带微光的标签每次重新出现都跳回起点、高光在标签自身的灰色上不可见,或设置 brush 后精心选定的文字颜色消失时。
  • touch-indication-bounds-and-alpha
    — 仅通过 alpha 柔化全应用触摸涟漪,并为其提供正确边界——颜色派生自本地 content colour,在强制配色子树中仍保持正确,而固定颜色在近黑表面上会绘制得更暗。涵盖为何主题 configuration 会到达裸的 Modifier.clickable,为何 .clip(shape) 必须位于 .clickable 之前,为何卡片及其 clip 必须使用同一个 shape 值,为何两个 clickable modifier 绝不能叠加,以及为何只有已弃用的构造函数才能设置 alpha。适用于点击圆角项目时闪现方形涟漪、深色主题下涟漪像煤烟污渍,或长按项目产生两次涟漪。
  • crossfade-container-sizes-to-the-visible-child
    — crossfade 的容器是一个对齐到 top-start 角并按当前参与 composition 的最大子项设定尺寸的 Box,因此把较矮子项替换为较高子项时,矮项会在过渡中固定在顶部,并在高项离开时消失。涵盖将两个分支都放入具有显式对齐方式的固定框架、替换而非堆叠两个轨道长度不同的进度渲染器,以及为何淡化整个交互控件会使其无法触摸。适用于状态变化后条形图明显落入到位、两个堆叠轨道长度不同时,或控件在淡化持续期间停止响应。
  • slide-transition-defaults-to-half-a-height
    — 垂直滑动过渡默认移动元素高度的 HALF,因此看起来从自身移动过程的中途开始,缺失了前半段——这看起来是 pop,而不是 slide。涵盖传入完整高度、决定从哪条边缘出发的符号、将较短的 fade 与较长的 slide 配对,使元素在稳定前保持不透明、让退出比进入更快,以及将二者作为共享值提供,使所有界面保持一致。适用于条形或面板看似直接吸附到位而不是滑动、进入与退出感觉不匹配,或同一控件在两个界面上动画表现不同时。
  • transparent-chip-selection-signals
    — 透明选择 chip——透明形状下方的 drop shadow 会透过它显现为深色圆环,将静止状态的 elevation 归零并不能移除它,因为每个交互状态都携带自己的独立 token 默认值。涵盖移除仅在选中时显示的前置图标(它会将 chip 宽度增加 26dp,并在手指刚点按时重排滚动行),在填充出现时关闭 outline,以及为 role colour 搭配自己的“on” token。适用于透明 chip 出现按压或悬停时又回来的深色光晕、点按 chip 导致相邻项横向移动,或选中 chip 的标签在两套主题之一中无法阅读。
  • word-timed-karaoke-lyrics
    — 直接由 ticking time source 驱动的逐词卡拉 OK 擦除效果看起来会有步进感;已经唱完的词在用户将播放位置回退到该词之前后仍会保持点亮;对现有行级歌词渲染器进行词级高亮改造,会让未同步的整行歌词一直发白。适用于构建或调试逐词歌词高亮、同步 transcript 视图,或任何由播放时钟驱动的从左到右文字“填充”效果。
  • compose-capture-to-share-image
    — 将 composable 渲染到屏幕外并生成 ImageBitmap,让按钮可以将其保存或分享为图片——将 capture primitive 移植到 common code,而不是引入仅限平台的依赖,以最大宽度上限代替固定输出尺寸,要求插图在按钮可点击前就已解析完成,以及将 Android MediaStore 保存置于权限控制之后,而同一功能的 Desktop 保存并不需要该权限。适用于“分享为图片”功能导出空白或只绘制一部分的图片、导出图片在每台设备上尺寸都不同,或在部分 Android 版本上保存正常、另一些版本上则静默失败时。

Charts and statistics displays

图表与统计展示

  • self-normalised-axes-need-a-second-shape
    — Build a radar/fingerprint chart whose axes are normalised 0..1 from your own data — no external corpus, one guarded denominator per axis, and a second polygon (the previous period) because a lone shape on self-normalised axes says nothing. Use when a "usage personality" or "year in review" chart needs a reference it cannot get, when a thin period leaves one axis at a guarded zero beside four real readings, or when one axis goes NaN and the whole polygon disappears.
  • dont-slice-one-circle-between-unrelated-measures
    — Put several measures that share no whole onto concentric arcs instead of slicing one circle between them, cap the largest sweep short of 360°, and draw value wedges over a full-ring track. Use when a donut/pie is about to encode counts that do not add up to anything, when the biggest ring looks identical to a full one, or when an "almost nothing here" bucket reads as a missing tick.
  • delta-absent-not-infinite
    — Render a change figure against an empty or zero baseline as nothing at all — never "+100%", never an infinity, never a saturated integer — and guard the two spans being compared as well as the divisor. Use when a new user's first period shows a huge increase against every figure, when "+0%" appears beside a number that went down, or when every delta on a screen reads as a decline for reasons nobody can explain.
  • partial-chart-must-say-so
    — Print the share of input a distribution could actually classify, computed with exactly the predicates that built the buckets, and keep that line reachable when coverage is zero. Use when a chart is built over a nullable join or a parsed text column, when the bars look plausible but the totals underneath disagree, or when the one case that most needs a disclaimer is the case that renders nothing.
  • equal-buckets-or-no-buckets
    — Split a span into buckets of exactly equal width and let the remainder fall outside, pick the bucket unit from how many rows a person will read, and never draw a partial newest bucket at full width. Use when a bar chart's oldest or newest bar is inexplicably long or short, when a range produces thirty rows nobody reads, or when one range in a set renders in the opposite direction from the others.
  • mosaic-arrangements-must-be-hole-free
    — Build a ranked mosaic (one big tile plus smaller ones) with an arm for every possible count, so no entry is silently dropped and no arrangement leaves an empty rectangle — plus one clip around the whole block rather than one per tile. Use when a "top five" shows four, when a grid renders a visible gap at some counts, or when an early return on an "unsupported" size looks harmless.
  • self-normalised-axes-need-a-second-shape
    — 构建雷达图或指纹图,其坐标轴根据自身数据归一化到 0..1——不依赖外部数据集,每条轴使用一个受保护的除数,并增加第二个多边形(上一周期),因为在自归一化坐标轴上只有单一形状无法表达任何信息。适用于“使用画像”或“年度回顾”图表需要自身无法获得的参照、某个极短周期使一条轴停在受保护零值旁而其他四条轴有真实读数,或某条轴变为 NaN 导致整个多边形消失时。
  • dont-slice-one-circle-between-unrelated-measures
    — 将彼此不构成整体的多个指标放到同心弧上,而不是切分同一个圆;将最大弧长限制在不足 360°,并在完整环形轨道上绘制数值楔形。适用于 donut 或 pie 即将编码互不构成总数的计数、最大环看起来与完整环无差别,或“几乎没有”的分桶看起来像缺失刻度时。
  • delta-absent-not-infinite
    — 将变化数值与空或零基线对比时,完全不显示任何内容——绝不显示“+100%”、Infinity 或饱和整数——并同时保护被比较的两个区间及其除数。适用于新用户首个周期相对所有数值都显示巨幅增长、数字下降时旁边却出现“+0%”,或屏幕上的每个 delta 都因无法解释的原因显示为下降。
  • partial-chart-must-say-so
    — 打印分布实际能够分类的输入占比,并使用与构建分桶完全相同的判定条件计算,同时让该行在覆盖率为零时仍可访问。适用于图表基于 nullable join 或解析后的文本列构建、柱状图看似合理但底层总数不一致,或最需要免责声明的唯一情况恰好什么也不渲染时。
  • equal-buckets-or-no-buckets
    — 将一个区间拆分为宽度完全相等的分桶,并让余数落在范围之外;根据一个人会阅读多少行来选择分桶单位,且绝不以完整宽度绘制最新的不完整分桶。适用于柱状图中最旧或最新的柱莫名其妙地过长或过短、某个范围产生三十行没人阅读的数据,或一组范围中有一个的渲染方向与其他范围相反时。
  • mosaic-arrangements-must-be-hole-free
    — 构建带排名的马赛克布局(一个大瓦片加若干小瓦片),为每一种可能的数量提供分支,从而不静默丢弃任何条目,也不留下空白矩形;整个区块只使用一次 clip,而不是每个瓦片分别 clip。适用于“前五名”只显示四个、网格在某些数量下出现可见空隙,或对“不支持”尺寸提前 return 看起来毫无影响时。