用途
scrim 是位于图片与下方页面之间的一段渐变,使图像看起来溶解进背景。若按最直观的写法 Brush.verticalGradient(listOf(Color.Transparent, background)) 实现,会同时产生两个瑕疵:淡出开始处有一条可见的线,以及中段出现一条脏带。两者都可修复,且都不是靠增加 alpha 能解决的。
主要能力
该技能提供两个主要函数(代码保持原样):
// adapted (the colour lerp is imported unqualified here)
fun smoothScrimBrush(
from: Color,
to: Color,
startFraction: Float = 0f,
endFraction: Float = 1f,
startY: Float = 0f,
endY: Float = Float.POSITIVE_INFINITY,
steps: Int = 24,
): Brush =
Brush.verticalGradient(
colorStops =
Array(steps + 1) { i ->
val t = i / steps.toFloat()
val position = startFraction + (endFraction - startFraction) * t
position to lerp(from, to, t * t * (3f - 2f * t))
},
startY = startY,
endY = endY,
)
// the common case: ramp one colour from invisible to opaque
fun artworkScrimBrush(color: Color, steps: Int = 24): Brush =
smoothScrimBrush(from = color.copy(alpha = 0f), to = color, steps = steps)
关键陷阱与做法
- 线性斜坡有拐角,而拐角就是肉眼看到的边缘。人眼对亮度变化率的敏感度高于亮度本身,因此直线斜坡离开 0 和到达 1 处的不连续会被读成一条画出来的线。smoothstep,t²(3−2t),在 t=0 和 t=1 处斜率为零,两端都没有拐角。两端都要缓动:只缓动起点,只会把接缝移到 scrim 底部,而不是消除它。
Color.Transparent是 alpha 为 0 的黑色,因此带颜色的淡出会拖过黑色。应当把透明端改为yourColor.copy(alpha = 0f),这样 RGB 通道不会移动。- 自己插值颜色并交出多个色标。两个色标等于把整条曲线交给渲染器:你得到它的插值方式、颜色空间和预乘选择,而这些在不同后端间不同。预先插值色标固定了色标值本身,渲染器只处理它们之间的短线段;这也是把非线性曲线放进没有缓动参数的 brush 的唯一方法。
- 两个色标无法表达曲线。即使手上有 smoothstep,两端各一个色标的渐变还是一条直线;曲线只存在于你发出的色标中。足够多的色标能把 S 曲线变成分段线性近似,其折角低于可见阈值。
- 步数要通过观察确定,而不是照抄数字。色标太少,线段连接处会显示为淡带;太多则是着色器中的浪费。在你交付的最深背景上渲染 scrim(深色背景是 8 位量化影响最大的地方),将数量减半直到出现条带,然后退回一档。示例中的 steps = 24 是在该测试中存活下来的值,是起点而不是常量。
- 用分位而不是缩小盒子来限制斜坡。短的 scrim 就是陡的 scrim,而陡正是让淡出被读成边缘的原因。startFraction/endFraction(或已知像素时的 startY/endY)保持盒子高度,同时限制斜坡发生的位置。
- tile mode 只在三条路径中的一条上起作用。默认 endY = Float.POSITIVE_INFINITY 路径下,着色器用盒子自身高度替代无限坐标,斜坡正好覆盖盒子,渐变参数不会离开 [0,1],tile mode 不会被查询。startFraction/endFraction 路径通过把色标位置向内移动来限制斜坡,平坦端由首末色标保持,也不是 tile mode。只有盒子内部显式的 startY/endY 组合会留下斜坡之外的区域交给 tile mode 负责;改成 repeat 或 mirror 只影响这一条路径。
- 淡出到 scrim 后面真正存在的颜色。scrim 底部必须与页面背景完全相等,否则只是把接缝挪了位置。当背景由图片推导而来时,两者必须来自同一来源(参见兄弟技能 artwork-palette-theming,以及无图片项的回退 deterministic-title-placeholder-painter)。
使用方式与验证
在仓库根目录运行以下只读命令进行验证:
检查渐变调用内没有透明色标。任何命中要么是注释,要么是 bug,逐一阅读:
grep -rn -A4 --include='*.kt' -E "verticalGradient\(|horizontalGradient\(|linearGradient\(" . \
| grep -v '/build/' | grep "Color.Transparent"
列出代码库中所有渐变 brush,以便检查每一个要么已缓动、要么是有意线性(小瓦片上的双色装饰渐变不需要曲线;scrim 需要):
grep -rln --include='*.kt' -E "verticalGradient\(|horizontalGradient\(" . | grep -v '/build/'
视觉验证:把 scrim 放在平坦的中间色调填充上而不是图片上。图片会掩盖条带和接缝,平坦填充会立刻暴露两者。然后在最深背景上重复。
适用场景与限制
适用于 Kotlin/Jetpack Compose 中图片与页面背景之间的渐变遮罩。资料未提供收费、价格、平台支持范围或效果保证等信息。资料未提供该技能是否适用于其他 UI 框架或编程语言的说明。资料未提供官方文档链接或版本信息。