Jetpack Paging3 基本使用

Paging3

Paging3,是Jetpack提供给开发者用来显示本地或者网络数据集的分页库。针对这类场景,传统的做法是用RecyclerView的加载更多来实现分页加载,很多逻辑需要自行处理且不一定完善。Paging3相当于是官网提供的一套解决方案。

特点

  • 每一页的数据会缓存至内存中,以此保证处理分页数据时更有效的使用系统资源
  • 内置请求重复数据删除功能,确保应用有效地使用网络带宽和系统资源
  • 支持Kotlin协程、Flow、LiveData以及RxJava
  • 内置错误处理支持,如刷新和重试功能。

逻辑图

官方

配置

]00io 
`	`123dependencies {
  def paging_version = "3.0.0-alpha03"

  implementation "androidx.paging:paging-runtime:$paging_version"

  // alternatively - without Android dependencies for tests
  testImplementation "androidx.paging:paging-common:$paging_version"

  // optional - RxJava2 support
  implementation "androidx.paging:paging-rxjava2:$paging_version"

  // optional - Guava ListenableFuture support
  implementation "androidx.paging:paging-guava:$paging_version"

基本使用

基本使用主要包含如下内容:

  • 配置数据源:PagingSource
  • 构建分页数据:Pager、PagingData
  • 构建RecyclerView Adapter:PagingDataAdapter
  • 展示分页UI列表数据
  • 加载状态
  • 预取阈值

配置数据源

abstract class PagingSource<Key : Any, Value : Any> 

Key:分页标识类型,如页码,则为Int

Value:返回列表元素的类型,如需要分页的是文章数据,则值应该为文章对象。下面以WanAndroid的接口为例。

class ArticleDataSource : PagingSource<Int, Article>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
        return try {
            val page = params.key ?: 0
            //获取网络数据
            val result = Retrofitance.instance.wanAndroidApi.getHomeArticles(page)
            LoadResult.Page(
                //需要加载的数据
                data = result.data.data,
                //如果可以往上加载更多就设置该参数,否则不设置
                prevKey = null,
                //加载下一页的key 如果传null就说明到底了
                nextKey = if (result.data.curPage == result.data.pageCount) null else page + 1
            )
        } catch (e: IOException) {
            // IOException for network failures.
            return LoadResult.Error(e)
        } catch (e: HttpException) {
            // HttpException for any non-2xx HTTP status codes.
            return LoadResult.Error(e)
        }
    }
}
interface WanAndroidApi {
    @GET("article/list/{pageNum}/json")
    suspend fun getHomeArticles(@Path("pageNum") pageNum: Int): BaseResp<Article>
}
class Retrofitance {
    private val okHttpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build()
    }

    private val retrofit: Retrofit by lazy {
        Retrofit.Builder().baseUrl("https://www.wanandroid.com/")
            .client(okHttpClient)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    }

    val wanAndroidApi: WanAndroidApi by lazy {
        retrofit.create(WanAndroidApi::class.java)
    }

    companion object {
        val instance: Retrofitance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Retrofitance()
        }
    }
}

构建分页数据

class PagingViewModel : ViewModel() {
    fun getArticleData() = Pager(PagingConfig(pageSize = 10)) {
        ArticleDataSource()
    }.flow.cachedIn(viewModelScope)
}

构建RecyclerView Adapter

class ArticleAdapter : PagingDataAdapter<Article, BindingViewHolder>(Article.DiffCallback) {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val binding = ItemArticleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
        val binding = holder.binding as ItemArticleBinding
        binding.data = getItem(position)
    }
}
class BindingViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)

展示分页列表数据

class PagingActivity : AppCompatActivity() {

    private val viewModel by viewModels<PagingViewModel>()

    private val adapter: ArticleAdapter by lazy { ArticleAdapter() }

    private val mBinding: ActivityPagingBinding by ActivityBinding(R.layout.activity_paging)


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            viewModel.getArticleData().collectLatest { pagingData ->
                adapter.submitData(pagingData)
            }
        }
    }
}

加载状态

LoadState分为LoadState.NotLoadingLoadState.LoadingLoadState.Error三种状态。而处理PagingAdapter的加载状态的方式有两种:

  • 使用监听器获取并处理加载状态
  • 使用PagingAdapter处理加载状态

使用监听器

adapter.addLoadStateListener { loadState ->
    Toast.makeText(
        this,
        "\uD83D\uDE28  $loadState",
        Toast.LENGTH_LONG
    ).show()
}

使用PagingAdapter

官方提供了工具类LoadStateAdapter给开发者处理加载状态的问题

class ArticleLoadStateAdapter(
    private val adapter: ArticleAdapter
) : LoadStateAdapter<BindingViewHolder>() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ): BindingViewHolder {
        val binding = ItemFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) {
        val binding = holder.binding as ItemFooterBinding

        when (loadState) {
            is LoadState.Error -> {
                binding.loading.visibility = View.GONE
                binding.loadingMsg.visibility = View.VISIBLE
                binding.loadingMsg.text = "Load Failed, Tap Retry"
                binding.loadingMsg.setOnClickListener {
                    adapter.retry()
                }
            }
            is LoadState.Loading -> {
                binding.loading.visibility = View.VISIBLE
                binding.loadingMsg.visibility = View.VISIBLE
                binding.loadingMsg.text = "Loading"
            }
            is LoadState.NotLoading -> {
                binding.loading.visibility = View.GONE
                binding.loadingMsg.visibility = View.GONE
            }
        }
    }
}
mBinding.list.adapter = adapter.withLoadStateHeaderAndFooter(
    ArticleLoadStateAdapter(adapter),
    ArticleLoadStateAdapter(adapter)
)
方法 使用
fun withLoadStateHeader(header: LoadStateAdapter<*> ) 头部添加状态适配器
fun withLoadStateFooter(footer: LoadStateAdapter<*> ) 底部添加状态适配器
fun withLoadStateHeaderAndFooter(header: LoadStateAdapter<*>, footer: LoadStateAdapter<*> ) 头尾都添加状态适配器

预取阈值

构建Pager时,使用到PagingConfig,其中有一个属性值为prefetchDistance,用于表示距离底部多少条数据开始预加载,设置0则表示滑到底部才加载。默认值为分页大小。若要让用户对加载无感,适当增加预取阈值即可。比如调整到分页大小的5倍。

class PagingViewModel : ViewModel() {
    fun getArticleData() = Pager(PagingConfig(pageSize = 10, prefetchDistance = 50)) {
        ArticleDataSource()
    }.flow.cachedIn(viewModelScope)
}

效果

预取阈值小

加载

预取阈值大

无限列表

下滑加载更多

下滑加载

源码

https://github.com/onlyloveyd/AndroidPractice

扫码关注,持续更新

回复【计算机视觉】【Android】【Flutter】【数字图像处理】获取对应学习资料。
欢迎留言交流。

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【Android or OpenCV】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注技术与研发。
©️2020 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值