如何在 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_visibleoffset,但使用amount items=0。 紧随其后的行将显示first_visibleoffset 被重置为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 的分页状态的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 Jetpack Compose 中的导航?

jetpack compose 接收返回参数

在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器

Jetpack Compose中的导航路由

底部导航栏与 Jetpack Compose 中的屏幕内容重叠

后端内部错误:Jetpack compose 中 psi2ir 期间出现异常