2023年7月25日发(作者:)
使⽤Paging3实现分页加载作者 / Florina MuntenescuPaging 库可以帮助您优雅地渐进加载⼤型数据集合,同时也可以减少⽹络的使⽤和系统资源的消耗。基于您的反馈我们得知,Paging 2.0 API还不能满⾜开发者们的需求——开发者们希望以更简便的⽅式处理错误;以更灵活的⽅式实现列表数据的转换操作,例如 map 和 filter;以及⽀持分割符、页眉和页脚。基于以上反馈,我们推出了 Paging 3.0。这是⼀个完全使⽤ Kotlin 协程重写的库 (依然⽀持 Java ⽤户),它将为您提供您所要求的功能。Paging 3 亮点Paging 3 的 API 对分页加载时可能需要实现的常见功能提供了⽀持:跟踪获取前⼀页或后⼀页所需要的参数;当⽤户滚动到现有数据的末尾时,⾃动请求正确的下⼀页;确保不会同时触发多个请求;跟踪加载状态,并⽀持您在 RecyclerView 的列表项或者界⾯中的其他地⽅展⽰它。为失败的加载提供简便的重试功能;⽆论您是否使⽤ Flow、LiveData、RxJava Flowable 或 Observable,都可以对需要展⽰的列表使⽤ map 或 filter 这类常见的操作;提供实现列表分隔符的简便⽅法;简化了数据缓存,确保不会让您在每次配置更改时都执⾏数据转换。我们还让 Paging 3 的⼀些组件向后兼容 Paging 2.0。因此,如果您已经在应⽤中使⽤了 Paging,则可以逐步 迁移⾄ Paging 3。在您的应⽤中使⽤ Paging 3假设我们正在实现⼀个展⽰所有狗狗的应⽤。狗狗的数据从 GoodDoggos API 获得,该 API ⽀持基于索引的分页。让我们研究下需要实现的Paging 组件,以及如何将 Paging 集成到现有的应⽤架构。接下来的例⼦将使⽤ Kotlin 及其协程功能编写,如果您需要使⽤ LiveData/RxJava实现的 Java 编程语⾔⽰例,请参阅 Android 开发者⽂档 | Paging 3 库概述。下图为您应⽤的各个层级中推荐直接接⼊ Paging 的 Android 应⽤架构:Paging 组件及其在应⽤架构的集成定义数据源数据源的定义取决于您从哪⾥加载数据。您仅需实现 PagingSource 或者 PagingSource 与 RemoteMediator 的组合:如果您从 单个源 加载数据,例如⽹络、本地数据、⽂件等,实现 PagingSource 即可,如果您使⽤了 Room,从 2.3.0-alpha 开始,它将默认为您实现 Paging Source,请参见: Android 开发⽂档|使⽤ Room DAO 访问数据;如果您从⼀个 多层级数据源 加载数据,就像带有本地数据库缓存的⽹络数据源那样。那么您需要实现 RemoteMediator 来合并两个数据源到⼀个本地数据库缓存的 PagingSource 中。PagingSourcePagingSource 可以定义⼀个分页数据的数据源,以及从该数据源获取数据的⽅式。PagingSource 应当为资源库层的⼀部分。您可以实现load() 函数来从数据源获取分页数据,并返回加载好的数据和加载前后页的参数信息。load() 是⼀个挂起函数,您可以在这⾥调⽤其他的 挂起函数,例如⽹络请求:class DoggosRemotePagingSource( val backend: GoodDoggosService) : PagingSource() { override suspend fun load( params: LoadParams ): LoadResult { try { // 未定义时加载第 1 页 val nextPageNumber = ?: 1 val response = gos(nextPageNumber) return ( data = , prevKey = null, // 仅向后翻页 nextKey = geNumber + 1 ) } catch (e: Exception) { // 在此块中处理错误 return (exception) } }}复制代码PagingData 与 Pager分页数据的容器被称为 PagingData,每次刷新数据时,都会创建⼀个 PagingData 的实例。如果要创建 PagingData 数据流,您需要创建⼀个Pager 实例,并提供⼀个 PagingConfig 配置对象和⼀个可以告诉 Pager 如何获取您实现的 PagerSource 的实例的函数,以供 Pager 使⽤。您要在 ViewModel 中构造 Pager 对象并向 UI 暴露⼀个 Flow。Flow 有⼀个⽅便的 cachedIn() ⽅法,该⽅法使得数据流可以被共享,也让您可以在 CoroutineScope 中缓存 Flow 的内容。这样⼀来,如果您在数据流中实现了任何转换操作,当 Activity 被重建并使得您从 flow 中获取数据时,不会再次触发这些操作。由于我们希望数据在配置产⽣变化后仍然存在,缓存应当尽可能靠近 UI 层,但⼜不能在 UI 层中,那么最好的位置便是在 ViewModel 中,并使⽤ viewModelScope:val doggosPagingFlow = Pager(PagingConfig(pageSize = 10)) { DogRemotePagingSource(goodDoggosService)}.In(viewModelScope)复制代码PagingDataAdapter为了将 RecyclerView 与 PagingData 联系起来,您需要实现⼀个 PagingDataAdapter:class DogAdapter(diffCallback: llback) : PagingDataAdapter(diffCallback) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): DogViewHolder { return DogViewHolder(parent) } override fun onBindViewHolder(holder: DogViewHolder, position: Int) { val item = getItem(position) if(item == null) { aceholder() } else { (item) } }}复制代码接下来,在您的
Activity/Fragment 中,您需要收集
Flow 并将其提交给
PagingDataAdapter。下⾯是⼀个在
Activity 的
onCreate() 函数中实现该操作的⽰例:val viewModel by viewModels()val pagingAdapter = DogAdapter(DogComparator)val recyclerView = findViewById(er_view)r = { tLatest { pagingData -> Data(pagingData) }}复制代码分页数据转换[图⽚上传失败...(image-a4853f-16)]展⽰⼀个过滤后的列表转换 PagingData 流与您在其他数据流中所做的同类操作相似。举例来说,如果我们只想要展⽰ Flow 中那些调⽪的狗狗,我们可能需要映射 Flow 对象并过滤 PagingData: { pagingData -> { dog -> ful } }复制代码有分隔符的列表向列表中添加 分隔符 同样是分页数据转换,这⾥我们通过转换 PagingData 向列表中插⼊分隔对象。举例来说,我们可以为狗狗的名字插⼊字母分隔符。当使⽤分隔符时,您需要⾃⼰实现 UI 模型类以⽀持新的分隔项。当您修改 PagingData 并插⼊分隔符时,您会⽤到insertSeparators 转换: { pagingData: PagingData -> { doggo -> // 将数据流中的项⽬转换为 el。 el(doggo) } .insertSeparators { before: Dog, after: Dog -> return if(after == null) { // 我们到了列表的末尾 null } else if (before == null || != ) { // 上下品种不同,显⽰分隔符 torItem() } else { // ⽆分隔符 null } } }}.cachedIn(viewModelScope)复制代码就像前⾯⼀样,我们会在数据到达 UI 层之前使⽤ cachedIn,这样便可以缓存所有已经加载的数据以及数据转换的结果。当配置发⽣改变时,这些缓存就会被复⽤。使⽤ RemoteMediator 进⾏⾼级分页操作当您从⼀个 多层级数据源 加载数据时,应当实现⼀个 RemoteMediator。举例来说,在此类的实现中,您应当从⽹络请求数据并存⼊数据库。每当数据库中没有数据可以被展⽰时,就会触发 load() ⽅法。基于 PagingState 和 LoadType,我们可以构造下⼀页的数据请求。由于 Paging 库并不知道您的 API 是怎样的,所以定义如何构造和获取前⼀页和下⼀页的远程数据的⼯作便需要由您⾃⼰来完成。举例来说,您可以将您从⽹络接收到的每个项⽬与远程关键字关联起来并存⼊数据库。override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { val page = ... // 基于 loadType 和 state 进⾏计算 try { val doggos = gos(page) Dao().insertAll(doggos) val endOfPaginationReached = y() return s(endOfPaginationReached = endOfPaginationReached) } catch (exception: Exception) { return (exception) }
}复制代码如果您从⽹络请求数据并存⼊数据库,那么数据库才是屏幕上所展⽰数据的真正数据源——这意味着 UI 会展⽰从数据库获取的数据,所以您需要为您的数据库实现 PagingSource。如果您正在使⽤ Room,那么您只需要向您的 DAO 添加⼀个返回 PagingSource 的查询:@Query("SELECT * FROM doggos")fun getDoggos(): PagingSource复制代码这种情况下 Pager 的实现略有不同,您还需要传⼊ RemoteMediator 实例:val pagingSourceFactory = { Dao().getDoggos() }return Pager( config = PagingConfig(pageSize = NETWORK_PAGE_SIZE), remoteMediator = DoggosRemoteMediator(service, database), pagingSourceFactory = pagingSourceFactory).flow复制代码您可以参阅⽂档了解 使⽤ RemoteMediator 的详细信息。如果您需要 RemoteMediator 在应⽤中的完整实现,可以参阅 Paging codelab 和Paging 相关代码。我们将 Paging 3 设计为⼀个帮您涵盖简单和复杂情形下的分页加载的库。它可以让您更⽅便地使⽤⼤规模数据集合,⽆论数据来⾃⽹络、数据库、内存缓存还是上述⼏种情况的组合。Paging 库基于 协程和 Flow 实现,使得它可以很简单地调⽤挂起函数并且处理数据流。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1690217922a316626.html
评论列表(0条)