You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中如何让视图子项支持屏幕阅读器可选访问

Android Talkback下轮播组件的无障碍优化方案

针对Talkback强制遍历轮播所有子项的问题,可以通过自定义无障碍操作+焦点控制实现用户自主选择的效果,具体步骤如下:

1. 给轮播标题添加自定义无障碍操作

将轮播标题设为可交互的无障碍节点,让Talkback读取标题后,弹出“浏览轮播内容”和“跳过轮播”两个选项:

代码实现(自定义标题View)

class CarouselTitleView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {

    // 自定义操作ID
    private val ACTION_BROWSE_CAROUSEL = 1
    private val ACTION_SKIP_CAROUSEL = 2

    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
        super.onInitializeAccessibilityNodeInfo(info)
        info ?: return

        // 标记当前节点为可交互焦点
        info.isClickable = true
        info.isFocusable = true

        // 添加“浏览轮播内容”操作
        val browseAction = AccessibilityNodeInfo.AccessibilityAction(
            ACTION_BROWSE_CAROUSEL,
            context.getString(R.string.accessibility_action_browse_carousel)
        )
        info.addAction(browseAction)

        // 添加“跳过轮播”操作
        val skipAction = AccessibilityNodeInfo.AccessibilityAction(
            ACTION_SKIP_CAROUSEL,
            context.getString(R.string.accessibility_action_skip_carousel)
        )
        info.addAction(skipAction)
    }

    // 对外暴露操作回调
    fun setOnAccessibilityActionListener(listener: (actionId: Int) -> Unit) {
        accessibilityDelegate = object : View.AccessibilityDelegate() {
            override fun performAccessibilityAction(host: View?, action: Int, arguments: Bundle?): Boolean {
                return when (action) {
                    ACTION_BROWSE_CAROUSEL, ACTION_SKIP_CAROUSEL -> {
                        listener.invoke(action)
                        true
                    }
                    else -> super.performAccessibilityAction(host, action, arguments)
                }
            }
        }
    }
}

2. 控制轮播容器的焦点遍历逻辑

默认屏蔽轮播子项的焦点遍历,仅当用户选择“浏览”时开放:

XML布局配置

<!-- 轮播容器 -->
<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/carousel_viewpager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:importantForAccessibility="no"
    android:descendantFocusability="blocksDescendants" />

处理操作逻辑

在页面代码中绑定回调,处理两种操作分支:

val carouselTitle = findViewById<CarouselTitleView>(R.id.carousel_title)
val carouselViewPager = findViewById<ViewPager2>(R.id.carousel_viewpager)
val nextTopElement = findViewById<View>(R.id.next_top_element) // 页面下一个顶级元素

carouselTitle.setOnAccessibilityActionListener { actionId ->
    when (actionId) {
        1 -> {
            // 浏览轮播:开放子项焦点,将焦点移至第一个轮播项
            carouselViewPager.apply {
                importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
                descendantFocusability = ViewGroup.DESCENDANT_FOCUS_AFTER_DESCENDANTS
            }
            carouselViewPager.getChildAt(0)?.requestFocus()
        }
        2 -> {
            // 跳过轮播:直接将焦点移至下一个顶级元素
            nextTopElement.requestFocus()
        }
    }
}

3. 额外优化点

  • 本地化适配:将自定义操作的描述字符串放入strings.xml,支持多语言;
  • 焦点自动回收:监听轮播项的焦点离开事件,自动恢复容器的焦点屏蔽状态;
  • 系统操作兼容:保留Talkback默认的双击激活等操作,避免冲突。

内容的提问来源于stack exchange,提问作者rohan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:52:43