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

Jetpack Compose无限滚动问题:derivedStateOf仅首次加载重组

解决Jetpack Compose LazyRow无限滚动中lastIndexToShow的异常问题

问题根源分析

你的lastIndexToShow工作异常主要有3个核心原因:

  1. State解包错误:传递lastIndexToShow时将State<Int>直接解包为普通Int,导致Compose无法感知该值的更新,LazyRow不会自动刷新列表长度。
  2. 列表不可观察:_games中存储的是普通MutableList,而非可观察的MutableStateList,当列表内容新增时,derivedStateOf无法捕获变化,不会重新计算lastIndexToShow。
  3. 依赖项非State类型:如果totalGamesGroupBy不是State类型,derivedStateOf也无法在其变化时触发重新计算。

分步解决方案

1. 修正ViewModel中的游戏列表类型

将_games中的普通MutableList替换为MutableStateList,确保列表内容变化能被Compose感知:

private var _games = mutableStateMapOf<String, MutableStateList<Game>>()
val games: SnapshotStateMap<String, MutableStateList<Game>> = _games

// 初始化某分类的游戏列表时使用mutableStateListOf
fun initSectionGames(section: String) {
    _games[section] = mutableStateListOf()
}

2. 修正lastIndexToShow的传递与使用

在Composable中直接观察State<Int>,不要提前解包,确保LazyRow能感知其变化:

// 在Composable中获取State
val lastIndexToShow by gameViewModel.lastIndexToShow(section)

LazyRow(
    contentPadding = PaddingValues(start = 10.dp, end = 10.dp, bottom = 10.dp),
    horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
    items(
        count = lastIndexToShow, // 直接使用观察到的State值
        key = { index ->
            games[section]?.getOrNull(index)?.id ?: index
        }
    ) { index ->
        Box {
            games[section]?.getOrNull(index)?.let { game ->
                GameCard(game = game, index = index)
            } ?: 
            Box(modifier = Modifier
                .size(width = 400.dp, height = 350.dp)
                .padding(0.dp))
        }

        // 修正LaunchedEffect的key,避免不必要的重复触发
        LaunchedEffect(index, section) {
            fetchNewPageIfNeeded(section, index)
        }
    }
}

3. 确保totalGamesGroupBy是State类型

如果totalGamesGroupBy尚未使用State封装,需要修改为可观察的State:

private val _totalGamesGroupBy = mutableStateOf<Map<String, Int>?>(null)
val totalGamesGroupBy: State<Map<String, Int>?> = _totalGamesGroupBy

// 更新totalGamesGroupBy时使用value赋值
fun updateTotalGamesGroupBy(newMap: Map<String, Int>) {
    _totalGamesGroupBy.value = newMap
}

4. 验证derivedStateOf的计算逻辑

确保lastIndexToShow的计算逻辑符合预期,比如阈值threshold的取值合理(建议设为单次加载数量的1.5-2倍,比如你每次加载30条,阈值设为45-60):

val lastIndexToShow: (String) -> State<Int> = { section ->
    derivedStateOf {
        val totalGames = totalGamesGroupBy.value?.get(section) ?: 0
        val currentLoaded = games[section]?.size ?: 0
        // 计算要显示的最后索引:不超过总游戏数-1,同时保证有足够的占位项
        maxOf(0, minOf(totalGames - 1, currentLoaded + threshold))
    }
}

额外优化建议

  • 加载状态处理:在占位Box中添加加载动画,提升用户体验。
  • 避免重复加载:在fetchNewPageIfNeeded中加入加载状态判断,防止同一页面被多次请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:35:07