Paging 库可帮助您加载和显示来自本地存储或网络中更大的数据集中的数据页面。此方法可让您的应用更高效地利用网络带宽和系统资源。Paging 库的组件旨在契合推荐的?Android 应用架构,流畅集成其他?Jetpack?组件,并提供一流的 Kotlin 支持。
def paging_version = "3.1.1"
implementation "androidx.paging:paging-runtime:$paging_version"
Repository层:
代码库层中的主要 Paging 库组件是 PagingSource
。每个 PagingSource
对象都定义了数据源,以及如何从该数据源检索数据。PagingSource
对象可以从任何单个数据源(包括网络来源和本地数据库)加载数据。
您可能使用的另一个 Paging 库组件是 RemoteMediator
。RemoteMediator
对象会处理来自分层数据源(例如具有本地数据库缓存的网络数据源)的分页。
ViewModel层:
Pager
组件提供了一个公共 API,基于 PagingSource
对象和 PagingConfig
配置对象来构造在响应式流中公开的 PagingData
实例。
将 ViewModel
层连接到界面的组件是 PagingData
。PagingData
对象是用于存放分页数据快照的容器。它会查询 PagingSource
对象并存储结果。
UI层:
界面层中的主要 Paging 库组件是 PagingDataAdapter
,它是一种处理分页数据的 RecyclerView
适配器。
此外,您也可以使用随附的 AsyncPagingDataDiffer
组件构建自己的自定义适配器。
object NetworkManager {
private const val BASE_URL = "https://api.github.com/"
private val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
logE(message)
}
}).setLevel(HttpLoggingInterceptor.Level.BASIC))
.build()
}
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
interface UserService {
@GET("search/repositories?sort=stars&q=Android")
suspend fun searchUser(@Query("page") page: Int, @Query("per_page") perPager: Int): BaseResponse
companion object {
fun create(): UserService {
return NetworkManager.retrofit.create(UserService::class.java)
}
}
}
实现 PagingSource 接口,PagingSource有2个泛型参数:
class UserPagingSource(private val userService: UserService) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
return try {
// 获取当前页数,params.key为null时,默认加载第1页
val page = params.key ?: 1
// 获取每页的数据
val pageSize = params.loadSize
// 网络请求:
val response = userService.searchUser(page, pageSize)
val data = response.items
// 获取前一页索引
val prevKey = if (page > 1) page - 1 else null
// 获取后一页索引
val nextKey = if (data.isNotEmpty()) page + 1 else null
// 返回分页数据
LoadResult.Page(data, prevKey, nextKey)
} catch (e: Exception) {
// 请求失败返回错误状态
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
return null
}
}
调用PagingSource:
通过 PagingConfig 可以对Paging3进行定制:
object UserRepo {
private const val PAGE_SIZE = 15
private val userService = UserService.create()
fun getPagingData(): Flow<PagingData<User>> {
// 配置Pager
return Pager(
config = PagingConfig(PAGE_SIZE),
pagingSourceFactory = { UserPagingSource(userService) }
).flow
}
}
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getPagingData(): Flow<PagingData<User>> {
// cacheIn表示将结果缓存到ViewModelScope中,在onClear之前一直存在
return UserRepo.getPagingData().cachedIn(viewModelScope)
}
}
继承 PagingDataAdapter 类。
class UserAdapter(val updateItem: (Int, User) -> Unit) :
PagingDataAdapter<User, UserAdapter.ViewHolder>(diffCallback) {
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvName: TextView = itemView.findViewById(R.id.tv_name)
val tvFullName: TextView = itemView.findViewById(R.id.tv_full_name)
val tvCount: TextView = itemView.findViewById(R.id.tv_count)
val tvDescription: TextView = itemView.findViewById(R.id.tv_description)
val btnUpdate: Button = itemView.findViewById(R.id.btn_update)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = getItem(position)
user?.let {
holder.tvName.text = it.name
holder.tvFullName.text = it.fullName
holder.tvDescription.text = it.description
holder.tvCount.text = it.starCount.toString()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
val viewHolder = ViewHolder(itemView)
viewHolder.btnUpdate.setOnClickListener {
val position = viewHolder.layoutPosition
val user = getItem(position)
updateItem(position, user!!)
notifyItemChanged(position)
}
return viewHolder
}
}
lifecycleScope.launch {
// 监听数据
viewModel.getPagingData().collect {
pagingData = it
// 将数据传递给适配器
mAdapter.submitData(it)
}
}
// 监听加载状态
mAdapter.addLoadStateListener {
when (it.refresh) {
is LoadState.NotLoading -> {
progressBar.visibility = View.INVISIBLE
rvUsers.visibility = View.VISIBLE
logE("无加载")
}
is LoadState.Loading -> {
progressBar.visibility = View.VISIBLE
rvUsers.visibility = View.INVISIBLE
logE("正在加载")
}
is LoadState.Error -> {
val state = it.refresh as LoadState.Error
progressBar.visibility = View.INVISIBLE
showToast("加载失败")
logE("加载失败:${state.error.message}")
}
}
}
定义FooterAdapter:
class FooterAdapter(val retry: () -> Unit) : LoadStateAdapter<FooterAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val progressBar: ProgressBar = itemView.findViewById(R.id.progress_bar)
val btnRetry: Button = itemView.findViewById(R.id.btn_retry)
}
override fun onBindViewHolder(holder: ViewHolder, loadState: LoadState) {
holder.progressBar.isVisible = loadState is LoadState.Loading
holder.btnRetry.isVisible = loadState is LoadState.Error
logE("loadState $loadState")
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): ViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.item_footer, parent, false)
val viewHolder = ViewHolder(itemView)
viewHolder.btnRetry.setOnClickListener {
retry()
}
return viewHolder
}
}
设置FooterAdapter:
rvUsers.adapter = mAdapter.withLoadStateFooter(FooterAdapter {
// 重试
mAdapter.retry()
})
在 onCreateViewHolder() 方法中设置监听。
class UserAdapter(val updateItem: (Int, User) -> Unit) :
PagingDataAdapter<User, UserAdapter.ViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
val viewHolder = ViewHolder(itemView)
viewHolder.btnUpdate.setOnClickListener {
val position = viewHolder.layoutPosition
val user = getItem(position)
updateItem(position, user!!)
notifyItemChanged(position)
}
return viewHolder
}
}
mAdapter = UserAdapter { position, user ->
user.fullName = System.currentTimeMillis().toString()
}