Jetpack Paging3 基本使用
程序员文章站
2022-06-07 15:14:23
...
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.NotLoading
、LoadState.Loading
、LoadState.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】【数字图像处理】获取对应学习资料。
欢迎留言交流。