Android Paging3的使用

本篇文章主要是自己对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的高级应用,可以去官网去看看。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Paging3 是一个用于在 Android 应用中实现分页加载数据的开源库。它是 Google 官方发布的最新版本的 Paging 库,相较于以往的版本,Paging3 在实现简单、功能强大和性能优化方面有了很大的改进。 首先,Android Paging3 提供了强大的数据加载和显示机制。它通过将数据分割成固定大小的数据块 (page),并在需要时按需加载和展示数据,实现了无限滚动加载的效果。相较于传统的 RecyclerView 分页加载,Paging3 更加灵活,可以自动处理数据的加载和卸载,无需手动实现判断是否到底部、加载更多等繁琐逻辑。同时,Paging3 还支持局部刷新、数据源无缝替换等操作,让数据的加载和显示更加简单和高效。 其次,Paging3 在性能方面进行了优化。它使用了异步数据加载和显示机制,可以在后台线程上进行数据加载,不会阻塞主线程。同时,Paging3 采用了数据预加载和缓存策略,可以将下一页的数据提前加载到内存中,从而提高用户体验和应用的响应速度。并且,Paging3 还支持数据的持久化存储,可以将加载的数据缓存到本地数据库或文件中,避免了重复加载数据的开销。 最后,Paging3 还提供了丰富的扩展功能和灵活的定制选项。开发者可以自定义数据加载策略、数据源类型、数据显示方式等,以满足不同的业务需求。同时,Paging3 还提供了相关的辅助类和工具方法,帮助开发者更加便捷地实现数据的分页加载和显示。 总结来说,Android Paging3 是一个功能强大、性能优越的分页加载库,可以帮助开发者轻松实现数据的分页加载和显示,提高应用的用户体验和性能表现。无论是处理大量数据的列表页,还是实现无限滚动加载的功能,Paging3 都是一个值得推荐的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值