SuperAPI AI 工具集

deterministic-title-placeholder-painter

设计与创作

查看项目

更新于 10-17 16:17

技能介绍

用途

该技能面向 Kotlin/Compose 场景,解决“没有封面图的条目”如何显示的问题。相比所有条目共用一个灰色方块,它希望为每个条目生成独一无二的封面:颜色由标题哈希决定,因此同一标题每次渲染结果一致,并且可以同步获得——在网络加载之前、加载过程中,甚至完全替代网络加载。核心做法是把标题哈希成一个渐变色,再用 Painter 让加载器把它当作图片处理。

主要能力

  • 确定性配色:通过 gradientFromTitle(title) 从标题哈希推导两个色相,生成线性渐变。
  • 自定义 Painter:TitlePlaceholderPainter 继承 Painter,在 onDraw 中绘制圆角矩形渐变、测量好的文字以及一个徽章。
  • 同步可用:不依赖网络请求,可在加载前、加载中或替代加载时直接绘制。
  • 文字测量与布局:在组合阶段用 rememberTextMeasurer 测量标题,限制为两行、自动换行、省略号溢出,并按瓦片尺寸减去内边距约束。
  • 徽章叠加:在右上角绘制白色圆点并叠加徽章 Painter。

使用方式

技能提供了工厂函数 rememberTitlePlaceholder(title, style, sizeDp),在组合中获取 LocalDensity、创建文字测量器、测量布局、加载徽章资源,并把 Dp 尺寸转换为像素后构造 Painter,最后用 remember 缓存。

关键实现要点:

  • 文字测量放在组合阶段,绘制放在 onDraw,避免每帧测量。
  • 测量结果按 title、style、size 作为 key 缓存,并传入不可变结果。
  • 约束为瓦片尺寸减去内边距,否则文字会按全宽布局并溢出边缘。
  • Painter 没有密度概念,需在组合中把 Dp 转成像素后再构造,并把像素尺寸作为 intrinsicSize 返回。
  • 在 onDraw 中,未限定的 size 解析为 DrawScope.size(实际绘制尺寸),而非构造器属性;示例已遵循绘制尺寸,存储尺寸只用于 intrinsicSize。
  • 三个图片槽位要全部填充:placeholder 表示加载中,error 表示请求失败,fallback 表示模型为空(即无封面场景)。
  • 要缓存 Painter 本身,而不仅是测量结果,否则每次组合都会产生新实例,破坏跳过优化。
  • 颜色需要一致的地方要复用同一个生成函数,例如详情页背景着色应与瓦片调用同一函数。

适用场景

适用于 Kotlin/Compose 应用中列表或网格里没有封面图的条目,需要稳定、唯一、同步可用的占位视觉。也适用于需要在加载前、加载中或加载失败时保持视觉一致的场景。

限制与陷阱

  • 确定性依赖哈希行为:Kotlin/JVM 上 String.hashCode() 由 JDK 规范保证跨运行、跨机器、跨 JDK 版本稳定;但 Kotlin/Native 和 Kotlin/JS 未作保证,非 String 类型也未说明。若增加 Native 或 Web 目标,或需要持久化颜色,应自行对字节做哈希。
  • 哈希要掩码而非取余:哈希是有符号的,hash % 256 对约一半输入为负,会产生负色相;hash and 0xFF 对负值正确,因为它作用于补码位。相邻字节取出的两个色相可能过于接近而显得扁平,若要保证对比度,可由第一个色相加固定旋转得到第二个。
  • 文字测量必须在组合阶段完成,onDraw 每帧执行,在其中测量会每帧分配和布局。
  • 没有布局阶段,定位靠算术:需自行计算左上角并加回内边距。
  • Size.Unspecified 会让图片组合无法确定自身尺寸,因此必须报告 intrinsicSize。
  • 混用存储尺寸与绘制尺寸会导致错误:例如按存储尺寸绘制圆角矩形、却按 DrawScope.size 居中画圆,当调用方以非固有尺寸绘制时就会错位。

验证方法

技能给出若干 grep 检查:

  • 检查每个 AsyncImage( 调用点是否命名了三个槽位,用 -A16 展开上下文,-[0-9]+- 锚定上下文行。
  • 检查组合中构造但未被 remember 的 Painter。
  • 检查有符号哈希取余(负色相 bug),期望无输出。
  • 确定性本身无法用 grep 检查:需在同一会话内渲染同一列表两次并跨重启确认颜色一致,再渲染仅差一个字符的标题——忽略部分输入的生成器会通过第一项测试但失败于第二项。