Android Paging3的使用

Android Paging3的使用,第1张

本篇文章主要是自己对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 = 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  getService(serviceClass: Class): 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() {
    override fun getRefreshKey(state: PagingState): Int? = null

    override suspend fun load(params: LoadParams): LoadResult {
        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,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> {
        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(COMPARATOR) {

    companion object {
        private val COMPARATOR = object : DiffUtil.ItemCallback() {
            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(private var diffCallback: DiffUtil.ItemCallback) :
    PagingDataAdapter(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()

        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(diffCallback) {

    companion object{
        val diffCallback = object : DiffUtil.ItemCallback() {
            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的高级应用,可以去官网去看看。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992151.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存