欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Jetpack Paging3 基本使用

程序员文章站 2022-06-07 15:14:23
...

Paging3

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

特点

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

逻辑图

Jetpack Paging3 基本使用

配置

]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)
}

效果

预取阈值小

Jetpack Paging3 基本使用

预取阈值大

Jetpack Paging3 基本使用

下滑加载更多

Jetpack Paging3 基本使用

源码

https://github.com/onlyloveyd/AndroidPractice

扫码关注,持续更新

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