即學(xué)即用Android Jetpack - Paging 3

技術(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】

目錄

目錄.png

一瘪弓、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ō)一下主要改變的東西:

  1. 支持 Kotlin 中的 Flow虑乖。
  2. 簡(jiǎn)化數(shù)據(jù)源 PagingSource 的實(shí)現(xiàn)。
  3. 增加請(qǐng)求數(shù)據(jù)時(shí)狀態(tài)的回調(diào)晾虑,支持設(shè)置 Header 和 Footer决左。
  4. 支持多種方式請(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婴渡,還有 LiveDataRxJava2
  • 內(nèi)置狀態(tài)處理凯亮,包括刷新边臼、錯(cuò)誤、加載等狀態(tài)假消。

3. 幾個(gè)重要的類(lèi)

先看一下結(jié)構(gòu):

Paging3架構(gòu).png

里面幾個(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)述一下就是 PagingSourceRemoteMediator 充當(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 中的 ObservableFlowable,其中男翰,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ù)等为严。
  • pagingSourceFactoryremoteMediator 都是數(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ù)话原,我們要做的是:

  1. 創(chuàng)建和設(shè)置適配器夕吻。
  2. 開(kāi)啟一個(gè)協(xié)程
  3. 在協(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ì)涉馅!它還支持添加 HeaderFooter,官方示例是把它們用作上拉刷新和下拉加載更多的控件黄虱。

官方實(shí)例

不過(guò) Hoo 中并沒(méi)有使用 HeaderFooter稚矿,我們來(lái)看看官方怎么使用的:

  1. 創(chuàng)建一個(gè) HeaderAdapter or FooterAdapter 繼承自 LoadStateAdapter,跟普通 Adapter 不一樣的地方在于它在 onBindViewHolder 方法中提供了 LoadState 參數(shù)捻浦,它可以提供當(dāng)前 PagingLoading晤揣、NotLoadingError 的狀態(tài)。
  2. 跟普通 Adapter 一樣創(chuàng)建自己需要的 ViewHolder朱灿。
  3. 稍微修改一下第五步中設(shè)置 ShoeAdapter昧识,調(diào)用它的withLoadStateHeaderAndFooter 方法綁定 HeaderFooter 的適配器:
    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 除了添加 HeaderFooter侣灶,還可以監(jiān)聽(tīng)數(shù)據(jù)的加載狀態(tài)甸祭,狀態(tài)對(duì)應(yīng)的類(lèi)是 LoadState,它有三種狀態(tài):

  1. Loading:數(shù)據(jù)加載中褥影。
  2. NotLoading:內(nèi)存中有已經(jīng)獲取的數(shù)據(jù)淋叶,即使往下滑,Paging 也不需要請(qǐng)求更多的數(shù)據(jù)伪阶。
  3. 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)驯杜,它里面有:

  1. refresh:LoadState:刷新時(shí)的狀態(tài),因?yàn)榭梢哉{(diào)用 PagingDataAdapter#refresh() 方法進(jìn)行數(shù)據(jù)刷新磷醋。
  2. append:LoadState:可以理解為 RecyclerView 向下滑時(shí)數(shù)據(jù)的請(qǐng)求狀態(tài)坛缕。
  3. prepend:LoadState:可以理解為RecyclerView 向上滑時(shí)數(shù)據(jù)的請(qǐng)求狀態(tài)墓猎。
  4. sourcemediator 分別包含上面123的屬性,source 代表單一的數(shù)據(jù)源赚楚,mediator 代表多數(shù)據(jù)源的場(chǎng)景毙沾,sourcemediator 二選一。

解釋了這么多宠页,說(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(...).flowPager(...).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昂利。

表情包
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铁坎,隨后出現(xiàn)的幾起案子蜂奸,更是在濱河造成了極大的恐慌,老刑警劉巖硬萍,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窝撵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡襟铭,警方通過(guò)查閱死者的電腦和手機(jī)碌奉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)寒砖,“玉大人赐劣,你說(shuō)我怎么就攤上這事×ǘ迹” “怎么了魁兼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)漠嵌。 經(jīng)常有香客問(wèn)我咐汞,道長(zhǎng)盖呼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任化撕,我火速辦了婚禮几晤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘植阴。我一直安慰自己蟹瘾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布掠手。 她就那樣靜靜地躺著憾朴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喷鸽。 梳的紋絲不亂的頭發(fā)上众雷,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音做祝,去河邊找鬼砾省。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剖淀,可吹牛的內(nèi)容都是我干的纯蛾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纵隔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翻诉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捌刮,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碰煌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后绅作,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芦圾,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年俄认,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了个少。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眯杏。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夜焦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岂贩,到底是詐尸還是另有隱情茫经,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站卸伞,受9級(jí)特大地震影響抹镊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荤傲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一垮耳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弃酌,春花似錦氨菇、人聲如沸儡炼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乌询。三九已至榜贴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妹田,已是汗流浹背唬党。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鬼佣,地道東北人驶拱。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像晶衷,于是被迫代替她去往敵國(guó)和親蓝纲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容