Android—Flow与Jetpack Paging3
前言
在上一篇中,主要讲解了Jetpack—Paging2的故事。因为Paging3改动较大,并且为了让更多人同时适应两个版本,因此在本篇中将会结合Flow与Paging3进行组合讲解。
因本篇主要围绕着Paging3以及与Paging2的区别进行讲解!
因此,本篇所需要知识点:
ViewBinding+DataBinding+Flow+ViewModel+KT+协程(不够的,可以看看之前写好的文章)
熟悉paging2者,阅读体验更佳(没有也可尝试看看)
1、回顾Jetpack—Paging2
回顾一下上一篇所讲解的Paging2!
是不是提到过三大核心类??
那么!Paging3的核心类呢?有哪些呢?
Paging3核心类之PagingDataAdapter(原PagedListAdpater)
Paging3核心类之PagingData(原PagedList)
Paging3核心类之PagingSource(原拥有三种格式的DataSource)
那么它们之间的改动就仅仅是命名上的改动么?
NO!NO!NO!要是真是如此,也不会单独另开一篇讲解了!
2、开讲Jetpack—Paging3
2.1 配置准备
plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' } ...略 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } buildFeatures { viewBinding = true } dataBinding { enabled = true } ...略 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' implementation "androidx.activity:activity-ktx:1.1.0" implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1' implementation 'androidx.paging:paging-runtime:3.0.0-alpha03' implementation 'com.squareup.picasso:picasso:2.71828' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' 复制代码
这里可以看到,我这里同时开启了ViewBinding与DataBinding
,然后引入了kotlin-kapt
插件和一系列库。
网络权限以及允许Http不可少
<uses-permission android:name="android.permission.INTERNET" /> <application ...略 android:networkSecurityConfig="@xml/network_security_config"> <activity android:name=".activity.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> 复制代码
这没啥说的
network_security_config.xml
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config> 复制代码
到这里准备工作做好了,现在依然按照上面顺序依次讲解Paging3!
2.2 PagingDataAdapter
class MovieAdapter(private val context: Context) : PagingDataAdapter<Movie, BindingViewHolder>(object : DiffUtil.ItemCallback<Movie>() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean { // kotlin === 引用 , == 内容 // Java == 引用, equals 内容 return oldItem == newItem } }) { override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { val movie = getItem(position) movie?.let { //DataBinding相关逻辑,这里不做详解 val binding = holder.binding as MovieItemBinding binding.movie = it binding.networkImage = it.cover } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { val binding = MovieItemBinding.inflate(LayoutInflater.from(context), parent, false) return BindingViewHolder(binding) } } 复制代码
我们可以看到,除了Item改为DataBinding外,对应的Adapter和之前差不多,也需要实现对应的差分比较。而且逻辑和之前得到相似。
不过对应的BindingViewHolder
为了等哈方便,因此单开了一个类(没有写成类部类)
class BindingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) 复制代码
结合ViewBinding,代码就这光溜溜的一句。
当然还有Json实体类
data class Movies( @SerializedName("subjects") val movieList: List<Movie>, @SerializedName("has_more") val hasMore: Boolean ) 复制代码
这里准备用上一篇的4.2格式的返回结构!
总结
这个核心类感觉和之前Paging2的使用效果差不多。
那看看下一个!
2.3 PagingData
class MovieViewModel : ViewModel() { private val movies by lazy { Pager( config = PagingConfig(pageSize = 8, prefetchDistance = 8, initialLoadSize = 16), pagingSourceFactory = { MoviePagingSource() }) .flow //转为Flow流 .cachedIn(viewModelScope) //使其生命周期与ViewModel相互绑定 } fun loadMovie(): Flow<PagingData<Movie>> = movies } 复制代码
代码解析:
我们这里可以看到在这通过懒加载lazy,实例化了Pager对象
pageSize
毋庸置疑,这里表示每页长度为多少prefetchDistance
预取距离,表示必须离加载内容边缘多远才能触发进一步加载。一般设置为大于0,小于等于pageSize
initialLoadSize
来自PagingSource 的初始加载定义请求的加载大小,通常大于pageSize ,因此在第一次加载数据时,加载的内容范围足够大以覆盖小滚动。并将对应的Page对象转为了Flow流,然后通过
cachedIn
使其生命周期与ViewModel相互绑定最后通过
loadMovie
方法,将对应的Flow返回出去
这里我们看到,在初始化Pager对象时,使用了pagingSourceFactory = { MoviePagingSource() }
指定了对应工厂为MoviePagingSource
因此
2.4 PagingSource
class MoviePagingSource : PagingSource<Int, Movie>() { //currentPage,pageSize //1,16 //3,8 //4,8 //prevKey,nextKey //null,3 //2,4 //3,5 override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> { delay(2000) //这里为了显示加载更多的效果所以特意挂起2秒 val currentPage = params.key ?: 1 val pageSize = params.loadSize val movies = RetrofitClient.createApi(MovieApi::class.java).getMovies(currentPage, pageSize) Log.d("hqk", "currentPage:$currentPage,pageSize:$pageSize") var prevKey: Int? = null var nextKey: Int? = null val realPageSize = 8 val initialLoadSize = 16 if (currentPage == 1) { prevKey = null nextKey = initialLoadSize / realPageSize + 1 } else { prevKey = currentPage - 1 nextKey = if (movies.hasMore) currentPage + 1 else null } Log.d("hqk", "prevKey:$prevKey,nextKey:$nextKey") return try { LoadResult.Page( data = movies.movieList, prevKey = prevKey, nextKey = nextKey ) } catch (e: Exception) { e.printStackTrace() return LoadResult.Error(e) } } } 复制代码
注意哟,这里重点来了!
还记得上面创建
PagingConfig
这个对象时所传入的参数吧!第三个参数
initialLoadSize = 16
表示首次将会加载16条数据(这里官方做法也比pageSize大,最好是pageSize的n倍)那么在这里第一页加载也就是首次加载,也会加载16条数据,而我们设置的
pageSize=8
,也就是说,首次加载第一页的时候,已经成功的加载了第一页和第二页数据,
因此在加载下一页的时候,就应该从第三页开始加载
//currentPage,pageSize //分别表示:当前页数,以及对应页数加载的个数 //1,16 //3,8 //4,8 //prevKey,nextKey //当前页数,下一页数,null表示首次加载第一页 //null,3 //2,4 //3,5 复制代码
也就是说:从第三页开始,后面的每一页的个数才是对应
pageSize
所设置的条数
再次注意《重点!!!》
这里一定要做对应处理,就算你没有设置
initialLoadSize
属性,官方默认在对应属性上扩大了3倍pageSize
。如果你没有处理
prevKey,nextKey
,那么你第二页和第三页加载的都是首次已经加载过的数据 !你将会在列表上看到有两页数据重复的bug!
来看看官方代码
@JvmField @IntRange(from = 1) //这里默认乘以3 val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER, 复制代码
接着,
2.5 来看看如何使用
class MainActivity : AppCompatActivity() { private val movieViewModel by viewModels<MovieViewModel>() private val mBinding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(mBinding.root) val movieAdapter = MovieAdapter(this) mBinding.apply { recyclerView.adapter = movieAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity)) swipeRefreshLayout.setOnRefreshListener { movieAdapter.refresh() } } lifecycleScope.launchWhenCreated { movieViewModel.loadMovie().collectLatest { movieAdapter.submitData(it) } } lifecycleScope.launchWhenCreated { movieAdapter.loadStateFlow.collectLatest { state -> mBinding.swipeRefreshLayout.isRefreshing = state.refresh is LoadState.Loading } } } } 复制代码
注意
这里使用
movieAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))
,设置了对应Adapter对应的加载尾(加载更多)为
MovieLoadMoreAdapter
最后来看看运行效果
哈哈哈哈,够具体吧,这样还不明白就来打我。坐标成都!!
Demo地址: 点我下载
结束语
好了,本篇到这里就结束了,相信看到这里的小伙伴已经对Jetpack-Paging3有了相当深刻的认知。下一篇将会结合Jetpack前面所讲解的知识点,整合成一个实战应用!
作者:hqk
链接:https://juejin.cn/post/7045304647153614878
玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/