如何在 Jetpack Compose 导航期间保存 LazyColumn 的分页状态
Posted
技术标签:
【中文标题】如何在 Jetpack Compose 导航期间保存 LazyColumn 的分页状态【英文标题】:How to save paging state of LazyColumn during navigation in Jetpack Compose 【发布时间】:2021-12-12 18:30:59 【问题描述】:我正在使用 androidx.paging:paging-compose
(v1.0.0-alpha-14) 和 Jetpack Compose (v1.0.3),我有一个自定义的 PagingSource
负责从后端提取项目。
我也使用撰写导航组件。
问题是我不知道如何在通过 NavHostController 导航到不同屏幕和返回(滚动状态和缓存项目)之间保存寻呼机流的状态。
我试图通过rememberSaveable
保存状态,但它无法完成,因为它不是可以放入 Bundle 的东西。
是否有一个快速/简单的步骤来做到这一点?
我的示例代码:
@Composable
fun SampleScreen(
composeNavController: NavHostController? = null,
myPagingSource: PagingSource<Int, MyItem>,
)
val pager = remember // rememberSaveable doesn't seems to work here
Pager(
config = PagingConfig(
pageSize = 25,
),
initialKey = 0,
pagingSourceFactory = myPagingSource
)
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn()
itemsIndexed(items = lazyPagingItems) index, item ->
MyRowItem(item)
composeNavController?.navigate(...)
【问题讨论】:
【参考方案1】:我找到了解决办法!
@Composable
fun Sample(data: Flow<PagingData<Something>>):
val listState: LazyListState = rememberLazyListState()
val items: LazyPagingItems<Something> = data.collectAsLazyPagingItems()
when
items.itemCount == 0 -> LoadingScreen()
else ->
LazyColumn(state = listState, ...)
...
...
我刚刚发现使用Paging
时的问题。
导航时Paging
不会记住列表滚动位置的原因归结为引擎盖下发生的事情。
它看起来像这样:
-
已创建可与
LazyColumn
组合。
我们异步从寻呼机请求我们的列表数据。当前寻呼机列表项计数 = 0。
UI 绘制了一个包含 0 个项目的惰性列。
寻呼机响应数据,例如10 个项目,并重新组合 UI 以显示它们。
用户滚动例如一直向下并单击底部的项目,将它们导航到其他地方。
用户使用例如导航返回后退按钮。
哦哦。由于导航,我们与LazyColumn
的组合被重新组合。我们再次异步请求寻呼机数据。注意:寻呼机项目计数再次 = 0!
rememberLazyListState
被评估,它告诉 UI 用户一直向下滚动,所以它现在应该回到相同的偏移量,例如到第五项。
这是 UI 混乱的地方,因为 pager 有 0 个项目,所以lazyColumn 有 0 个项目。 UI 无法处理到第五个项目的滚动偏移量。滚动位置设置为仅从第 0 项开始显示,因为只有 0 项。
接下来会发生什么:
-
寻呼机响应有例如又是 10 个项目,导致另一个重组。
重组后,我们再次看到我们的列表,滚动位置从第 0 项开始。
要确认您的代码是否属于这种情况,请在 LazyColumn
调用上方添加一个简单的日志语句:
Log.w("TEST", "List state recompose. " +
"first_visible=$listState.firstVisibleItemIndex, " +
"offset=$listState.firstVisibleItemScrollOffset, " +
"amount items=$items.itemCount")
在导航返回时,您应该会看到一条日志行,说明完全相同的first_visible
和offset
,但使用amount items=0
。
紧随其后的行将显示first_visible
和offset
被重置为0
。
我的解决方案有效,因为它跳过使用listState
,直到寻呼机加载数据。
加载后,正确的值仍然驻留在listState
中,并且滚动位置正确恢复。
来源:https://issuetracker.google.com/issues/177245496
【讨论】:
谢谢!它对我有用【参考方案2】:将列表状态保存在视图模型中,并在导航回包含列表的屏幕时重新加载。您可以在视图模型中使用LazyListState
来保存状态并将其作为参数传递到您的可组合组件中。像这样的:
class MyViewModel: ViewModel()
var listState = LazyListState()
@Composable
fun MessageListHandler()
MessageList(
messages: viewmodel.messages,
listState = viewmode.listState
)
@Composable
fun MessageList(
messages: List<Message>,
listState: LazyListState)
LazyColumn(state = listState)
如果您不喜欢 Navigation Compose 给您带来的限制,您可以尝试使用 Jetmagic。它允许您在屏幕之间传递任何对象,甚至以一种更容易从任何可组合访问它们的方式管理您的视图模型:
https://github.com/JohannBlake/Jetmagic
【讨论】:
这不能与 PagingSource 一起使用。以上是关于如何在 Jetpack Compose 导航期间保存 LazyColumn 的分页状态的主要内容,如果未能解决你的问题,请参考以下文章
在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器