Jetpack - Paging

一、概念

相对于传统的下拉刷新上拉加载,只需要告诉Paging如何加载数据,不用再监听滑动事件操作何时加载下一页。

二、基本使用

2.1 添加依赖

查看官方最新版本

implementation "androidx.paging:paging-runtime:3.1.1"

2.2 定义数据源 PagingSource

自定义一个类继承 PagingSource 并重写 load() 来提供获取页面数据。
/**
 * 参数一:页码的类型。参数二:item的类型(注意不是表)。
 * PagingSource会在ViewModel中调用,ViewModel一般写法会创建Repository实例,
 * 由于Repository中对于数据来源进行了封装,因此构造传入Repository实例来调用获取数据的方法会更好。
 */
class MyPagingSource(
    private val repository: MyRepository
) : PagingSource<Int, HotNewestArticleBean.HotNewestArticle.Article>() {    //参数一页码,参数二API返回数据对应的实体类
    private val startPage = 0   //API的默认开始页码
    //提供对应页面的数据(分页逻辑)
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, HotNewestArticleBean.HotNewestArticle.Article> {
        return try {
            //拿到当前页码(为null就设置为默认开始页码)
            val currentPage = params.key ?: startPage
            //当前页获取数据逻辑(数据源在内部自己管理,而不是在Adapter中了)
            val data = getData(currentPage)
            //上一页页码(当前页是第一页,上一页就返回null)
            val prevKey = if (currentPage > startPage) currentPage - 1 else null
            //下一页页码(当前页是最后一页,下一页就返回null)
            val nextKey = if (data.isNotEmpty()) currentPage + 1 else null
            //返回结果
            LoadResult.Page(data, prevKey, nextKey)
        } catch (e: Exception) {
            //返回错误
            LoadResult.Error(e)
        }
    }

    //控制刷新时,从最后请求页面加载,还是设为null从第一页加载。
    //例如当前已经请求了1-4页,可以通过设置在刷新后会加载5-8页数据,而1-4页数据都没了。
    override fun getRefreshKey(state: PagingState<Int, HotNewestArticleBean.HotNewestArticle.Article>): Int? {
        return null
    }

    //具体获取数据的方法。这里能更细分的对异常处理,否则在load()中合并返回后在UI中难区分。
    //但处理后还是要抛出异常,不然load()不会返回异常,影响UI中对Paging状态判断
    private suspend fun getData(currentPage: Int): List<HotNewestArticleBean.HotNewestArticle.Article> {
        var data: List<HotNewestArticleBean.HotNewestArticle.Article> = emptyList()
        runCatching {
            repository.getData(currentPage.toString())  //通过仓库获取数据,
        }.onSuccess { response ->
            response.getData().onSuccess {
                data = it.datas
            }.onFailure {
                Log.e("服务器错误", it.message.toString())
                throw Exception("服务器错误:${it.message}")
            }
        }.onFailure {
            Log.e("本地错误", it.message.toString())
            throw Exception("本地错误:${it.message}")
        }
        return data
    }
}

2.3 ViewModel

Pager会调用PagingSource的load( )方法获取数据,每个PagingData代表一页的数据。
class MyViewModel : ViewModel() {
    private val repository = Drawer3Repository()
    val dataLiveData by lazy {
        Pager(PagingConfig(pageSize = 15)) {
            MyPagingSource(repository)
        }.liveData.cachedIn(viewModelScope)
    }
    //返回值Flow<PagingData<HotNewestArticleBean.HotNewestArticle.Article>>
    //PagingData<>里面包含的是item类型,这里用的是Flow,也可以用上面的LiveData
    //通过PagingConfig配置一页加载的item数量(pageSize)
    fun getData() = Pager(PagingConfig(pageSize = 15)) {
            MyPagingSource(repository)
        }.flow.cachedIn(viewModelScope) //将数据在ViewModel中缓存,横竖屏切换后Paging能从缓存中读取数据而不是重新联网请求。
}

2.4 PagingDataAdapter

//比较器DIFFCALLBACK通过伴生对象DiffUtil实现
//不需要传数据源进来,不需要实现条目数量,这些在PagingSource中进行
class MyPagingAdapter : PagingDataAdapter<HotNewestArticleBean.HotNewestArticle.Article, RecyclerView.ViewHolder>(DIFFCALLBACK) {
    lateinit var binding: Drawer3ItemBinding

