简体   繁体   English

Jetpack Compose 中带有 Paging3 的水平寻呼机

[英]Horizontal Pager with Paging3 in Jetpack Compose

There are two screens in my app.我的应用程序中有两个屏幕。 The first screen shows the list of images on the device by using the Paging3 library in a vertical grid.第一个屏幕使用垂直网格中的Paging3库显示设备上的图像列表。 Now, when the user clicks on an image, I am passing the click position to the second screen where I am using HorizontalPager from Accompanist to show the images in full screen.现在,当用户单击图像时,我将单击 position 传递到第二个屏幕,在那里我使用 Accompanist 的HorizontalPager ntalPager 全屏显示图像。 Both the screens share the same ViewModel to fetch images using Paging3 .两个屏幕共享相同的 ViewModel 以使用Paging3获取图像。

The code to show the images in HorizontalPager is shown below.在 HorizontalPager 中显示图像的代码如下所示。

val images: LazyPagingItems<MediaStoreImage> =
    viewModel.getImages(initialLoadSize = args.currentImagePosition + 1, pageSize = 50)
        .collectAsLazyPagingItems()

val pagerState = rememberPagerState(initialPage = currentImagePosition)

Box(modifier = modifier) {
    HorizontalPager(
        count = images.itemCount,
        state = pagerState,
        itemSpacing = 16.dp
    ) { page ->
        ZoomableImage(
            modifier = modifier,
            imageUri = images[page]?.contentUri
        )
    }
}

Here, currentImagePosition is the index of the image clicked on the first screen.这里, currentImagePosition是在第一个屏幕上单击的图像的索引。 I am setting the initialLoadSize to currentImagePosition + 1 which makes sure that the clicked image to be shown is already fetched by the paging library.我将initialLoadSize设置为currentImagePosition + 1 ,以确保分页库已经获取了要显示的单击图像。

When the second screen is opened, the clicked image is shown in full screen as expected.打开第二个屏幕时,单击的图像按预期全屏显示。 However, when the user swipes for the next image, instead of loading the next image, the image with the index 50 and so on gets loaded as the user swipes further.但是,当用户滑动下一个图像时,不是加载下一个图像,而是随着用户进一步滑动而加载具有索引50等的图像。

I am not sure what am I missing here.我不确定我在这里错过了什么。 Any help will be appreciated.任何帮助将不胜感激。

Edit: Added ViewModel, Repository & Paging code编辑:添加了 ViewModel、Repository 和 Paging 代码

ViewModel视图模型

fun getImages(initialLoadSize: Int = 50): Flow<PagingData<MediaStoreImage>> {
        return Pager(
            config = PagingConfig(
                pageSize = 50,
                initialLoadSize = initialLoadSize,
                enablePlaceholders = true
            )
        ) {
            repository.getImagesPagingSource()
        }.flow.cachedIn(viewModelScope)
    }

Repository存储库

fun getImagesPagingSource(): PagingSource<Int, MediaStoreImage> {
        return ImagesDataSource { limit, offset ->
            getSinglePageImages(
                limit,
                offset
            )
        }
    }
private fun getSinglePageImages(limit: Int, offset: Int): List<MediaStoreImage> {
        val images = ArrayList<MediaStoreImage>()
        val cursor = getCursor(limit, offset)

        cursor?.use {
            val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
            val dateModifiedColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
            val displayNameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
            val sizeColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)

            while (it.moveToNext()) {
                val id = it.getLong(idColumn)
                val dateModified =
                    Date(TimeUnit.SECONDS.toMillis(it.getLong(dateModifiedColumn)))
                val dateModifiedString = getFormattedDate(dateModified)
                val displayName = it.getString(displayNameColumn)
                val size = it.getLong(sizeColumn)
                val sizeInMbKb = getFileSize(size)
                val contentUri = ContentUris.withAppendedId(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    id
                )

                images.add(
                    MediaStoreImage(
                        id,
                        displayName,
                        dateModifiedString,
                        contentUri,
                        sizeInMbKb
                    )
                )
            }
        }

        cursor?.close()
        return images
    }
