LaunchedEffect(listState) 和 snapshotFlow { listState.layoutInfo }中, 为什么LaunchedEffect用listState,而snapshotFlow用listState.layoutInfo?
一句话结论
- LaunchedEffect(listState) 监听的是:listState 这个对象本身
- snapshotFlow { listState.layoutInfo } 监听的是:listState 内部的 layoutInfo(滑动布局信息)
为什么要这样?因为它们的用途完全不一样!
1. 先看:LaunchedEffect (listState)
kotlin
LaunchedEffect(listState) {
// 只在 listState 对象第一次创建或被替换时执行一次
}
它监听的是:listState 这个 “状态实例” 本身
什么时候会变?
- 页面重建
- 状态被重新
remember - 组件重新创建
它不会因为你滑动列表而执行!滑动不会改变 listState 这个对象,只会改变它内部的值。
所以:
LaunchedEffect(listState) 不能监听滑动!
它只能做一次性初始化操作。
2. 再看:snapshotFlow {listState.layoutInfo}
kotlin
snapshotFlow { listState.layoutInfo }
.collect {
// 滑动一次,执行一次
}
它监听的是:listState 内部的 layoutInfo
layoutInfo 里面包含:
- 总条数
- 可见条目范围
- 最后可见位置
只要你手指滑动列表,layoutInfo 就会变!
所以:
snapshotFlow { listState.layoutInfo }专门用来监听滑动!
3. 为什么不能写成 LaunchedEffect (listState.layoutInfo)?
因为:
LaunchedEffect (key) 只会在 key 变化的那一瞬间执行一次
滑动列表时 layoutInfo 会持续变化,但 LaunchedEffect 不会持续响应。
也就是说:
LaunchedEffect 只能 “触发一次”
snapshotFlow 才能 “持续监听”
4. 最本质区别(必背)
表格
| 写法 | 监听目标 | 能否监听滑动 | 执行次数 |
|---|---|---|---|
| LaunchedEffect(listState) | listState 对象 | ❌ 不能 | 1 次 |
| snapshotFlow(layoutInfo) | 滑动布局信息 | ✅ 能 | 滑动 N 次 |
5. 为什么我们代码要这样写?
kotlin
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo }
.distinctUntilChanged()
.collect { ... }
}
原因:
- LaunchedEffect(listState) 保证:列表状态创建后,只启动一次监听,不重复创建流。
- snapshotFlow { listState.layoutInfo } 保证:滑动时持续收到最新布局信息,实现上拉加载。
6. 终极人话总结
- LaunchedEffect(listState) = 打开监听器(只开一次)
- snapshotFlow(listState.layoutInfo) = 监听滑动内容(持续接收)
完整代码学习
val listState = rememberLazyListState()
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo }
.distinctUntilChanged() // 关键:防止重复触发
.collect { layoutInfo ->
val totalItems = layoutInfo.totalItemsCount
val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
// 核心判断:只有 真正滑动到底部 才加载
val shouldLoad = !isRefreshing
&& !isLoadingMore
&& hasMoreData
&& totalItems > 0
&& lastVisibleIndex >= totalItems - 2
if (shouldLoad) {
isLoadingMore = true
currentPage++
// 模拟网络请求
delay(1200)
val start = dataList.size + 1
val newData = (start until start + pageSize).map {
DataBean(it, "加载更多 $it")
}
dataList = dataList + newData
// 最多加载5页
if (currentPage >= 5) hasMoreData = false
isLoadingMore = false
}
}
}