本篇文章主要是自己对Paging3的学习使用,学习主要是根据郭神的文章进行的, 郭神文章的链接:Jetpack新成员,Paging3从吐槽到真香_郭霖的专栏-CSDN博客_pagingPaging是Google推出的一个应用于Android平台的分页加载库。事实上,Paging并不是现在才刚刚推出的,而是之前就已经推出过两个版本了。但Paging 3和前面两个版本的变化非常大,甚至可以说是完全不同的东西了。所以即使你之前没有学习过Paging的用法也没有关系,把Paging 3当成是一个全新的库去学习就可以了。我相信一定会有很多朋友在学习Paging 3的时候会产生和我相同的想法:本身Android上的分页功能并不难实现,即使没有Paging库我们也完全做得出来,但为什么Pahttps://guolin.blog.csdn.net/article/details/114707250?spm=1001.2014.3001.5502自己仅仅是对郭神文章的学习,因此很多代码都是郭神的,自己仅仅是做下学习,以及在此记录下自己的理解,巩固一下。
背景:
GitHub - fuusy/component-jetpack-mvvm: 💖组件化+Jetpack+Kotlin+MVVM项目实战,涉及Jetpack相关组件,Kotlin相关技术,协程+Retrofit,Paging3+Room等。💖组件化+Jetpack+Kotlin+MVVM项目实战,涉及Jetpack相关组件,Kotlin相关技术,协程+Retrofit,Paging3+Room等。 - GitHub - fuusy/component-jetpack-mvvm: 💖组件化+Jetpack+Kotlin+MVVM项目实战,涉及Jetpack相关组件,Kotlin相关技术,协程+Retrofit,Paging3+Room等。https://github.com/fuusy/component-jetpack-mvvm我是在学习这位大神的封装框架的时候,看到jetpack的paging3这块的内容,以前虽然也是粗略的看过,但是觉得好麻烦,就没有沉下心来好好的学习一下,这次碰到就决定,重新根据郭神的文章好好学习一遍。
使用的后台数据也是一样的:https://api.github.com/search/repositories?sort=stars&q=Android&per_page=5&page=1
开始:
1、添加paging3的依赖?
//paging3
implementation 'androidx.paging:paging-runtime:3.0.0-beta01'
implementation 'androidx.paging:paging-common:3.0.0-beta01'
2、根据返回的数据创建实体类
data class Repo (
@SerializedName("id") val id: Int,
@SerializedName("name") val name: String,
@SerializedName("description") val description: String?,
@SerializedName("html_url") val htmlUrl: String?,
@SerializedName("stargazers_count") val starCount: Int
)
class RepoResponse(
@SerializedName("items") val items: List<Repo> = emptyList()
)
3、创建Retrofit 网络请求
? ? ? ? 1.创建对应的RetrofitManager
package com.example.common.network
import android.util.Log
import com.example.common.network.config.LocalCookieJar
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
private const val TAG = "RetrofitManager"
object RetrofitManager {
private val mOkClient = OkHttpClient.Builder()
.callTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.followRedirects(false)
.cookieJar(LocalCookieJar())
.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.d(TAG, "log: $message")
}
}).setLevel(HttpLoggingInterceptor.Level.BODY)).build()
private var mRetrofit: Retrofit? = null
fun initRetrofit(): RetrofitManager {
mRetrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(mOkClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
return this
}
fun <T> getService(serviceClass: Class<T>): T {
if (mRetrofit == null) {
throw UninitializedPropertyAccessException("Retrofit必须初始化")
} else {
return mRetrofit!!.create(serviceClass)
}
}
}
? ? ? ? 2.创建对应的网络接口
@GET("search/repositories?sort=stars&q=Android")
suspend fun searchRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): RepoResponse
? ? ? ? 3.继承PagingSource
? ? ? ? ? ?首先,继承PagingSource,其中包含两个泛型,第一个是页数的类型,一般无特殊要求就是Int,第二个是真实数据的类型,不是list,
? ? ? ? ? ?然后,实现其中的两个方法,getRefreshKey()这里可以暂时不用管,暂时给个null就可以,主要是实现load()方法,也是固定写法,主要是理解:
class RepoPagingSource(private val homeService: HomeService):PagingSource<Int,Repo>() {
override fun getRefreshKey(state: PagingState<Int, Repo>): Int? = null
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
return try {
val page = params.key ?: 1
val pageSize = params.loadSize
val repoRepos = homeService.searchRepos(page, pageSize)
val repoItems = repoRepos.items
val prevKey = if (page > 1) page - 1 else null
val nextKey = if (repoItems.isNotEmpty()) page + 1 else null
LoadResult.Page(repoItems,prevKey,nextKey)
}catch (e:Exception){
e.printStackTrace()
LoadResult.Error(e)
}
}
}
? ? ? ? load()方法正常状态返回数据,使用LoadResult.Page()进行包装,其中包含三个参数,repoItems:正常请求接口返回的数据的实际内容,即:请求的当前页的数据,List<Item>,preKey:前一页的页码,如果是当前页是第一页,这里放null,nextKey:下一页的页码,如果当前页是最后一页,这里放null。
????????4.创建对应的仓库
package com.example.home.repo
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.common.base.BaseRepository
import com.example.home.api.HomeService
import com.example.home.repo.data.RepoPagingSource
import kotlinx.coroutines.flow.Flow
/**
*
* @Description: java类作用描述
* @Author: 作者名
* @CreateDate: 2022/2/21 9:41
* @Version: 1.0
*/
@ExperimentalPagingApi
class HomeRepo(private val service: HomeService):BaseRepository() {
companion object{
private const val PAGE_SIZE = 50
val config =PagingConfig(
pageSize = PAGE_SIZE,
prefetchDistance = 5,
initialLoadSize = 50,
enablePlaceholders = false,
maxSize = PAGE_SIZE *3
)
}
fun getPagingData(): Flow<PagingData<Repo>> {
return Pager(
config = config,
pagingSourceFactory = {RepoPagingSource(service)}
).flow
}
}
????????创建仓库类,在这里取步骤三中数据源的数据并放入到Flow中,这里如果不想使用Flow,也可以使用Rxjava、liveData等,形式多样,目的就是取到数据,并发射到UI层,更新UI,使用什么方式随意。
? ? ? ? 获取数据源中的数据,使用的是Pager(),这里需要两个参数:config、pagingSourceFactory。
config可以直接创建,config的构造方法包含6个参数:
????????pageSize:不做过多介绍,都理解
????????prefetchDistance : 在结束项之前的多少项之前开始加载下一项数据,这也就是为什么paging3可以一直往下加载的原因。
????????enablePlaceholders:是否显示空占位符,默认位true。
????????initialLoadSize:初始化加载的大小,一般设置大于pageSize。
????????maxSize:我的理解是:最大的缓存数据的大小,官方给的设置:最小是prefetchDistance*2+pageSize,非必要参数,默认值是:MAX_SIZE_UNBOUNDED,页面永远不删除。
????????jumpThreshold:个人理解是:滑动距离超出加载项多大以后,放弃加载页面(暂不确定),非必要参数,默认值:Int.MIN_VALUE。
pagingSourceFactory:数据源工厂,里面放已经这是好的数据源即可。
这样数据的获取基本上就算完成了,接下来就是创建适配器以及往适配器中放入数据
4、创建适配器
Google本身已经为我们提供了基础的配置,这里我们只需要继承PagingAdapter即可,其中ViewHolder,onCreateViewHolder,onBindViewHolder,与通用的Adapter区别不大,这里不做过多的介绍,唯一的区别是DiffUtil.ItemCallback,这里做新旧数据的对比设置,但是也是固定的写法,一般不用做修改。
class RepoAdapter : PagingDataAdapter<Repo, RepoAdapter.ViewHolder>(COMPARATOR) {
companion object {
private val COMPARATOR = object : DiffUtil.ItemCallback<Repo>() {
override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean {
return oldItem == newItem
}
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name: TextView = itemView.findViewById(R.id.name_text)
val description: TextView = itemView.findViewById(R.id.description_text)
val starCount: TextView = itemView.findViewById(R.id.star_count_text)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.repo_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val repo = getItem(position)
if (repo != null) {
holder.name.text = repo.name
holder.description.text = repo.description
holder.starCount.text = repo.starCount.toString()
}
}
}
这里我直接放的是郭神的适配器,写的很明白。下面是我学习的框架中,人家自己封装的适配器,我也列出来。
abstract class BasePagingAdapter<T : Any>(private var diffCallback: DiffUtil.ItemCallback<T>) :
PagingDataAdapter<T, RecyclerView.ViewHolder>(diffCallback) {
companion object {
private const val TAG = "BasePagingAdapter"
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position) ?: return
(holder as BasePagingAdapter<*>.BaseViewHolder).bindNormalData(item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val holder = BaseViewHolder(parent, viewType)
//Item的点击事件
holder.itemView.setOnClickListener {
onItemClick(getItem(holder.layoutPosition))
}
return holder
}
override fun getItemViewType(position: Int): Int {
return getItemLayout(position)
}
/**
* 子类获取layout
*/
protected abstract fun getItemLayout(position: Int): Int
/**
* itemView的点击事件,子类实现
*/
protected abstract fun onItemClick(data: T?)
/**
* 子类绑定数据
*/
protected abstract fun bindData(helper: ItemHelper, data: T?)
inner class BaseViewHolder(parent: ViewGroup, layout: Int) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false)
) {
private val helper: ItemHelper = ItemHelper(this)
fun bindNormalData(item: Any?) {
bindData(helper, item as T)
}
}
/**
* ItemView的辅助类
*/
class ItemHelper(private val holder: BasePagingAdapter<*>.BaseViewHolder) {
private val itemView = holder.itemView
private val viewCache = SparseArray<View>()
private fun findViewById(viewId: Int): View {
var view = viewCache.get(viewId)
if (view == null) {
view = itemView.findViewById(viewId)
if (view == null) {
throw NullPointerException("$viewId can not find this item!")
}
viewCache.put(viewId, view)
}
return view
}
/**
* TextView设置内容
*/
fun setText(viewId: Int, text: CharSequence?): ItemHelper {
(findViewById(viewId) as TextView).text = text
return this
}
/**
* Coil加载图片
*/
fun bindImgGlide(viewId: Int, url: String) {
val imageView: ImageView = findViewById(viewId) as ImageView
imageView.load(url) {
placeholder(R.mipmap.img_placeholder)
}
}
}
}
class RepoAdapter:BasePagingAdapter<Repo>(diffCallback) {
companion object{
val diffCallback = object : DiffUtil.ItemCallback<Repo>() {
override fun areItemsTheSame(
oldItem: Repo,
newItem: Repo
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: Repo,
newItem: Repo
): Boolean {
return oldItem == newItem
}
}
}
override fun getItemLayout(position: Int): Int = R.layout.repo_item
override fun onItemClick(data: Repo?) {
ARouter.getInstance()
.build(Constants.PATH_WEBVIEW)
.withString(KEY_WEBVIEW_PATH, data?.htmlUrl)
.withString(KEY_WEBVIEW_TITLE,data?.name)
.navigation()
}
override fun bindData(helper: ItemHelper, repo: Repo?) {
if (repo != null){
helper.setText(R.id.name_text, repo.name)
helper.setText(R.id.description_text, repo.description)
helper.setText(R.id.star_count_text, repo.starCount.toString())
}
}
}
5、页面中使用Paging3加载数据
开始就是Recyclerview的基础配置,获取View,设置layoutManager,设置适配器,最后是获取诗句后加载数据,使用adapter.submitData(data)。
mBinding?.homeRv?.layoutManager = LinearLayoutManager(context)
mBinding?.homeRv?.adapter =repoAdapter.withLoadStateFooter(FooterAdapter{repoAdapter.retry()})
lifecycleScope.launchWhenCreated {
mViewMode.getPagingData().collectLatest { data ->
repoAdapter.submitData(data)
}
}
这样基本上就大功告成了!
通过写本篇文章的总结,算是明白Paging3其实并不复杂,也不难懂,全是一些通用的代码,直接拿过来用就好,也算是明白Google提到的说是使用Paging3基本上只用关注本身的数据业务就可以了,不用关注分页的逻辑。
这里只是我自己总结的关于Paging3的基本应用,如果想了解更多关于Paging3的高级应用,可以去官网去看看。
|