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

Jetpack Compose字母索引滚动条显示数字而非首字母问题修复

问题原因
  • 核心问题是混淆了字符串资源ID和实际显示的字符串内容,所有和条目名称相关的逻辑都错误操作了整型资源ID,而非真实的名称字符串:
    1. 枚举类Items中定义的nameId是@StringRes标记的整型常量,是Android编译时生成的资源索引值(本质是整数),不是用户看到的条目名称字符串。
    2. 生成侧边索引的代码it.nameId.first().uppercase()直接对整型ID操作,本质是将整数转为十进制字符串后取首位字符,自然得到数字,而非条目名称的首字母。
    3. 列表排序逻辑sortedBy { it.nameId }是按资源ID的数字大小排序,不是按条目名称的字母序排序,即使修复首字母显示问题,索引和列表位置也会完全错位。
    4. 索引点击跳转的匹配逻辑同样基于资源ID判断,会导致跳转位置完全错误。
修复方法
  • 核心原则:所有和条目名称相关的排序、首字母提取、位置匹配逻辑,都必须基于stringResource()解析出的真实字符串操作,不能直接使用整型资源ID。
  • 具体调整步骤:
    1. 在Compose可组合上下文内,先将所有枚举项和对应的解析后真实名称、类型字符串绑定,避免后续反复操作资源ID。
    2. 基于真实的条目名称字符串做排序,保证列表顺序和字母索引顺序一致。
    3. 基于真实名称提取首字母生成索引集合,替换原有基于资源ID的生成逻辑。
    4. 调整索引跳转的匹配逻辑,同样基于真实名称的首字母做匹配。

修复后的核心实现代码:

enum class Items(@StringRes val nameId: Int, val typeId: Int) {
    Item1(R.string.apple, R.string.fruit),
    Item2(R.string.banana, R.string.fruit),
    Item3(R.string.cherry, R.string.fruit),
    Item4(R.string.date, R.string.fruit),
    Item5(R.string.eggplant, R.string.vegetable),
    Item6(R.string.fig, R.string.fruit),
    Item7(R.string.guava, R.string.fruit),
    Item8(R.string.jackfruit, R.string.fruit),
    Item9(R.string.kiwi, R.string.fruit),
    Item10(R.string.leek, R.string.vegetable),
    Item11(R.string.okra, R.string.vegetable),
    Item12(R.string.pumpkin, R.string.fruit),
    Item13(R.string.rhubarb, R.string.fruit),
    Item14(R.string.spinach, R.string.vegetable),
    Item15(R.string.turnip, R.string.vegetable),
    Item16(R.string.watermelon, R.string.fruit);
}

// 原始枚举列表直接取内置values即可,无需手动逐个添加
val originalItems = remember { Items.values() }
// 先解析所有条目的真实显示文本,再按名称排序
val items = originalItems.map { item ->
    Triple(
        first = item,
        second = stringResource(id = item.nameId),
        third = stringResource(id = item.typeId)
    )
}.sortedBy { it.second }
// 基于真实名称生成首字母索引
val headers = remember(items) { items.map { it.second.first().uppercase() }.toSet().toList() }

Row {
    val listState = rememberLazyListState()
    LazyColumn(
        state = listState,
        modifier = Modifier.weight(1f)
    ) {
        items(items) { itemWrapper ->
            Column {
                Text(text = itemWrapper.second)
                Text(text = itemWrapper.third)
            }
        }
    }
    val offsets = remember { mutableStateMapOf<Int, Float>() }
    var selectedHeaderIndex by remember { mutableStateOf(0) }
    val scope = rememberCoroutineScope()

    fun updateSelectedIndexIfNeeded(offset: Float) {
        val index = offsets
            .mapValues { abs(it.value - offset) }
            .entries
            .minByOrNull { it.value }
            ?.key ?: return
        if (selectedHeaderIndex == index) return
        selectedHeaderIndex = index
        // 基于真实名称首字母匹配跳转位置
        val selectedItemIndex = items.indexOfFirst { it.second.first().uppercase() == headers[selectedHeaderIndex] }
        scope.launch {
            listState.scrollToItem(selectedItemIndex)
        }
    }

    Column(
        verticalArrangement = Arrangement.SpaceEvenly,
        modifier = Modifier
            .fillMaxHeight()
            .background(Color.Green)
            .pointerInput(Unit) {
                detectTapGestures {
                    updateSelectedIndexIfNeeded(it.y)
                }
            }
            .pointerInput(Unit) {
                detectVerticalDragGestures { change, _ ->
                    updateSelectedIndexIfNeeded(change.position.y)
                }
            }
    ) {
        headers.forEachIndexed { i, header ->
            Text(
                header,
                modifier = Modifier.onGloballyPositioned {
                    offsets[i] = it.boundsInParent().center.y
                }
            )
        }
    }
}

问题运行效果参考:
运行效果截图


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 07:48:32