非文本对比度(Non-text Contrast)在移动端无障碍中指的是图标、按钮边界、输入框轮廓、进度条、选中框等非文字元素与相邻背景之间的颜色明暗差异。Android 上许多组件依赖边框或图形来传达可交互区域,一旦对比度低于标准,低视力用户或强光环境下的普通用户都可能无法辨识。Web 内容无障碍指南 WCAG 2.1 的 1.4.11 条款将这类元素的对比度门槛定为 3:1,而 Android 系统本身并未强制约束,因此需要开发团队自行检测和修正。

一、非文本对比度到底覆盖哪些Android控件
非文本对比度的核心对象是那些不依赖文字就能表达状态或边界的视觉元素。在 Android 界面中,最典型的是 <EditText> 的底边框或填充框轮廓。如果输入框边框使用 #D0D0D0 这样的浅灰色,放在白色背景上,实际对比度可能只有 1.3:1 左右,远低于 3:1。对视力正常的用户来说它只是“淡淡的线”,但对低视力用户而言这条线可能完全消失。类似的问题还出现在 <CheckBox> 的勾选框、<RadioButton> 的圆点、<Switch> 的轨道和滑块,以及 <ImageButton> 的图标上。
除了可交互控件,进度条轨道与已完成部分的对比、Tab 选中指示器与底部背景的对比、Chip 边框与容器背景的对比同样需要满足 3:1。需要注意的是,Material 主题下的 OutlinedButton 默认边框颜色往往偏浅,例如使用 ?attr/colorOutline 时在浅色主题中对比度经常不达标。阴影和 elevation 虽然能帮助用户感知边界,但 WCAG 的非文本对比度计算并不包含阴影,因此不能把阴影作为唯一的边界指示手段。
另一个容易忽略的场景是图标本身。图标与背景之间需要 3:1 对比,但图标内部不同形状之间如果只是装饰性细节,则不受严格约束。如果一个图标由多个路径组成,相邻路径之间的对比不足不会导致功能失效,但图标整体轮廓与背景的对比必须达标。这一区分可以避免设计团队对所有图形元素都采用过高对比度的色彩,导致视觉噪音增加。
二、用Kotlin计算对比度并判断是否达标
对比度的计算依赖相对亮度(Relative Luminance)。每一步都要先把 sRGB 颜色通道做线性化处理:当通道值除以 255 后小于等于 0.03928 时,用原值除以 12.92;否则对通道值做一次 2.4 次幂的 gamma 展开。之后再按照 0.2126、0.7152、0.0722 的权重计算亮度。最后对比度等于较亮颜色的亮度加 0.05 后除以较暗颜色的亮度加 0.05。这个公式与文本对比度完全一致,区别只是阈值:文本要求 4.5:1,而非文本组件要求 3:1。
下面给出一个基于 android.graphics.Color 的 Kotlin 实现。代码中把颜色解析、线性化、亮度计算和对比度判断都拆成了独立函数,方便直接在工具类或单元测试中调用。
import android.graphics.Color
import kotlin.math.pow
fun calculateRelativeLuminance(color: Int): Double {
val red = Color.red(color) / 255.0
val green = Color.green(color) / 255.0
val blue = Color.blue(color) / 255.0
fun linearize(channel: Double): Double {
return if (channel <= 0.03928) {
channel / 12.92
} else {
((channel + 0.055) / 1.055).pow(2.4)
}
}
val r = linearize(red)
val g = linearize(green)
val b = linearize(blue)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
fun getContrastRatio(foreground: Int, background: Int): Double {
val lum1 = calculateRelativeLuminance(foreground)
val lum2 = calculateRelativeLuminance(background)
val lighter = maxOf(lum1, lum2)
val darker = minOf(lum1, lum2)
return (lighter + 0.05) / (darker + 0.05)
}
fun isNonTextContrastValid(componentColor: Int, adjacentColor: Int): Boolean {
return getContrastRatio(componentColor, adjacentColor) >= 3.0
}
实际使用时,可以把控件的边框颜色和相邻背景颜色分别传给 isNonTextContrastValid。比如检查一个输入框边框 #BDBDBD 与白色背景 #FFFFFF 的对比度,函数会返回 false。这时如果把边框加深到 #767676,对比度会提升到 4.0:1 左右,才算稳妥。对于图标,可以取图标主体颜色和背景色进行判断;如果图标有多个颜色,应以最浅且承担主要识别功能的颜色为准。
写单元测试时也可以直接用这套函数做 CI 拦截。例如把设计规范中定义的所有状态颜色对做成列表,循环断言对比度大于等于 3.0。这样一旦有人在主题中改动了边框色或背景色,测试会立即失败,比人工走查更可靠。
三、工具检测与设计阶段的落地建议
除了自己写检测函数,Android 官方提供的 Accessibility Scanner 是更快捷的走查工具。安装后打开应用,在任何页面上启动扫描,它会自动标记文本对比度不足、触摸目标太小以及非文本对比度不足的区域。扫描结果会以橙色框覆盖在界面上,点击可以查看具体颜色和对比度值。该工具依赖无障碍服务,扫描逻辑本质上也是计算相邻像素的颜色差异,因此检测结果可以作为开发阶段的重要参考,但不能完全替代真实设备上的多亮度验证。
在设计阶段有一些可以提前规避的规则。第一,不要只用颜色区分状态,比如选中和未选中的 Tab 如果只有文字颜色变化,色弱用户可能无法分辨;应同时改变图标、下划线粗细或背景填充。第二,边框颜色避免使用低饱和灰色,尤其在浅色背景下。Material 3 中 colorOutline 的默认值在浅色主题下对比度往往不足,建议覆盖为更深的 colorOnSurfaceVariant 或自定义值。第三,为禁用态单独设定处理方式。虽然 WCAG 1.4.11 对禁用控件有豁免,但 Android 应用通常会降低整个控件透明度,这会让边框和图标同时变淡,导致用户无法判断该区域是否可交互。更好的做法是保留禁用态的文字或图标轮廓清晰度,只降低背景填充或其他辅助元素。
测试时应该在最低亮度、最高亮度和开启夜间模式三种条件下观察。同一组颜色在 OLED 屏幕最低亮度下可能因亮度压缩而更接近,导致感知对比度下降。如果有条件,可以使用色彩分析仪测量实际屏幕亮度,再计算对比度,而不是完全依赖设计稿中的 hex 值。对关键操作路径,如登录按钮、支付确认框、导航 Tab,建议单独记录每对颜色及其对比度数值,确保在后续改版中不被无意破坏。
把这些检测方法和设计约束纳入组件库的开发流程后,非文本对比度就不再是上线前才想起的补救项。一个按钮的边框、一个图标的描边,这些看似微小的视觉元素,实际上决定了产品能否被更多用户顺畅使用。
Android无障碍非文本对比度颜色对比度计算修改时间:2026-10-02 01:04:30