如何转到使用分页 3 加载数据的视图分页器 2 的位置?

axl*_*rtr 7 android android-paging android-viewpager2

我使用 ViewPager2 来显示从服务器获取的数据,并使用 Paging 3 库保存到 Room 数据库。我的问题是,如何通过代码导航到特定的视图寻呼机项目?如果我使用 viewPager.setCurrentItem(position, false) 那么这不起作用。例如,如果我在向左/向右滑动时动态加载总共 3000 个项目,如何将位置设置为 1000,然后从那里双向导航/加载数据?我无法让它发挥作用。

PS:在下面的 DogPagingMediator 类中,我还尝试在刷新块中设置一些起始编号而不是最新(最高)编号,但是在加载应用程序时,如果编号较高的项目不这样做,则视图寻呼机只会从此位置开始t 存在于数据库本地,否则它将始终从编号最高的项目开始,无论刷新中返回的页面如何(我假设因为dogDao.getDogs() 按降序获取数据库中的所有项目)。

PPS:我使用实时数据而不是 Flow 的原因是因为 Flow 在我滑动时由于某种原因导致 NullPointerException 。

包含视图分页器的片段中的 onCreateView 代码:

    lifecycleScope.launch {
        // Fetch the latest dog item from the network (data is sorted by descending)
        if (!browseDogsViewModel.latestDogIsFetched()) {
            browseDogsViewModel.setLatestDogNumber()
        }

        browseDogsViewModel.pagingDataStream.observe(viewLifecycleOwner) {
            adapter.submitData(viewLifecycleOwner.lifecycle, it)
        }
    }
Run Code Online (Sandbox Code Playgroud)

从视图模型来看:

val pagingDataStream = repository.getAllDogsPagingData()

suspend fun setLatestDogNumber() {
    latestDogNumber = repository.getLatestDogNumber()
}
Run Code Online (Sandbox Code Playgroud)

从存储库:

fun getAllDogsPagingData() = Pager(
    config = PagingConfig(pageSize = PAGE_SIZE),
    remoteMediator = dogPagingMediator,
    pagingSourceFactory = { dogDao.getDogs() }
).liveData
Run Code Online (Sandbox Code Playgroud)

调解器(类似于 google paging3 Codelab 示例,只是按降序排序): https: //codelabs.developers.google.com/codelabs/android-paging/#0):

@OptIn(ExperimentalPagingApi::class)
class DogPagingMediator @Inject constructor(
    private val dogDatabase: DogDatabase,
    private val dogDao: DogDao,
    private val remoteKeysDao: RemoteKeysDao,
    private val service: DogService,
) : RemoteMediator<Int, Dog>() {
    override suspend fun load(loadType: LoadType, state: PagingState<Int, Dog>): MediatorResult {
        try {
            val page = when (loadType) {
                LoadType.REFRESH -> {
                    val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
                    remoteKeys?.nextKey?.plus(PAGE_SIZE) ?: BrowseDogsViewModel.latestDogNumber
                }
                LoadType.PREPEND -> {
                    val remoteKeys = getRemoteKeyForFirstItem(state)
                    if (remoteKeys == null) {
                        // The LoadType is PREPEND so some data was loaded before,
                        // so we should have been able to get remote keys
                        // If the remoteKeys are null, then we're an invalid state and we have a bug
                        throw InvalidObjectException("Remote key and the prevKey should not be null")
                    }
                    // If the previous key is null, then we can't request more data
                    remoteKeys.prevKey
                        ?: return MediatorResult.Success(endOfPaginationReached = true)
                    remoteKeys.prevKey
                }
                LoadType.APPEND -> {
                    val remoteKeys = getRemoteKeyForLastItem(state)
                    if (remoteKeys?.nextKey == null) {
                        throw InvalidObjectException("Remote key should not be null for $loadType")
                    }
                    remoteKeys.nextKey
                }
            }

            val dogs: MutableList<Dog> = mutableListOf()
            for (i in page downTo page - PAGE_SIZE) {
                try {
                    val response = service.geDogWithNumber(i)
                    dogs.add(convertFromDto(response))
                } catch (ex: HttpException) {
                    // Will be 404 when requesting a dog out of range
                    if (ex.code() != 404) {
                    throw ex
                    }
                }
            }

            val endOfPaginationReached = dogs.isEmpty()

            dogDatabase.withTransaction {
                val prevKey =
                    if (page == BrowseDogsViewModel.latestDogNumber) null else page + PAGE_SIZE
                val nextKey = if (endOfPaginationReached) null else page - PAGE_SIZE
                val keys = dogs.map {
                    RemoteKeys(dogNum = it.number, prevKey = prevKey, nextKey = nextKey)
                }

                remoteKeysDao.insertAll(keys)
                dogDao.insertAll(dogs)
            }

            return MediatorResult.Success(
                endOfPaginationReached = endOfPaginationReached
            )
        } catch (exception: IOException) {
            return MediatorResult.Error(exception)
        } catch (exception: HttpException) {
            return MediatorResult.Error(exception)
        }
    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Dog>): RemoteKeys? {
        // Get the last page that was retrieved, that contained items.
        // From that last page, get the last item
        return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
            ?.let { dog->
                // Get the remote keys of the last item retrieved
                remoteKeysDao.remoteKeysDogNum(dog.number)
            }
    }

    private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Dog>): RemoteKeys? {
        // Get the first page that was retrieved, that contained items.
        // From that first page, get the first item
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { dog->
                // Get the remote keys of the first items retrieved
                remoteKeysDao.remoteKeysDogNum(dog.number)
            }
    }

    private suspend fun getRemoteKeyClosestToCurrentPosition(
        state: PagingState<Int, Dog>
    ): RemoteKeys? {
        // The paging library is trying to load data after the anchor position
        // Get the item closest to the anchor position
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.number?.let { num ->
                remoteKeysDao.remoteKeysDogNum(num)
            }
        }
    }

    private fun convertFromDto(dogDto: DogDto): Dog {
        return Dog(...)
    }
}
Run Code Online (Sandbox Code Playgroud)

适配器:

class DogPagingAdapter() :
    PagingDataAdapter<Dog, DogPagingAdapter.ViewPagerViewHolder>(DogDiffUtilCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerViewHolder {
        return ViewPagerViewHolder(
            ItemDogViewPagerBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ViewPagerViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    inner class ViewPagerViewHolder(private val binding: ItemDogViewPagerBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(dog: Dog?) {
            binding.dog = dog
            binding.executePendingBindings()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Koz*_*nik 0

官方文档说:

设置当前选择的页面。如果 ViewPager 已经使用其当前适配器完成了第一个布局,则当前项目和指定项目之间将出现平滑的动画过渡。如果适配器未设置或为空,则静默忽略。将项目夹在适配器的边界上。

在调用此方法之前,您必须确保适配器中存在第 N 个项目。