我正在 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
。
问题似乎出在这里:
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
_games[section]?.addAll(data)
但是这似乎不被视为地图中的更新。像这样完全设置一个新列表。-
_games[section] = _games[section]?.plus(data) ?: listOf()
成功了。