    companion object {
        private val DIFFCALLBACK = object : DiffUtil.ItemCallback<HotNewestArticleBean.HotNewestArticle.Article>() {
            override fun areItemsTheSame(oldItem: HotNewestArticleBean.HotNewestArticle.Article, newItem: HotNewestArticleBean.HotNewestArticle.Article): Boolean {
                return oldItem.id == newItem.id
            }
            override fun areContentsTheSame(oldItem: HotNewestArticleBean.HotNewestArticle.Article, newItem: HotNewestArticleBean.HotNewestArticle.Article): Boolean {
                return oldItem == newItem
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val myViewHolder = holder as MyViewHolder
        val article = getItem(position) //拿到bean
        if (article != null) {
            val title = Html.fromHtml(article.title).toString()
            myViewHolder.tvTitle.text = title
            myViewHolder.tvTime.text = article.niceDate
            val author = article.author
            val shareUser = article.shareUser
            val superChapterName = article.superChapterName
            if (author.isEmpty()) {
                myViewHolder.tvUserName.text = String.format("%s · %s", superChapterName, shareUser)
            } else {
                myViewHolder.tvUserName.text = String.format("%s · %s", superChapterName, author)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        binding = ItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MyViewHolder()
    }

    inner class MyViewHolder : RecyclerView.ViewHolder(binding.root) {
        var tvTitle = binding.tvTitle
        var tvUserName = binding.tvUsername
        var tvTime = binding.tvTime
    }
}

2.5 UI

    private fun initView() {
        val progressBar = binding.progressBar
        val recyclerView = binding.recyclerView
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = MyPagingAdapter()
        recyclerView.adapter = adapter
        //设置加载状态监听(也可以写成adapter.loadStateFlow.collectLatest{}对it进行分类)
        adapter.addLoadStateListener {
            //it.refresh:在初始化刷新的使用(也就是说第二页第三页...是监听不到的)
            //it.append:在加载更多的时候使用
            //it.prepend:在当前列表头部添加数据的时候使用
            when (it.refresh) {
                //当没有加载动作并且没有错误的时候
                is LoadState.NotLoading -> {
                    progressBar.visibility = View.INVISIBLE
                    recyclerView.visibility = View.VISIBLE
                }
                //正在加载
                is LoadState.Loading -> {
                    progressBar.visibility = View.VISIBLE
                    recyclerView.visibility = View.INVISIBLE
                }
                //加载错误(这里的错误是PagingSource里捕获的)
                is LoadState.Error -> {
                    val state = it.refresh as LoadState.Error
                    progressBar.visibility = View.INVISIBLE
                    showToast("adapter报错: ${state.error.message}")
                }
            }
        }
    }

    private fun byFlow() {
        lifecycleScope.launch {
            viewModel.getData().collect { pagingData ->
                adapter.submitData(pagingData)  //提交数据后Paging就开始工作了
            }
        }
    }

//    private fun byLiveData() {
//        viewModel.dataLiveData.observe(this) { pagingData ->
//            lifecycleScope.launch {
//                adapter.submitData(pagingData)
//            }
//        }
//    }

三、添加Footer、Header

//通过构造传入重试的方法,在UI中直接传入PagingAdapter.retry()
class Drawer3FooterAdapter(val retry: () -> Unit) : LoadStateAdapter<RecyclerView.ViewHolder>() {
    private lateinit var binding: Drawer3FooterBinding

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, loadState: LoadState) {
        val footViewHolder = holder as FootViewHolder
        //根据LoadState状态来控制脚部界面显示(加载/重试)
        footViewHolder.progressBar.isVisible = loadState is LoadState.Loading
        footViewHolder.retryButton.isVisible = loadState is LoadState.Error
        footViewHolder.retryButton.setOnClickListener {
            retry
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): RecyclerView.ViewHolder {
        binding = Drawer3FooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return FootViewHolder()
    }

    inner class FootViewHolder : RecyclerView.ViewHolder(binding.root) {
        val progressBar = binding.progressBar
        val retryButton = binding.retryButton
    }
}
adapter = MyPagingAdapter()
val concatAdapter = adapter.withLoadStateFooter(Drawer3FooterAdapter { adapter.retry() })  //添加脚部
recyclerView.adapter = concatAdapter

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值