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

Meh*_*iya 7 android android-jetpack-compose jetpack-compose-accompanist android-jetpack-compose-pager

我的应用程序中有两个屏幕。Paging3第一个屏幕通过使用垂直网格中的库显示设备上的图像列表。现在,当用户单击图像时,我将单击位置传递到第二个屏幕,我在第二个屏幕上使用HorizontalPager伴奏来全屏显示图像。两个屏幕共享相同的 ViewModel 以使用Paging3.

在 Horizo​​ntalPager 中显示图像的代码如下所示。

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
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

这里,currentImagePosition是在第一个屏幕上单击的图像的索引。我正在设置initialLoadSize确保currentImagePosition + 1分页库已获取要显示的单击图像。

当第二个屏幕打开时,单击的图像将按预期全屏显示。但是,当用户滑动获取下一个图像时,不会加载下一个图像,而是随着50用户进一步滑动而加载带有索引等的图像。

我不确定我在这里缺少什么。任何帮助将不胜感激。

编辑:添加了 ViewModel、存储库和分页代码

视图模型

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

存储库

fun getImagesPagingSource(): PagingSource<Int, MediaStoreImage> {
        return ImagesDataSource { limit, offset ->
            getSinglePageImages(
                limit,
                offset
            )
        }
    }
Run Code Online (Sandbox Code Playgroud)
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
    }
Run Code Online (Sandbox Code Playgroud)
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
            )
        }
    }
Run Code Online (Sandbox Code Playgroud)

分页数据源

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
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

Meh*_*iya 0

问题解决了。每当第二个屏幕打开时,getImages()就会调用共享 ViewModel 的函数,从而创建一个新的 Pager 实例。这个新的寻呼机实例与第一个屏幕上使用的不同。参考StackOverflow上的这个答案,我在ViewModel的块中创建了Pager,init如下所示。

    val images: Flow<PagingData<MediaStoreImage>>

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

在第二个屏幕上,我收集了分页项目,如下所示。

val lazyImages: LazyPagingItems<MediaStoreImage> = viewModel.images.collectAsLazyPagingItems()
Run Code Online (Sandbox Code Playgroud)

现在,我不必传递,initialLoadSize因为在第一个屏幕中创建的寻呼机正在被重用,它已经加载了项目。