阅读 230

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

最后来看看运行效果

2.png

哈哈哈哈,够具体吧,这样还不明白就来打我。坐标成都!!

Demo地址: 点我下载

结束语

好了,本篇到这里就结束了,相信看到这里的小伙伴已经对Jetpack-Paging3有了相当深刻的认知。下一篇将会结合Jetpack前面所讲解的知识点,整合成一个实战应用!


作者:hqk
链接:https://juejin.cn/post/7045304647153614878

玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