Paging 3
基于 RecyclerView
提供了 1 个分页库,为用户处理了翻页、加载更多、刷新等多种功能,可以有效优化代码中这部分的逻辑。
implementation "androidx.paging:paging-runtime:3.0.0-alpha02" 复制代码
详见 Paging 3 library overview
在定义 Dao
接口的 Query
语句时,返回类型要使用 PagingSource
类型,第 2 个参数为表的数据结构。同时不需要在 Query
里指定页数和每页展示数量,页数由 PagingSource
来控制,每页数量页在 PagingConfig
中定义。
@Dao interface CheeseDao { @Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC") fun allCheesesByName(): PagingSource<Int, Cheese> @Insert fun insert(cheese: Cheese) @Delete fun delete(cheese: Cheese) } 复制代码
使用 Room 有一个好处是,如果通过
insert()
或delete()
等方法修改了 Room 里的数据,不需要额外处理就会即时反应在PagingSource
里,界面上展示的数据会相应变化。
如果不是直接使用 Room 的数据,而是使用源自其他地方的数据,比如网络数据,就需要自定义 PagingSource
了,创建方式如下:
class PageKeyedSubredditPagingSource( private val redditApi: RedditApi, private val subredditName: String ) : PagingSource<String, RedditPost>() { override suspend fun load(params: LoadParams<String>): LoadResult<String, RedditPost> { return try { val data = redditApi.getTop( subreddit = subredditName, // key 类的类型为 PagingSource<Key : Any, Value : Any> 里的 key ,具体值由用户自行定义。首次调用时为 null after = if (params is Append) params.key else null, before = if (params is Prepend) params.key else null, limit = params.loadSize // loadSize 为请求数据量 ).data Page( data = data.children.map { it.data }, prevKey = data.before, nextKey = data.after ) } catch (e: IOException) { LoadResult.Error(e) } catch (e: HttpException) { LoadResult.Error(e) } } } 复制代码
val allCheeses: Flow<PagingData<Cheese>> = Pager( PagingConfig( // 每页显示的数据的大小。对应 PagingSource 里 LoadParams.loadSize pageSize = 20, // 预刷新的距离,距离最后一个 item 多远时加载数据 prefetchDistance = 3, // 初始化加载数量,默认为 pageSize * 3 initialLoadSize = 60, // 一次应在内存中保存的最大数据 maxSize = 200 ) ) { // 数据源,要求返回的是 PagingSource 类型对象 dao.allCheesesByName() }.flow // 最后构造的和外部交互对象,有 flow 和 liveData 两种 复制代码
class CheeseAdapter : PagingDataAdapter<Cheese, CheeseViewHolder>(diffCallback) { override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) { holder.bindTo(getItem(position)) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder = CheeseViewHolder(parent) companion object { private val diffCallback = object : DiffUtil.ItemCallback<Cheese>() { override fun areItemsTheSame(oldItem: Cheese, newItem: Cheese): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: Cheese, newItem: Cheese): Boolean = oldItem == newItem } } } 复制代码
PagingDataAdapter
在构造方法中只需要传入 DiffUtil.ItemCallback
对象,不需要定义和传入数据。后续会同 PagingData
关联,使用 PagingData
的数据。如果需要获取某个位置处的数据,在类里使用 getItem()
方法即可。
同时在自定义的 PagingDataAdapter
里只需要实现 onBindViewHolder()
和 onCreateViewHolder()
方法即可。
val adapter = CheeseAdapter() recyclerView.adapter = adapter lifecycleScope.launch { viewModel.allCheeses.collectLatest { adapter.submitData(it) } } 复制代码
参考文章:
Android Jetpack 分页库概览
Jetpack 成员 Paging3 实践以及源码分析(一)
JetPack系列 Paging 3.0学习