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

Compose HorizontalPager无障碍焦点异常问题求助

Compose HorizontalPager 无障碍焦点异常问题修复

问题根源分析

你的代码存在三个核心问题导致焦点混乱:

  • 所有卡片共用同一个FocusRequester:页面切换后,焦点请求的目标不明确,系统无法确定要聚焦哪张卡片
  • Surface绑定了同一个FocusRequester:这会和卡片的焦点请求产生冲突,导致焦点被意外夺走
  • 未正确监听页面切换事件:仅用LaunchedEffect(Unit)只执行一次,无法响应后续的页面切换动作

修复方案

1. 为每个卡片分配独立的FocusRequester

为每张卡片创建专属的FocusRequester,确保焦点请求的目标唯一明确。

2. 监听Pager页面切换事件,触发焦点请求

通过LaunchedEffect监听PagerState.currentPage的变化,在页面切换动画完成后,请求当前页卡片的焦点。

3. 移除Surface上的焦点相关修饰符

Surface不需要焦点,移除其focusRequester和focusable,避免干扰卡片焦点。

4. 补充卡片的无障碍属性

添加contentDescription帮助屏幕阅读器识别卡片内容,提升无障碍体验。

修改后的完整代码

// 1. 开启无障碍套件中的TalkBack(Google Play商店可获取)
// 2. 启动应用
// 3. 滑动/点击NEXT按钮切换轮播卡片
// 4. 验证每次切换后,无障碍焦点是否正常落在当前卡片上

// 修复后行为:每次滑动/切换后,无障碍焦点都会自动聚焦到当前显示的卡片
// 预期行为:每次左右滑动后,无障碍焦点都应落在对应卡片上

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TalkBackHorizontalPagerExperimentsTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background,
                ) {
                    val listOfCards = listOf(
                        Color.Green,
                        Color.Black,
                        Color.Red,
                        Color.Yellow,
                        Color.Blue
                    )

                    BasicPager(listOfCards = listOfCards) { index ->
                        SomeCard(color = listOfCards[index], index = index)
                    }
                }
            }
        }
    }
}

@Composable
fun SomeCard(color: Color, index: Int) {
    val requester = remember { FocusRequester() }

    Card(
        shape = RoundedCornerShape(10.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 10.dp),
        modifier = Modifier
            .fillMaxWidth()
            .height(200.dp)
            .border(width = 4.dp, color = Color.Black)
            .clipToBounds()
            .focusRequester(requester)
            .focusable()
            .semantics {
                contentDescription = "第${index + 1}张卡片"
            }
    ) {
        Row(
            Modifier
                .fillMaxSize()
                .background(color = color)
        ) {}
    }

    // 对外暴露当前卡片的FocusRequester
    CompositionLocalProvider(LocalCardFocusRequester provides requester)
}

// 创建CompositionLocal用于传递卡片的FocusRequester
private val LocalCardFocusRequester = staticCompositionLocalOf<FocusRequester> {
    error("No FocusRequester provided!")
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BasicPager(
    listOfCards: List<Color>,
    itemContent: @Composable (index: Int) -> Unit,
) {
    val state = rememberPagerState { listOfCards.size }
    val coroutineScope = rememberCoroutineScope()

    HorizontalPager(state = state) { cardIndex ->
        itemContent(cardIndex)
    }

    // 监听页面切换,请求当前页卡片的焦点
    LaunchedEffect(state.currentPage) {
        // 等待页面切换动画完成后再请求焦点
        state.waitForPageIdle()
        // 获取当前页卡片的FocusRequester并请求焦点
        val currentRequester = LocalCardFocusRequester.current
        currentRequester.requestFocus()
    }

    Box(
        Modifier
            .width(25.dp)
            .height(25.dp)
    ) {
        Button(
            onClick = {
                val nextPage = (state.currentPage + 1) % listOfCards.size
                coroutineScope.launch {
                    state.animateScrollToPage(nextPage)
                }
            },
        ) {
            Text("NEXT")
        }
    }
}

关键修复点说明

  • 独立FocusRequester:每张卡片通过remember创建自己的FocusRequester,确保焦点请求不会混淆
  • 页面切换监听:用LaunchedEffect(state.currentPage)监听页面变化,每次页面切换都会触发焦点请求
  • 等待页面空闲:调用state.waitForPageIdle()确保页面切换动画完全结束后再请求焦点,避免因组件未完全渲染导致焦点请求失败
  • 移除Surface焦点修饰符:避免Surface和卡片争夺焦点
  • 无障碍语义补充:添加contentDescription让屏幕阅读器用户清楚知道当前聚焦的内容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 16:15:35