【问题标题】:Android jetpack compose pagination : Pagination not working with staggered layout jetpack composeAndroid jetpack compose 分页:分页不适用于交错布局 jetpack compose
【发布时间】:2022-08-23 14:21:35
【问题描述】:

android (https://developer.android.com/topic/libraries/architecture/paging/v3-overview) 给出的分页与 Column,Row,lazy column,lazy rows 一起工作得很好。当我尝试在交错布局中实现分页时会出现问题(答案How to achieve a staggered grid layout using Jetpack compose? 非常有帮助)。

问题陈述是当我滚动到列表底部时没有进一步的网络调用。根据文档,没有方法可以对下一个项目进行分页调用,只要我们将输入列表设置为 itemList.collectAsLazyPagingItems() 并将其传递给lazycolumn/lazyrow,它就会自动执行。但对于上述交错布局,它不会自动发生。

我正在测试的一种解决方案是手动观察可见项目的索引,以及它们是否接近列表末尾并手动调用网络请求。 (请参阅此代码实验室的起始代码 (https://developer.android.com/codelabs/android-paging#0)

交错布局的本质是在内部创建和使用多个 COLUMNS 并将项目分配给它们的列。这里的挑战是我们如何知道我们正在接近列表的末尾。

交错布局的代码是这样的(我不完全理解这是如何工作的)

@Composable
private fun CustomStaggeredVerticalGrid(
  // on below line we are specifying
  // parameters as modifier, num of columns
    modifier: Modifier = Modifier,
    numColumns: Int = 2,
    content: @Composable () -> Unit
) {
// inside this grid we are creating
// a layout on below line.
Layout(
    // on below line we are specifying
    // content for our layout.
    content = content,
    // on below line we are adding modifier.
    modifier = modifier
) { measurable, constraints ->
    // on below line we are creating a variable for our column width.
    val columnWidth = (constraints.maxWidth / numColumns)

    // on the below line we are creating and initializing our items 
    constraint widget.
    val itemConstraints = constraints.copy(maxWidth = columnWidth)

    // on below line we are creating and initializing our column height
    val columnHeights = IntArray(numColumns) { 0 }

    // on below line we are creating and initializing placebles
    val placeables = measurable.map { measurable ->
        // inside placeble we are creating
        // variables as column and placebles.
        val column = testColumn(columnHeights)
        val placeable = measurable.measure(itemConstraints)

        // on below line we are increasing our column height/
        columnHeights[column] += placeable.height
        placeable
    }

    // on below line we are creating a variable for
    // our height and specifying height for it.
    val height =
        columnHeights.maxOrNull()?.coerceIn(constraints.minHeight, 
    constraints.maxHeight)
            ?: constraints.minHeight

    // on below line we are specifying height and width for our layout.
    layout(
        width = constraints.maxWidth,
        height = height
    ) {
        // on below line we are creating a variable for column y pointer.
        val columnYPointers = IntArray(numColumns) { 0 }

        // on below line we are setting x and y for each placeable item
        placeables.forEach { placeable ->
            // on below line we are calling test
            // column method to get our column index
            val column = testColumn(columnYPointers)

            placeable.place(
                x = columnWidth * column,
                y = columnYPointers[column]
            )

            // on below line we are setting
            // column y pointer and incrementing it.
            columnYPointers[column] += placeable.height
        }
    }
}

}

调用上面的代码如下

Column(
    // for this column we are adding a
    // modifier to it to fill max size.
    modifier = Modifier
        .fillMaxSize()
        .verticalScroll(rememberScrollState())
        .then(layoutModifier)
) {
    // on below line we are creating a column
    // for each item of our staggered grid.
    CustomStaggeredVerticalGrid(
        // on below line we are specifying
        // number of columns for our grid view.
        numColumns = numColumns,
    ) {
        // inside staggered grid view we are
        // adding images for each item of grid.
        itemList.forEachIndexed { index,  singleItem ->
            // on below line inside our grid
            // item we are adding card.
            SomesingleItemCompose(singleItem , singleItemModifier ,index) // this one single grid item Ui as per requirement
        }
    }
}

    标签: android pagination android-jetpack-compose


    【解决方案1】:

    正如我上面所说,我正在测试以组合交错布局加载分页数据。它工作。诀窍是使用和调整一点“advance-pagiantion-start”codelab 代码(手动分页数据处理)并将其移动到 compose。 (目前还没有办法使用分页库)

    解决方案:https://github.com/rishikumr/stackoverflow_code_sharing/tree/main/staggered-layout-compose-with_manual_pagination

    工作视频:https://drive.google.com/file/d/1IsKy0wzbyqI3dme3x7rzrZ6uHZZE9jrL/view?usp=sharing

    它是如何工作的 :

    1. 发出网络请求,将其提供给 UI 自定义交错布局 (How to achieve a staggered grid layout using Jetpack compose?)

    2. 收听手动滚动并在滚动到末尾时发出网络请求(用于下一页)。 (https://github.com/rishikumr/stackoverflow_code_sharing/blob/main/staggered-layout-compose-with_manual_pagination/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt)

      val itemList = remember { mutableStateListOf<Repo>() }
           var lastListSize by remember { mutableStateOf(0) }
      
           LaunchedEffect(Unit) {
               viewModel.fetchContent()
                   .collect { result ->
                       when (result) {
                           is RepoSearchResult.Success -> {
                               Log.d("GithubRepository", "result.data ${result.data.size}")
                               itemList.clear()
                               itemList.addAll(result.data)
                           }
                           is RepoSearchResult.Error -> {
                               Toast.makeText(
                                   this@SearchRepositoriesActivity,
                                   "\uD83D\uDE28 Wooops $result.message}",
                                   Toast.LENGTH_LONG
                               ).show()
                           }
                       }
                   }
           }
      
           val scrollState = rememberScrollState()
           val endReached = remember {
               derivedStateOf {
                   (scrollState.value == scrollState.maxValue) && (lastListSize != itemList.size) && (scrollState.isScrollInProgress)
               }
           }
      
           Column(Modifier.verticalScroll(scrollState), horizontalAlignment = Alignment.CenterHorizontally) {
               Box(modifier = Modifier.size(100.dp)) {
                   Text("Other Top composable")
               }
               StaggeredVerticalScreen(
                   itemList = itemList,
                   numColumns = 2,
                   layoutModifier = Modifier.padding(
                       start = 12.dp,
                       bottom = 12.dp
                   ),
                   singleItemModifier = Modifier.padding(
                       end = 12.dp,
                       top = 12.dp
                   )
               ) { singleGridItem, singleItemModifier, index ->
                   SingleArticleItem(singleGridItem  , index)
               }
      
               if (endReached.value) {
                   lastListSize = itemList.size
                   Log.d("SearchRepositoriesActivity", "End of scroll lazyItems.itemCount=${itemList.size}")
                   viewModel.accept(UiAction.FetchMore)
               }
           }
      
    3. 你必须维护当前的页码和其他一些东西。 (https://github.com/rishikumr/stackoverflow_code_sharing/blob/main/staggered-layout-compose-with_manual_pagination/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt)

      A. 浏览代码实验室 Advance-pagination-start-code,您将了解它是如何工作的。 (我删除了文本更改 api 调用的部分代码,因为我不需要它们) B. 目前仅在内存中缓存,我正在研究 Room 数据库存储。我相信这应该不难。

      如果您必须使用分页库,那么我们需要在我们的 compose 中嵌入视图

      选项 2:另一种选择是使用 'androidx.compose.ui:ui-viewbinding' 库在父 compose 中扩展视图交错布局 xml。我也试过这个,这非常有效。当心所有的事情都需要是视图,适配器和所有

       setContent {
              // get the view model
              val viewModel = ViewModelProvider(
                  this, Injection.provideViewModelFactory(
                      context = this,
                      owner = this
                  )
              )[SearchRepositoriesViewModel::class.java]
      
                  AndroidViewBinding(ActivitySearchRepositoriesBinding::inflate) {
                      val repoAdapter = ReposAdapter()
                      val header = ReposLoadStateAdapter { repoAdapter.retry() }
                      list.adapter = repoAdapter.withLoadStateHeaderAndFooter(
                          header = header,
                          footer = ReposLoadStateAdapter { repoAdapter.retry() }
                      )
      
                      val staggeredGridLayoutManager =
                          StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL)
                      this.list.apply {
                          layoutManager = staggeredGridLayoutManager
                          setHasFixedSize(true)
                          adapter = repoAdapter
                      }
      
                      lifecycleScope.launch {
                          viewModel.pagingDataFlow.collectLatest { movies ->
                              repoAdapter.submitData(movies)
                          }
                      }
                      retryButton.setOnClickListener { repoAdapter.retry() }
      
                      lifecycleScope.launch {
                          viewModel.pagingDataFlow.collectLatest(repoAdapter::submitData)
                      }
      
                      lifecycleScope.launch {
                          repoAdapter.loadStateFlow.collect { loadState ->
                              // Show a retry header if there was an error refreshing, and items were previously
                              // cached OR default to the default prepend state
                              header.loadState = loadState.mediator
                                  ?.refresh
                                  ?.takeIf { it is LoadState.Error && repoAdapter.itemCount > 0 }
                                  ?: loadState.prepend
      
                              val isListEmpty =
                                  loadState.refresh is LoadState.NotLoading && repoAdapter.itemCount == 0
                              // show empty list
                              emptyList.isVisible = isListEmpty
                              // Only show the list if refresh succeeds, either from the the local db or the remote.
                              list.isVisible =
                                  loadState.source.refresh is LoadState.NotLoading || loadState.mediator?.refresh is LoadState.NotLoading
                              // Show loading spinner during initial load or refresh.
                              progressBar.isVisible = loadState.mediator?.refresh is LoadState.Loading
                              // Show the retry state if initial load or refresh fails.
                              retryButton.isVisible =
                                  loadState.mediator?.refresh is LoadState.Error && repoAdapter.itemCount == 0
                              // Toast on any error, regardless of whether it came from RemoteMediator or PagingSource
                              val errorState = loadState.source.append as? LoadState.Error
                                  ?: loadState.source.prepend as? LoadState.Error
                                  ?: loadState.append as? LoadState.Error
                                  ?: loadState.prepend as? LoadState.Error
                              errorState?.let {
                                  Toast.makeText(
                                      this@SearchRepositoriesActivity,
                                      "\uD83D\uDE28 Wooops ${it.error}",
                                      Toast.LENGTH_LONG
                                  ).show()
                              }
                          }
                      }
              }
          }
      

      我的样本(选项 1 和选项 2)都在工作和测试。到目前为止,还没有闪烁和突然的行为。它工作得很好,有两种选择。让我知道有什么我可以帮忙的。 (这也是我的第一个答案耶耶......!!)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-22
      • 1970-01-01
      • 2022-11-10
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多