技術(shù)不止绊谭,文章有料越锈,加
JiuXinDev
入群宠进,Android 搬磚路上不孤單
前言
又到了學(xué)習(xí) Android Jetpack 的時(shí)間了,之前我已經(jīng)寫(xiě)過(guò)了一篇《即學(xué)即用Android Jetpack - Paging》绘梦,不過(guò)Android Jetpack 一直都在更新,這不赴魁,Paging 2 已經(jīng)升級(jí)到了 Paging 3卸奉,那么,大家可能有很多要關(guān)注的問(wèn)題颖御,比如:
- Paging 3 到底升級(jí)了那些東西榄棵?
- 如何使用最新的 Paging 3?
這是我本期要和大家討論的東西。本期的最終效果:
如果想學(xué)習(xí) Android Jetpack疹鳄,可以參考我之前的文章《學(xué)習(xí)Android Jetpack? 實(shí)戰(zhàn)和教程這里全都有拧略!》,這已經(jīng)是 《即學(xué)即用Android Jetpack》系列的第八篇啦~
本篇的源碼地址:【Hoo】
目錄
一瘪弓、Paging 2 和 Paging 3 有什么不同垫蛆?
如果你沒(méi)有使用過(guò) Paging 2,那么你可以跳過(guò)本章節(jié)(友情提醒~)腺怯。
如果你使用過(guò) Paging 2袱饭,你會(huì)發(fā)現(xiàn) Paging 3 簡(jiǎn)直是大刀闊斧,很多 API 的使用方式都變了呛占,簡(jiǎn)單說(shuō)一下主要改變的東西:
- 支持 Kotlin 中的 Flow虑乖。
- 簡(jiǎn)化數(shù)據(jù)源
PagingSource
的實(shí)現(xiàn)。 - 增加請(qǐng)求數(shù)據(jù)時(shí)狀態(tài)的回調(diào)晾虑,支持設(shè)置 Header 和 Footer决左。
- 支持多種方式請(qǐng)求數(shù)據(jù),比如網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)庫(kù)請(qǐng)求走贪。
二佛猛、介紹
友情提示
官方文檔:Paging 3
谷歌實(shí)驗(yàn)室:官方教程
官方Demo:Github倉(cāng)庫(kù)查詢(xún)Demo
1. 定義
看一下官方的定義:
The Paging library helps you load and display pages of data from a larger dataset from local storage or over network.
就是幫助你采用分頁(yè)的方式解決顯示包含本地?cái)?shù)據(jù)庫(kù)和網(wǎng)絡(luò)的大數(shù)據(jù)集的問(wèn)題。
2. 優(yōu)點(diǎn)
Paging 3 的優(yōu)點(diǎn):
- 使用內(nèi)存幫你緩存數(shù)據(jù)坠狡。
- 內(nèi)置請(qǐng)求去重继找,幫助你更有效率的顯示數(shù)據(jù)。
- 自動(dòng)發(fā)起請(qǐng)求當(dāng)
RecyclerView
滑到底部的時(shí)候逃沿。 - 支持 Kotlin 中的
協(xié)程
和Flow
婴渡,還有LiveData
和RxJava2
。 - 內(nèi)置狀態(tài)處理凯亮,包括刷新边臼、錯(cuò)誤、加載等狀態(tài)假消。
3. 幾個(gè)重要的類(lèi)
先看一下結(jié)構(gòu):
里面幾個(gè)類(lèi)的作用:
-
PagingSource
:?jiǎn)我坏臄?shù)據(jù)源柠并。 -
RemoteMediator
:其實(shí)RemoteMediator
也是單一的數(shù)據(jù)源,它會(huì)在PagingSource
沒(méi)有數(shù)據(jù)的時(shí)候富拗,再使用RemoteMediator
提供的數(shù)據(jù)臼予,如果既存在數(shù)據(jù)庫(kù)請(qǐng)求,又存在網(wǎng)絡(luò)請(qǐng)求啃沪,通常PagingSource
用于進(jìn)行數(shù)據(jù)庫(kù)請(qǐng)求粘拾,RemoteMediator
進(jìn)行網(wǎng)絡(luò)請(qǐng)求。 -
PagingData
:?jiǎn)未畏猪?yè)數(shù)據(jù)的容器创千。 -
Pager
:用來(lái)構(gòu)建Flow<PagingData>
的類(lèi)缰雇,實(shí)現(xiàn)數(shù)據(jù)加載完成的回調(diào)入偷。 -
PagingDataAdapter
:分頁(yè)加載數(shù)據(jù)的RecyclerView
的適配器。
簡(jiǎn)述一下就是 PagingSource
和 RemoteMediator
充當(dāng)數(shù)據(jù)源的角色械哟,ViewModel
使用 Pager
中提供的 Flow<PagingData>
監(jiān)聽(tīng)數(shù)據(jù)刷新盯串,每當(dāng) RecyclerView
即將滾動(dòng)到底部的時(shí)候,就會(huì)有新的數(shù)據(jù)的到來(lái)戒良,最后体捏,PagingAdapter
復(fù)雜展示數(shù)據(jù)。
三糯崎、實(shí)戰(zhàn)
為了簡(jiǎn)單起見(jiàn)几缭,先從單一的數(shù)據(jù)源開(kāi)始。
第一步 引入依賴(lài)
dependencies {
def paging_version = "3.0.0-alpha08"
implementation "androidx.paging:paging-runtime:$paging_version"
// 用于測(cè)試
testImplementation "androidx.paging:paging-common:$paging_version"
// [可選] RxJava 支持
implementation "androidx.paging:paging-rxjava2:$paging_version"
// ... 其他配置不重要沃呢,具體可以查看官方文檔
}
第二步 配置數(shù)據(jù)源
在 Paging 2
中年栓,里面有三種 Page Source
,開(kāi)發(fā)者使用的時(shí)候薄霜,還得去思考對(duì)應(yīng)的場(chǎng)景某抓,想的腦瓜子疼。
在 Paging 3
中惰瓜,如果只有單一的數(shù)據(jù)源否副,那么你要做的很簡(jiǎn)單,繼承 PagingSource
即可崎坊。
private const val SHOE_START_INDEX = 0;
class CustomPageDataSource(private val shoeRepository: ShoeRepository) : PagingSource<Int, Shoe>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Shoe> {
val pos = params.key ?: SHOE_START_INDEX
val startIndex = pos * params.loadSize + 1
val endIndex = (pos + 1) * params.loadSize
return try {
// 從數(shù)據(jù)庫(kù)拉去數(shù)據(jù)
val shoes = shoeRepository.getPageShoes(startIndex.toLong(), endIndex.toLong())
// 返回你的分頁(yè)結(jié)果备禀,并填入前一頁(yè)的 key 和后一頁(yè)的 key
LoadResult.Page(
shoes,
if (pos <= SHOE_START_INDEX) null else pos - 1,
if (shoes.isNullOrEmpty()) null else pos + 1
)
}catch (e:Exception){
LoadResult.Error(e)
}
}
}
正常用 LoadResult.Page()
返回結(jié)果,出現(xiàn)錯(cuò)誤的情況用 LoadResult.Error
返回錯(cuò)誤奈揍。
第三步 生成可觀察的數(shù)據(jù)集
這里可觀察數(shù)據(jù)集包括 LiveData
曲尸、Flow
以及 RxJava 中的 Observable
和 Flowable
,其中男翰,RxJava 需要單獨(dú)引入擴(kuò)展庫(kù)去支持的另患。
這里的可觀察數(shù)據(jù)集 shoes
使用的是 Flow
:
class ShoeModel constructor(private val shoeRepository: ShoeRepository) : ViewModel() {
/**
* @param config 分頁(yè)的參數(shù)
* @param pagingSourceFactory 單一數(shù)據(jù)源的工廠,在閉包中提供一個(gè)PageSource即可
* @param remoteMediator 同時(shí)支持網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)庫(kù)請(qǐng)求的數(shù)據(jù)源
* @param initialKey 初始化使用的key
*/
var shoes = Pager(config = PagingConfig(
pageSize = 20
, enablePlaceholders = false
, initialLoadSize = 20
), pagingSourceFactory = { CustomPageDataSource(shoeRepository) }).flow
// ... 省略
}
可以看到蛾绎,我們中了的 shoes: Flow<PagingData<Shoe>>
是由 Pager().flow
提供的昆箕。Pager
中的參數(shù)稍微解釋一下:
-
PagerConfig
可以提供分頁(yè)的參數(shù),比如每一頁(yè)的加載數(shù)秘通、初始加載數(shù)和最大數(shù)等为严。 -
pagingSourceFactory
和remoteMediator
都是數(shù)據(jù)源敛熬,我們使用其中的一個(gè)即可肺稀。
第四步 創(chuàng)建Adapter
和普通的 Adapter
沒(méi)有特別大的區(qū)別,主要是:
- 繼承
PagingDataAdapter
- 提供
DiffUtil.ItemCallback<Shoe>
實(shí)際代碼:
/**
* 鞋子的適配器 配合Data Binding使用
*/
class ShoeAdapter constructor(val context: Context) :
PagingDataAdapter<Shoe, ShoeAdapter.ViewHolder>(ShoeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ShoeRecyclerItemBinding.inflate(
LayoutInflater.from(parent.context)
, parent
, false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val shoe = getItem(position)
holder.apply {
bind(onCreateListener(shoe!!.id), shoe)
itemView.tag = shoe
}
}
/**
* Holder的點(diǎn)擊事件
*/
private fun onCreateListener(id: Long): View.OnClickListener {
return View.OnClickListener {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra(BaseConstant.DETAIL_SHOE_ID, id)
context.startActivity(intent)
}
}
class ViewHolder(private val binding: ShoeRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(listener: View.OnClickListener, item: Shoe) {
binding.apply {
this.listener = listener
this.shoe = item
executePendingBindings()
}
}
}
}
class ShoeDiffCallback: DiffUtil.ItemCallback<Shoe>() {
override fun areItemsTheSame(oldItem: Shoe, newItem: Shoe): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Shoe, newItem: Shoe): Boolean {
return oldItem == newItem
}
}
第五步 在UI中使用
先來(lái)簡(jiǎn)單一點(diǎn)的应民,如果只顯示數(shù)據(jù)话原,我們要做的是:
- 創(chuàng)建和設(shè)置適配器夕吻。
- 開(kāi)啟一個(gè)協(xié)程
- 在協(xié)程中接收
Flow
提供的數(shù)據(jù)。
我的代碼:
private fun onSubscribeUi(binding: ShoeFragmentBinding) {
binding.lifecycleOwner = this
// 初始化RecyclerView部分
val adapter = ShoeAdapter(context!!)
binding.recyclerView.adapter = adapter
job = viewModel.viewModelScope.launch(Dispatchers.IO) {
viewModel.shoes.collect() {
adapter.submitData(it)
}
}
// ... 省略
}
第六步 設(shè)置Header和Footer
顯然 Paging 3
還有更多的功能繁仁,對(duì)涉馅!它還支持添加 Header
和 Footer
,官方示例是把它們用作上拉刷新和下拉加載更多的控件黄虱。
不過(guò) Hoo 中并沒(méi)有使用 Header
和 Footer
稚矿,我們來(lái)看看官方怎么使用的:
- 創(chuàng)建一個(gè)
HeaderAdapter or FooterAdapter
繼承自LoadStateAdapter
,跟普通Adapter
不一樣的地方在于它在onBindViewHolder
方法中提供了LoadState
參數(shù)捻浦,它可以提供當(dāng)前Paging
是 Loading晤揣、NotLoading 和 Error 的狀態(tài)。 - 跟普通
Adapter
一樣創(chuàng)建自己需要的ViewHolder
朱灿。 - 稍微修改一下第五步中設(shè)置
ShoeAdapter
昧识,調(diào)用它的withLoadStateHeaderAndFooter
方法綁定Header
和Footer
的適配器:
private fun onSubscribeUi(binding: ShoeFragmentBinding) {
binding.lifecycleOwner = this
// 初始化RecyclerView部分
val adapter = ShoeAdapter(context!!).withLoadStateHeaderAndFooter(
header = ReposLoadStateAdapter { adapter.retry() },
footer = ReposLoadStateAdapter { adapter.retry() }
)
binding.recyclerView.adapter = adapter
// ... 省略
}
需要說(shuō)明一下,我這里使用的偽代碼盗扒。想要看詳細(xì)的教程跪楞,可以官網(wǎng)的這一步教程。
第七步 監(jiān)聽(tīng)數(shù)據(jù)加載的狀態(tài)
PagingDataAdapter
除了添加 Header
和 Footer
侣灶,還可以監(jiān)聽(tīng)數(shù)據(jù)的加載狀態(tài)甸祭,狀態(tài)對(duì)應(yīng)的類(lèi)是 LoadState
,它有三種狀態(tài):
-
Loading
:數(shù)據(jù)加載中褥影。 -
NotLoading
:內(nèi)存中有已經(jīng)獲取的數(shù)據(jù)淋叶,即使往下滑,Paging 也不需要請(qǐng)求更多的數(shù)據(jù)伪阶。 -
Error
:請(qǐng)求數(shù)據(jù)時(shí)返回了一個(gè)錯(cuò)誤煞檩。
監(jiān)聽(tīng)數(shù)據(jù)狀態(tài)的代碼:
adapter.addLoadStateListener {state:CombinedLoadStates->
//... 狀態(tài)監(jiān)聽(tīng)
}
監(jiān)聽(tīng)方法就是這么簡(jiǎn)單,可以看到這個(gè) state
并不是 LoadState
栅贴,而是一個(gè) CombinedLoadStates
斟湃,顧名思義爬虱,就是多個(gè) LoadState
組合而成的狀態(tài)類(lèi)驯杜,它里面有:
-
refresh:LoadState
:刷新時(shí)的狀態(tài),因?yàn)榭梢哉{(diào)用PagingDataAdapter#refresh()
方法進(jìn)行數(shù)據(jù)刷新磷醋。 -
append:LoadState
:可以理解為RecyclerView
向下滑時(shí)數(shù)據(jù)的請(qǐng)求狀態(tài)坛缕。 -
prepend:LoadState
:可以理解為RecyclerView
向上滑時(shí)數(shù)據(jù)的請(qǐng)求狀態(tài)墓猎。 -
source
和mediator
分別包含上面123的屬性,source
代表單一的數(shù)據(jù)源赚楚,mediator
代表多數(shù)據(jù)源的場(chǎng)景毙沾,source
和mediator
二選一。
解釋了這么多宠页,說(shuō)一下我的玩法左胞,下拉刷新我使用了第三方的刷新控件 SmartRefreshLayout寇仓,這就意味著我要自己處理 SmartRefreshLayout
的加載狀態(tài)。布局文件不貼了烤宙,就是 SmartRefreshLayout
+ RecyclerView
+ FloatingActionButton
遍烦。
成果圖(Gif有一點(diǎn)點(diǎn)問(wèn)題):
使用代碼就是:
private fun onSubscribeUi(binding: ShoeFragmentBinding) {
binding.lifecycleOwner = this
// 初始化RecyclerView部分
val adapter = ShoeAdapter(context!!)
// 數(shù)據(jù)加載狀態(tài)的回調(diào)
adapter.addLoadStateListener { state:CombinedLoadStates ->
currentStates = state.source
// 如果append沒(méi)有處于加載狀態(tài),但是refreshLayout出于加載狀態(tài)躺枕,refreshLayout停止加載狀態(tài)
if (state.append is LoadState.NotLoading && binding.refreshLayout.isLoading) {
refreshLayout.finishLoadMore()
}
// 如果refresh沒(méi)有出于加載狀態(tài)服猪,但是refreshLayout出于刷新?tīng)顟B(tài),refreshLayout停止刷新
if (state.source.refresh is LoadState.NotLoading && binding.refreshLayout.isRefreshing) {
refreshLayout.finishRefresh()
}
}
binding.recyclerView.adapter = adapter
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastPos = getLastVisiblePosition(binding.recyclerView)
if (!(lastPos == adapter.itemCount - 1 && currentStates?.append is LoadState.Loading)) {
binding.refreshLayout.finishLoadMore()
}
}
})
job = viewModel.viewModelScope.launch(Dispatchers.IO) {
viewModel.shoes.collect {
adapter.submitData(it)
}
}
binding.refreshLayout.setRefreshHeader(DropBoxHeader(context))
binding.refreshLayout.setRefreshFooter(ClassicsFooter(context))
binding.refreshLayout.setOnLoadMoreListener {
// 如果當(dāng)前數(shù)據(jù)已經(jīng)全部加載完拐云,就不再加載
if(currentStates?.append?.endOfPaginationReached == true)
binding.refreshLayout.finishLoadMoreWithNoMoreData()
}
//... 省略無(wú)關(guān)代碼
}
到這兒蔓姚,正常的流程就走通了。
四慨丐、更多
1. 使用RemoteMediator
RemoteMediator
的使用機(jī)制跟 PageSource
沒(méi)什么區(qū)別坡脐,默認(rèn)是先走 PageSource
的數(shù)據(jù),如果 PageSource
提供的數(shù)據(jù)返回為空的情況房揭,才會(huì)走 RemoteMediator
的數(shù)據(jù)备闲,所以你可以用:
-
PageSource
:進(jìn)行數(shù)據(jù)庫(kù)請(qǐng)求提供數(shù)據(jù)。 -
RemoteMediator
:進(jìn)行網(wǎng)絡(luò)請(qǐng)求捅暴,然后對(duì)請(qǐng)求成功的數(shù)據(jù)進(jìn)行數(shù)據(jù)庫(kù)的存儲(chǔ)恬砂。
鑒于使用 RemoteMediator
跟普通的方式?jīng)]什么本質(zhì)區(qū)別,我這里就不再做更多介紹蓬痒,感興趣的同學(xué)可以查看文檔泻骤。
2. 談?wù)勎矣龅降囊恍┛?/h4>
界面
之前點(diǎn)擊品牌按鈕, Paing 2
對(duì)應(yīng)的版本使用的 LiveData
梧奢,這是我之前的寫(xiě)法:
class ShoeModel constructor(shoeRepository: ShoeRepository) : ViewModel() {
// 品牌的觀察對(duì)象 默認(rèn)觀察所有的品牌
private val brand = MutableLiveData<String>().apply {
value = ALL
}
// 鞋子集合的觀察類(lèi)
val shoes: LiveData<PagedList<Shoe>> = brand.switchMap {
// Room數(shù)據(jù)庫(kù)查詢(xún)狱掂,只要知道返回的是LiveData<List<Shoe>>即可
if (it == ALL) {
// LivePagedListBuilder<Int,Shoe>( shoeRepository.getAllShoes(),PagedList.Config.Builder()
LivePagedListBuilder<Int, Shoe>(
CustomPageDataSourceFactory(shoeRepository) // DataSourceFactory
, PagedList.Config.Builder()
.setPageSize(10) // 分頁(yè)加載的數(shù)量
.setEnablePlaceholders(false) // 當(dāng)item為null是否使用PlaceHolder展示
.setInitialLoadSizeHint(10) // 預(yù)加載的數(shù)量
.build()
)
.build()
//shoeRepository.getAllShoes()
} else {
val array: Array<String> =
when (it) {
NIKE -> arrayOf("Nike", "Air Jordan")
ADIDAS -> arrayOf("Adidas")
else -> arrayOf(
"Converse", "UA"
, "ANTA"
)
}
shoeRepository.getShoesByBrand(array)
.createPagerList(6, 6)
}
}
fun setBrand(brand: String) {
this.brand.value = brand
}
companion object {
public const val ALL = "所有"
public const val NIKE = "Nike"
public const val ADIDAS = "Adidas"
public const val OTHER = "other"
}
}
切換到 Paging 3
的時(shí)候遇到一些坑:
先把可觀察的數(shù)據(jù)集從 Flow
轉(zhuǎn)到 LiveData
,也就是從 Pager(...).flow
到 Pager(...).livedata
:
class ShoeModel constructor(private val shoeRepository: ShoeRepository) : ViewModel() {
/**
* @param config 分頁(yè)的參數(shù)
* @param pagingSourceFactory 單一數(shù)據(jù)源的工廠亲轨,在閉包中提供一個(gè)PageSource即可
* @param remoteMediator 同時(shí)支持網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)庫(kù)請(qǐng)求的數(shù)據(jù)源
* @param initialKey 初始化使用的key
*/
var shoes = Pager(config = PagingConfig(
pageSize = 20
, enablePlaceholders = false
, initialLoadSize = 20
), pagingSourceFactory = { CustomPageDataSource(shoeRepository) }).liveData
//....
}
緊接著趋惨,我在 ShoeFragment
進(jìn)行數(shù)據(jù)的監(jiān)聽(tīng):
job = viewModel.viewModelScope.launch() {
viewModel.shoes.observe(viewLifecycleOwner, Observer<PagingData<Shoe>> {
adapter.submitData(viewLifecycleOwner.lifecycle,it)
})
}
第一個(gè)坑出現(xiàn)了,在 Paging 2
中惦蚊,PagingSouce
獲取數(shù)據(jù)是處在子線程的器虾,而 Paging 3
中,獲取數(shù)據(jù)處在當(dāng)前線程蹦锋,不會(huì)做線程切換兆沙,如果直接使用數(shù)據(jù)庫(kù)查詢(xún),會(huì)報(bào)錯(cuò)莉掂,因?yàn)?Room 中規(guī)定葛圃,數(shù)據(jù)庫(kù)查詢(xún)不能在主線程。
好家伙,既然主線程不可以請(qǐng)求數(shù)據(jù)庫(kù)装悲,那么我就把它放在IO線程昏鹃,在 launch
方法中加上 Dispatchers.IO
:
job = viewModel.viewModelScope.launch(Dispatchers.IO) {
viewModel.shoes.observe(viewLifecycleOwner, Observer<PagingData<Shoe>> {
adapter.submitData(viewLifecycleOwner.lifecycle,it)
})
}
第二個(gè)坑安排上尚氛,LiveData#observe(...)
方法不能發(fā)生在后臺(tái)線程诀诊。
這就沒(méi)辦法了,只能使用 Flow
去實(shí)現(xiàn)阅嘶,不過(guò) Flow
也有坑属瓣,當(dāng)數(shù)據(jù)源發(fā)生變化的時(shí)候,數(shù)據(jù)需要重新監(jiān)聽(tīng)讯柔,代碼也沒(méi)有了之前的優(yōu)雅:
class ShoeModel constructor(private val shoeRepository: ShoeRepository) : ViewModel() {
var shoes = Pager(config = PagingConfig(
pageSize = 20
, enablePlaceholders = false
, initialLoadSize = 20
), pagingSourceFactory = { CustomPageDataSource(shoeRepository) }).flow
fun setBrand(br: String) {
if (br == ALL) {
shoes = Pager(config = PagingConfig(
pageSize = 20
, enablePlaceholders = false
, initialLoadSize = 20
), pagingSourceFactory = { CustomPageDataSource(shoeRepository) }).flow
} else {
val array: Array<String> =
when (br) {
NIKE -> arrayOf("Nike", "Air Jordan")
ADIDAS -> arrayOf("Adidas")
else -> arrayOf(
"Converse", "UA"
, "ANTA"
)
}
shoes = shoeRepository.getShoesByBrand(array).createPager(20, 20).flow
}
}
//....
}
class ShoeFragment : Fragment() {
// ... 省略
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// ... 省略
onSubscribeUi(binding)
return binding.root
}
/**
* 鞋子數(shù)據(jù)更新的通知
*/
private fun onSubscribeUi(binding: ShoeFragmentBinding) {
// ... 省略
mNike.setOnClickListener {
viewModel.setBrand(ShoeModel.NIKE)
reInitSubscribe(adapter)
shoeAnimation()
}
mAdi.setOnClickListener {
viewModel.setBrand(ShoeModel.ADIDAS)
reInitSubscribe(adapter)
shoeAnimation()
}
mOther.setOnClickListener {
viewModel.setBrand(ShoeModel.OTHER)
reInitSubscribe(adapter)
shoeAnimation()
}
}
private fun reInitSubscribe(adapter: ShoeAdapter) {
job?.cancel()
job = viewModel.viewModelScope.launch(Dispatchers.IO) {
viewModel.shoes.collect() {
adapter.submitData(it)
}
}
}
private fun setViewVisible(isShow: Boolean) {
nikeGroup.visibility = if (isShow) View.VISIBLE else View.GONE
adiGroup.visibility = if (isShow) View.VISIBLE else View.GONE
otherGroup.visibility = if (isShow) View.VISIBLE else View.GONE
}
}
期間也試了 LiveData
轉(zhuǎn) Flow
的操作符抡蛙,也沒(méi)起作用,可能自己研究的不深魂迄,有了解的同學(xué)可以探討一下哈~
五粗截、總結(jié)
相比較 Paging 2
而言,Paging 3
的功能確實(shí)更加健全了捣炬,也值得去使用熊昌。
在后續(xù)的文章中,我還將和大家探討一下 Paging 3 的源碼湿酸,當(dāng)然婿屹,Paging 3 現(xiàn)在還處于 Alpha 階段,并不適用于我們的生產(chǎn)環(huán)境推溃,所以我還打算和大家聊聊如何更好的使用 Paging 2昂利。