Jetpack Compose如何检测滚动列中与箭头同Y轴的可组合项
功能需求
需要实现宝可梦进化链视图中,箭头上方的关联图片随箭头当前指向的宝可梦条目动态切换,视图本身支持双向滚动:水平滚动查看下一进化阶段,垂直滚动查看同阶段的进化变种。
具体交互规则:
- 初始状态箭头默认指向 Vaporeon
- 用户垂直滚动列表到Jolteon(黄色条目)位置时,箭头开始指向Jolteon的瞬间,上方图片立刻切换为Jolteon对应的关联图片
- 该图片保持显示,直到箭头指向Jolteon之外的其他条目
- 箭头指向新条目时,重复上述切换逻辑更新对应图片
现有基础代码
@Composable private fun EvolutionChainContent( parentPokemonDetails: PokemonDetails, pokemonEvolutionSteps: List<EvolutionStep>, navigateToPokemonDetails: (String) -> Unit ) { val commonScrollState = rememberScrollState() Row( modifier = Modifier .horizontalScroll(rememberScrollState()), verticalAlignment = Alignment.Bottom ) { pokemonEvolutionSteps.forEach { evolutionStep -> val columnScrollState = if (evolutionStep.pokemonDetailList.hasOneItem()) rememberScrollState() else commonScrollState if (pokemonEvolutionSteps.isNotFirstElement(evolutionStep)) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom, modifier = Modifier .padding(bottom = 115.dp) ) { // TODO 替换为箭头当前指向的宝可梦对应内容 EvolutionRuleView(evolutionStep.pokemonEvolutionDetails.first()) Spacer(modifier = Modifier.height(5.dp)) ArrowToNextEvolution(parentPokemonDetails.types.first().getTypeColor()) } } Column( modifier = Modifier .height(250.dp) .drawVerticalScrollbar(columnScrollState) .verticalScroll(columnScrollState) ) { evolutionStep.pokemonDetailList.forEachIndexed { index, it -> PokemonItem( pokemonDetails = it, modifier = Modifier .width(155.dp) .height(230.dp), onClick = if (parentPokemonDetails.name != it.name) { { navigateToPokemonDetails(it.name) } } else null ) if (evolutionStep.pokemonDetailList.isLastElement(it)) Spacer(modifier = Modifier.height(10.dp)) } } } } }
已尝试方案与问题
- 使用
onGlobalPosition()方法实现,无法精准完成Y轴位置的判断控制 - 尝试用
LazyColumn替换现有Column实现,因根布局已存在垂直滚动逻辑引发报错
实现方案
不需要替换现有布局组件,也不需要复杂的全局位置监听,直接基于现有固定布局尺寸和ScrollState做计算即可,精度可达像素级:
- 先确定固定参考值:右侧宝可梦列表容器高度固定为250dp,箭头所在Column设置了115dp的底部内边距,和列表底部对齐,因此箭头相对于列表容器的Y轴固定锚点偏移为135dp;每个宝可梦条目固定高度230dp,所有尺寸都是硬编码固定值,不需要动态测量。
- 基于列表的滚动状态实时计算当前箭头指向的条目索引,代码如下:
// 在evolutionStep循环内,columnScrollState定义之后添加 val density = LocalDensity.current // 预计算固定尺寸对应的像素值 val arrowAnchorPx = with(density) { 135.dp.roundToPx() } val itemHeightPx = with(density) { 230.dp.roundToPx() } // 监听滚动状态,自动计算当前指向的条目索引 val currentTargetIndex by remember { derivedStateOf { val currentScroll = columnScrollState.value val hitPosition = currentScroll + arrowAnchorPx (hitPosition / itemHeightPx) .coerceIn(0, evolutionStep.pokemonDetailList.lastIndex) } } // 索引变化时更新箭头上方的图片/进化规则 val currentTargetPokemon = evolutionStep.pokemonDetailList[currentTargetIndex] // 将currentTargetPokemon传入EvolutionRuleView替换原来的固定取值即可
注意:多进化阶段共用
commonScrollState的逻辑无需修改,每个阶段的列会基于同一个滚动值独立计算指向的条目,不会出现错位;该方案无额外布局测量开销,滚动时不会出现性能问题,切换时机完全和箭头指向同步。
内容的提问来源于stack exchange,提问作者Arkadiusz Mądry
相关产品推荐
相关产品推荐

