如何使屏幕阅读器可以访问 ClickableText

How to make ClickableText accessible to screen readers

此代码在 Jetpack Compose Composable 中创建一个 ClickableText 元素:

ClickableText(
  text = forgotPasswordAnnotatedString,
  onClick = {
  context.startActivity(intent)
},
  modifier = Modifier
    .padding(top = mediumPadding)
)

这里定义了带注释的字符串,使文字看起来像link:

val forgotPasswordAnnotatedString = buildAnnotatedString {
    append(stringResource(R.string.forgot_password))
    addStyle(
        style = SpanStyle(
            textDecoration = TextDecoration.Underline,
            color = Color.White,
            fontSize = 16.sp,
            fontWeight = FontWeight.Medium
        ),
        start = 0,
        end = 21,
    )
}

当我在 Android 中使用 TalkBalk 屏幕 reader 遇到此文本时,屏幕 reader 没有明确表示这是可点击的文本,点击后会执行某些操作. reader 只是阅读文本。

有没有办法让屏幕清楚 reader 此文本是交互式的?否则我应该只使用一个按钮并将其设置为看起来像 link?

将 .semantics.contentDescription 添加到修改器会更改屏幕上读取的内容 reader。我不得不使用 contentDescription 来明确表示这是 link 来重置您的密码。

屏幕 reader 仍然无法识别可点击的元素,但希望描述有助于向用户传达该元素是交互式的。

ClickableText(
      text = forgotPasswordAnnotatedString,
      onClick = {
          context.startActivity(intent)
      },
      modifier = Modifier
          .padding(top = mediumPadding)
          // new code here:
          .semantics { 
              contentDescription = "Forgot your password? link"
          }
  )

看起来你的意图是让整个文本都可以点击?您最好的选择可能是 TextButton,如建议的那样 加布里埃尔·马里奥蒂。

但是,如果您不仅希望 link 的一部分可点击,或有多个可点击部分,我能找到的最好方法是在文本。这意味着我可以将可点击区域的触摸目标控制在至少 48.dp 并且可以使用 semantics{} 修饰符来控制屏幕 reader 解释它的方式。

欢迎任何建议。

// remember variables to hold the start and end position of the clickable text
val startX = remember { mutableStateOf(0f) }
val endX = remember { mutableStateOf(0f) }
// convert to Dp and work out width of button
val buttonPaddingX = with(LocalDensity.current) { startX.value.toDp() }
val buttonWidth = with(LocalDensity.current) { (endX.value - startX.value).toDp() }

Text(
    text = forgotPasswordAnnotatedString,
    onTextLayout = {
        startX.value = it.getBoundingBox(0).left // where 0 is the start index of the range you want to be clickable
        endX.value = it.getBoundingBox(21 - 1).right // where 21 is the end index of the range you want to be clickable
    }
)

请注意,buttonPaddingX 是相对于文本位置而不是 Window,因此您可能必须将两者包围在 Box{} 中或使用 ConstraintLayout。

然后绘制隐形框

Box(modifier = Modifier
    .sizeIn(minWidth = 48.dp, minHeight = 48.dp) // minimum touch target size
    .padding(start = buttonPaddingX)
    .width(buttonWidth)
    //  .background(Color.Magenta.copy(alpha = 0.5f)) // uncomment this to debug where the box is drawn
    .clickable(onClick = { context.startActivity(intent) })
    .semantics {
        // tell TalkBack whatever you need to here
        role = Role.Button
        contentDescription = "Insert button description here"
    }
)

在我的代码中,我使用 pushStringAnnotation(TAG, annotation) 而不是直接引用字符串索引。这样我就可以用 annotatedString.getStringAnnotations(TAG,0,annotatedString.length).first() 获取可点击区域的开始和结束索引。如果文本中有多个 link 则很有用。

令人失望的是,ClickableText 从一开始就没有考虑到可访问性,希望我们能够在未来的更新中再次使用它。