private fun getCursor(limit: Int, offset: Int): Cursor? {
        val projection = arrayOf(
            MediaStore.Images.Media._ID,
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.DATE_MODIFIED,
            MediaStore.Images.Media.SIZE
        )

        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val bundle = bundleOf(
                ContentResolver.QUERY_ARG_SQL_SELECTION to "${MediaStore.Images.Media.RELATIVE_PATH} like ? ",
                ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf("%${context.getString(R.string.app_name)}%"),
                ContentResolver.QUERY_ARG_OFFSET to offset,
                ContentResolver.QUERY_ARG_LIMIT to limit,
                ContentResolver.QUERY_ARG_SORT_COLUMNS to arrayOf(MediaStore.Images.Media.DATE_MODIFIED),
                ContentResolver.QUERY_ARG_SORT_DIRECTION to ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
            )

            context.contentResolver.query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                projection,
                bundle,
                null
            )
        } else {
            context.contentResolver.query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                projection,
                "${MediaStore.Images.Media.DATA} like ? ",
                arrayOf("%${context.getString(R.string.app_name)}%"),
                "${MediaStore.Images.Media.DATE_MODIFIED} DESC LIMIT $limit OFFSET $offset",
                null
            )
        }
    }

Paging Data Source分页数据源

class ImagesDataSource(private val onFetch: (limit: Int, offset: Int) -> List<MediaStoreImage>) :
    PagingSource<Int, MediaStoreImage>() {

    override fun getRefreshKey(state: PagingState<Int, MediaStoreImage>): Int? {
        return state.anchorPosition?.let {
            state.closestPageToPosition(it)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
        }
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MediaStoreImage> {
        val pageNumber = params.key ?: 0
        val pageSize = params.loadSize
        val images = onFetch.invoke(pageSize, pageNumber * pageSize)
        val prevKey = if (pageNumber > 0) pageNumber.minus(1) else null
        val nextKey = if (images.isNotEmpty()) pageNumber.plus(1) else null

        return LoadResult.Page(
            data = images,
            prevKey = prevKey,
            nextKey = nextKey
        )
    }
}

Seems like a bug in the Paging3 library.似乎是 Paging3 库中的一个错误。 Consider sharing the codebase with the issue tracker team to help with the resolution of the issue.考虑与问题跟踪团队共享代码库,以帮助解决问题。

The issue is solved.问题解决了。 Whenever the second screen was opened, the getImages() function of the shared ViewModel was called which created a new instance of Pager.每当打开第二个屏幕时,就会调用共享 ViewModel 的getImages() function,从而创建一个新的 Pager 实例。 This new instance of the Pager was different from the one used on the first screen.这个新的寻呼机实例与第一个屏幕上使用的不同。 Referring to this answer on StackOverflow, I created the Pager in the init block of the ViewModel as shown below.参考 StackOverflow 上的这个答案,我在 ViewModel 的init块中创建了 Pager,如下所示。

    val images: Flow<PagingData<MediaStoreImage>>

    init {
        images = Pager(
            config = PagingConfig(
                pageSize = 50,
                initialLoadSize = 50,
                enablePlaceholders = true
            )
        ) {
            repository.getImagesPagingSource()
        }.flow.cachedIn(viewModelScope)
    }

On the second screen, I collected the paging items as shown below.在第二个屏幕上,我收集了如下所示的分页项。

val lazyImages: LazyPagingItems<MediaStoreImage> = viewModel.images.collectAsLazyPagingItems()

Now, I didn't have to pass the initialLoadSize as the Pager created in the first screen was being reused which had already loaded the items.现在,我不必传递initialLoadSize ,因为在第一个屏幕中创建的寻呼机正在被重用,它已经加载了项目。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM