Jetpack Compose 无限滚动。 generatedStateOf 仅在第一次加载时重新组合

问题描述 投票:0回答:2

我正在 Jetpack Compose 中创建无限滚动。鉴于

LazyRow

LazyRow(
    contentPadding = PaddingValues(start = 10.dp, end = 10.dp, bottom = 10.dp),
    horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
    items (
        lastIndexToShow,
            key = { index ->
                games.getOrNull(index)?.id ?: index
            }
        ) { index ->
            Box {
                games.getOrNull(index)?.let { game ->
                    GameCard(game = game, index = index)
                } ?:
                Box(modifier = Modifier
                    .size(width = 400.dp, height = 350.dp)
                    .padding(0.dp))
            }

            LaunchedEffect(Unit) {
                fetchNewPageIfNeeded(section, index)
            }
        }
    }
}

games
lastIndexToShow
都来自我的
ViewModel
lastIndexToShow
的想法是允许在快速滚动的情况下在加载时显示空白空间,以便在加载之前到达游戏列表末尾时滚动永远不会停止。这正是无法正常工作的变量。

我是这样定义它们的:

游戏

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

最后索引显示

val lastIndexToShow: (String) -> State<Int> = { section ->
    derivedStateOf {
        maxOf(0, minOf((totalGamesGroupBy?.get(section) ?: 1) - 1, (_games[section] ?: emptyList()).size + threshold))
    }
}

然后我就这样通过

lastIndexToShow
。-

lastIndexToShow = { section ->
    val indexToShow by gameViewModel.lastIndexToShow(section)
    indexToShow 
}

如果我不为这个变量设置

1000
来显示最后一个索引(我正在获取 30 个元素页面),我可以看到该行顺利地显示新元素,所以我很确定问题出在定义上
lastIndexToShow

android kotlin android-jetpack-compose
2个回答
1
投票

问题似乎出在这里:

private val _games = mutableStateMapOf<String, MutableList<Game>>()

mutableStateMapOf
创建一个地图,将观察该地图的变化以触发重组。但这只是地图的一个功能。添加、删除或替换项目将触发重组,但不会观察到地图内部对象的更改。

地图的值是

MutableList
s。您可以向该列表添加元素,但这不会对地图进行任何更改:地图仍将包含 same 列表。只有列表的内容发生了变化,列表本身没有变化:

_games[section]?.addAll(data)

这只会读取地图的值。然后它获取该值(一个 MutableList)并向其添加

data
。因此映射保持不变:没有新的、删除的或替换的值(即新的或删除的 MutableList),只有已经存在的 MutableList 被修改。

由于 Compose 只会监控地图本身的结构变化,因此上述不会触发重组。

这就是一般可变对象(不仅仅是 MutableLists)的棘手之处,当它们发生变化时很难跟踪。这也是常见建议在这种情况下使用immutable对象的原因之一:

private val _games = mutableStateMapOf<String, List<Game>>()

地图仍然是一个可观察的状态,但现在它只允许

List
类型的值。这与 MutableList 相同,只是没有添加、删除或替换元素的方法:该列表是“不可变的”,并且在创建后就无法再更改。添加 data 现在可以像这样完成:
_games[section] = _games[section]?.plus(data)

plus

创建一个新的(不可变)列表,其中包含

_games[section]
加上
data 的条目。然后,这个
new
列表将保存在地图中。 that 最终将被注册为对地图的更改,以便安排重组。 乍一看,总是创建新列表似乎效率低下。这在某种程度上是正确的,但一般来说,这不会引人注目,因为只要您没有数百万个条目并在循环中重复它,创建列表就相当轻量。相反,您将获得其他优势,例如提高线程安全性和更好的封装以防止其他类型的错误。作为一般规则,尝试使用尽可能多的不可变数据结构。

仍然缺少一件事:当

_games

映射没有

section
键的值时的空处理。在原始版本中,数据只是被跳过。我猜你真正想要的是创建一个
new
条目: _games[section] = (_games[section] ?: emptyList()) + data



0
投票

_games[section]?.addAll(data)

但是这似乎不被视为地图中的更新。像这样完全设置一个新列表。-

_games[section] = _games[section]?.plus(data) ?: listOf()

成功了。

© www.soinside.com 2019 - 2024. All rights reserved.