Paging 3
基于 RecyclerView
提供了 1 个分页库,为用户处理了翻页、加载更多、刷新等多种功能,可以有效优化代码中这部分的逻辑。
1. 引用依赖项
implementation "androidx.paging:paging-runtime:3.0.0-alpha02"
2. 加载数据源
2.1 Room
在定义 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
里,界面上展示的数据会相应变化。
2.2 自定义数据源
如果不是直接使用 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)
}
}
}
3. 构造 Pager 和 PagingData
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 两种
4. 构造自定义 PagingDataAdapter
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()
方法即可。
5. 关联数据
val adapter = CheeseAdapter()
recyclerView.adapter = adapter
lifecycleScope.launch {
viewModel.allCheeses.collectLatest { adapter.submitData(it) }
}
参考文章: