Jetpack 之 Paging 3 小白入手

声明 : https://www.jianshu.com/p/714062a9af75
目录

简介
原理
使用方法

1, PagingSource 简单使用
2,添加尾布局,实现点击加载更多

简介:

  Paging 是Google 为了开发人员更方便的完成分页加载而设计的一个组件.
  这个库需要配合 kotlin 协程使用哦~
  

原理:

image.png

  1,在 RecycleView 的滑动过程中,会触发 PagingDataAdapter 类中的 onBindViewHodler()方法.数据与 RecycleView Item 布局中 UI 控件正是在这个方法中绑定的.
  2,在 RecycleView滑动到底部的时候,在 onBindViewHolder 的方法中所调用的 getItem() 方法会通知 PageList,当前需要载入更多数据.
  3,接着,PagedList 会根据 PagedList.Config 中的配置通知 PagingSource 执行具体的数据获取工作.
  4, PagingSource 从网络/本地数据库取得数据后,交给 PagedList,PagedList将持有这些数据.
  5,PagedList 将数据交给 PagedListAdapter 中的 DiffUtil 进行的比对和处理.
  6,数据在经过处理后,交给 RecycleView进行展示.

使用方法:

ps:本文内容涉及到了 kotlin 的协程功能.

1, build.gradle
    implementation "androidx.paging:paging-runtime:3.0.0"
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

  这里我引用了 Retrofit 库,因为我要进行网络请求数据,通过Paging 3进行分页展示.
  使用的 api 是wanandroid 开放的 api.

https://wanandroid.com/article/listproject/{pageNum}/json

方法:GET
参数:页码,拼接在连接中,从0开始。
2,创建 model
data class WnaAndroidBean(
      val data: Data,
      val errorCode: Int,
      val errorMsg: String
)

data class Data(
      val curPage: Int,
      val datas: List,
      val offset: Int,
      val over: Boolean,
      val pageCount: Int,
      val size: Int,
      val total: Int
)

data class SubData(
      val desc: String,
      val title: String,
      val author: String,
)

  我这是精简版的,源数据很多,但是用不到,就挑了几个使.

3,创建ApiService
interface ApiService {

    @GET("article/listproject/{pageNum}/json")
    suspend fun searchProject(@Path("pageNum") pageNum: Int): WnaAndroidBean
    
    companion object {
         var apiService: ApiService = Retrofit.Builder()
            .baseUrl("https://wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
    
}

  suspend 修饰符和挂起函数,不可缺少哦

4,定义数据源,创建WanAndroidDataSource
class WanAndroidDataSource(private val apiService: ApiService) : PagingSource() {

    override suspend fun load(params: LoadParams): LoadResult {
        return try {
            var pageNum = params.key ?: 1  //获取当前的页数,若为空,默认值为 1
            // var loadSize = params.loadSize //每一页包含多少条数据,可以设置固定,也可以灵活设置,我们这个项目,暂时用不到
            var searchProject = apiService.searchProject(pageNum)   //请求数据
            var datas = searchProject.data.datas
            val prevKey = if (pageNum > 1) pageNum - 1 else null  ///上一页
            val nextKey = if (!searchProject.data.over) pageNum + 1 else null  //下一页
            LoadResult.Page(datas, prevKey, nextKey)
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    //实现必须定义如何从已加载分页数据的中间恢复刷新,使用 state.anchorPosition 作为最近访问的索引来映射正确的初始键。
    //高级用法,和本文无关
    override fun getRefreshKey(state: PagingState): Int? = null
}

   PagingSource 对象可以从任何单个数据源(包括网络来源和本地数据库)加载数据.
   该类继承 PagingSource 需要声明两个泛型 。key 定义了用于加载数据的标识符,value 是数据本身的类型。例如,如果您通过将 Int 页码传递给 Retrofit 来从网络加载各页 SubData 对象,则应选择 Int 作为 Key 类型,选择 SubData 作为 Value 类型。
   LoadParams 对象包含有关要执行的加载操作的信息,其中包括要加载的键和要加载的项数
   load () 是一个挂起函数,处理数据请求, params.key 代表当前页, params.loadSize 代表加载的数据量,最后调用 LoadResult.Page() 函数返回 LoadResult 对象.三个参数,分别是数据列表,上一个,下一页
   getRefreshKey() 函数,当数据在初始加载后刷新或失效时,该方法会返回要传递给 load() 方法的键。在后续刷新数据时,Paging 库会自动调用此方法。本文暂时用不到.

5,创建 WanAndroidViewModel 设置 PagingData.
class WanAndroidViewModel : ViewModel() {

    fun getPageData(): Flow> {
        return Pager(
            config = PagingConfig(10),
            pagingSourceFactory = { WanAndroidDataSource(ApiService.apiService) }).flow.cachedIn(
            viewModelScope
        )
    }

}

   PagingData 对象是用于存放分页数据的容器,它会查询 PagingSource 对象并存储结果。
   Pager 类提供的方法可显示来自 PagingSource 的 PagingData 对象的响应式流.
   这里用到了 kotlin 的协程技术,写法是固定的,实现Flow>,接口XXXXX 是灵活的,替换成列表展示的实例.返回Flow 对象.
   创建Pager对象的时候,需要传递pageSize,也就是我们每页加载多少数据和pagingSourceFactory,也就是我们之前创建好的 WanAndroidDataSource.
   Flow 提供了一个方法 cachedIn() 使数据流可共享,并使用提供的 CoroutineScope 缓存加载的数据。此示例使用 Lifecycle lifecycle-viewmodel-ktx 工件提供的 viewModelScope.这样一来,当手机屏幕发生旋转时,会从缓存中读取数据,不会发起网络请求.

6,创建PagingAdapter
class PagingAdapter : PagingDataAdapter(DATA_COMPARATOR) {
    
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        var item = getItem(position)
        if (item != null) {
            holder.author.text = item.author
            holder.tile.text = item.title
            holder.desc.text = item.desc
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_paging, parent, false)
        )
    }

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val author: TextView = itemView.findViewById(R.id.textView7)
        val tile: TextView = itemView.findViewById(R.id.textView8)
        val desc: TextView = itemView.findViewById(R.id.textView9)
    }

 
}

object DATA_COMPARATOR : DiffUtil.ItemCallback() {
    //检查两个对象是否代表同一个项目
    override fun areItemsTheSame(oldItem: SubData, newItem: SubData): Boolean {
        return oldItem.title == newItem.title
    }

