可访问性目标
- 支持 TalkBack 阅读与焦点导航。
- 满足动态字体尺寸、色彩对比度要求。
- 提供语义信息与操作提示。
语义描述
@Composable
fun AccessibleButton(onClick: () -> Unit, enabled: Boolean) {
Button(
onClick = onClick,
enabled = enabled,
modifier = Modifier.semantics {
contentDescription = "提交订单"
stateDescription = if (enabled) "可点击" else "不可用"
}
) {
Text("提交")
}
}
隐藏内容
Modifier.clearAndSetSemantics { }
- 用于视觉上展示但无语义的装饰元素。
焦点导航
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.focusRequester(focusRequester)
.focusProperties { next = FocusRequester.Default }
)
动态字体与布局
- 使用
sp与TextUnit.Unspecified。 - 通过
LocalDensity响应字体缩放。
val fontScale = LocalDensity.current.fontScale
val adaptivePadding = 16.dp * fontScale
Text(
text = title,
modifier = Modifier.padding(horizontal = adaptivePadding)
)
色彩对比
- Material 3 默认提供符合 WCAG 的调色板。
- 对自定义颜色使用
ContrastChecker工具。
MaterialTheme(colorScheme = dynamicColorScheme(context)) {
// composables
}
TalkBack 交互
| 场景 | 处理方式 |
|---|---|
| 分页内容 | PagerState + announce 当前页 |
| 动画提示 | 使用 Snackbar 或 announceForAccessibility |
| 手势操作提示 | 提供 contentDescription 说明 |
val context = LocalContext.current
LaunchedEffect(selectedTab) {
context.getSystemService(AccessibilityManager::class.java)
?.let { manager ->
if (manager.isEnabled) {
manager.interrupt()
manager.sendAccessibilityEvent(AccessibilityEvent.obtain().apply {
eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT
text.add("切换到第${selectedTab + 1}个标签")
})
}
}
}
辅助技术调试
- 使用 Android Studio 的
Accessibility Scanner。 - 开启 TalkBack,逐屏体验。
- 通过
uiautomatorviewer检查节点属性。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| TalkBack 报读错误 | 缺少语义描述或描述不准确 | 使用 semantics { contentDescription = ... } |
| 焦点丢失 | 动态视图未设置焦点顺序 | 使用 focusProperties、focusRequester |
| 对比度不足 | 自定义颜色亮度不足 | 调整色值或启用高对比模式 |
实战案例:无障碍是”被骂了才想起来”的事
说实话,我之前做 Flutter 项目时,无障碍(a11y)基本是 0 分——直到有用户反馈”TalkBack 读不出来按钮”。Compose 和 Flutter 在这块的 API 几乎一一对应:
- Compose 的
Modifier.semantics { contentDescription = "加入购物车" }≈ Flutter 的Semantics(label: "加入购物车") - Compose 的
Modifier.clickable自带语义 ≈ Flutter 里InkWell也自带,但自定义手势(GestureDetector)就要手动包Semantics - 图片装饰元素:Compose 用
contentDescription = null告诉读屏”跳过我”,Flutter 用ExcludeSemantics
最容易踩的三个坑(跨平台通用):
- 图标按钮没有 label:
IconButton(onClick = {}, icon = { Icon(Icons.Favorite) }),读屏只会读”按钮”,用户根本不知道干嘛的。必须加 contentDescription。 - 颜色传达信息:红色=错误、绿色=通过,色盲用户全军覆没。信息必须有文字或图标双通道。
- 触摸目标太小:Material 规范要求 48dp,Flutter/Compose 的
IconButton默认就是 48dp,但自定义小按钮经常缩水。
国内 App 的无障碍现状普遍不及格,但这是个”做了就赢”的赛道——应用宝、华为应用市场都开始有无障碍评分了。早做早省心。
参考:Android 官方文档《Accessibility in Compose》;Material 无障碍设计指南
总结
- Compose 提供语义树,合理设置
semantics可大幅提升无障碍体验。 - 关注字体缩放、对比度与焦点导航,确保用户在不同环境下都能顺畅操作。
- 将无障碍检查纳入测试流程,持续优化,使应用符合行业法规与用户期望。