    //检查两个项目是否具有相同的数据
    override fun areContentsTheSame(oldItem: SubData, newItem: SubData): Boolean {
        return oldItem == newItem
    }
}
}

   该类继承 PagingDataAdapter ,实现 onCreateViewHolder()onBindViewHolder() 方法,并指定 DiffUtil.ItemCallback
   DiffUtil.ItemCallback 用于计算列表中两个非空项目之间差异的回调,之前我们刷新,都用notifyDataSetChanged()全部刷新一遍,现在用 DiffUtil 比对新、旧两个数据集的差异,生成旧数据到新数据的最小变动,然后对有变动的数据项,进行局部刷新。
   areItemsTheSame() 判断对象是否一致,如果不一样,则直接更新数据,如果一样,areContentsTheSame() 则判断对象内容是否一样.如果一样,不更新做额外的操作,如果不一样,则更新数据.
   强烈推荐 DiffUtil.ItemCallback学习连接: https://segmentfault.com/a/1190000010722635

7,编写布局文件 activity_paging.xml 自己 item 的布局文件 item_paging.xml


    

  只有一个 RecycleView,很简单



    

    

    
    


   创建三个 TextView,分别展示 author ,title ,desc 三个字段.

8,创建PagingActivity
class PagingActivity : AppCompatActivity() {
    var pagingAdapter = PagingAdapter()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_paging)
        recycler_view.layoutManager = LinearLayoutManager(this)
        recycler_view.adapter = pagingAdapter
        lifecycleScope.launchWhenCreated {
            WanAndroidViewModel().getPageData().collectLatest {
                pagingAdapter.submitData(it)
            }
        }
        pagingAdapter.addLoadStateListener {
            when (it.refresh) {
                is LoadState.NotLoading -> {
                    Log.e("mmm","没有加载,加载数据前和加载数据完成后回调")
                }
                is LoadState.Loading -> {
                    Log.e("mmm", "正在加载")
                }
                is LoadState.Error -> {
                    val state = it.refresh as LoadState.Error
                    Log.e("mmm", "加载错误$state")
                }
            }
        }
    }
}

   pagingAdapter.submitData(it)是一个协程挂起(suspend)操作,所以要放入协程赋值.所以要放在 lifecycleScope.launch()函数来启动一个协程
   submitData()要接受 PagingData 类型的参数,,调用WanAndroidViewModel().getPageData().方法返回Flow 对象的collectLatest()函数才能获取到.
   collectLatest() 是末端操作符,收集 Flow 在 ViewModel 层发射出来的数据,在一段时间内发送多次数据,只会接受最新的一次发射过来的数据.
   pagingAdapter.addLoadStateListener 添加状态监听.

好了到此为止,可以运行程序啦啦 ✿✿ヽ(°▽°)ノ✿~~~~

运行截图:


E2F047B5C0A7A8670B6E05EC6AA7663D.gif
添加尾布局,比如我们加载失败或者网络不好,我们希望有个提示~~~~
1,编写item_foot.xml


    

  布局很简单,就一个按钮.

2,创建FooterAdapter
class FooterAdapter(private val retryLoad: () -> Unit) :
    LoadStateAdapter() {
    
    override fun onBindViewHolder(holder: MyViewHolder, loadState: LoadState) {
        holder.goon.isVisible = loadState is LoadState.Error
        holder.goon.setOnClickListener {
            retryLoad()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): MyViewHolder {
        return MyViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_foot, parent, false)
        )
    }

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val goon: Button = itemView.findViewById(R.id.goon)
    }

}

   该类继承 LoadStateAdapter
   我们用 kotlin 高级函数给按钮添加点击事件,当按钮点击是,会执行传入的函数.

3, 修改PagingActivity
class PagingActivity : AppCompatActivity() {
    var pagingAdapter = PagingAdapter()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_paging)
        recycler_view.layoutManager = LinearLayoutManager(this)
        //最重要的是下面这一句
        recycler_view.adapter = pagingAdapter.withLoadStateFooter(FooterAdapter { pagingAdapter.retry() })
         //.....省略部分代码
    }
}

   调用pagingAdapter.withLoadStateFooter()函数把 FooterAdapter 传进去.
   { pagingAdapter.retry() }是Lambda 表达方式传入FooterAdapter的参数.

运行截图:


26C7F077AD1279B602A529B5A7656B56.gif

END

image.png

你可能感兴趣的:(Jetpack 之 Paging 3 小白入手)