JetPack--Paging3

前面我们使用过Paging,最新版本Paging3和以前对比,有所改动

Paging2->Paging3三个模块改为:

1.DataSource->PagingSource : 数据从该模块中获取,数据可以来源于网络、本地数据库等
2.PagedList->Pager : 负责具体获取数据的逻辑,何时获取、加载下一页、预加载等
3.PagedListAdapter->PagingDataAdapter : RecyclerView的adapter需要继承它,内部做了一系列处理

一、paging3上手

效果:


1.首先配置gradle

使用kapt插件

plugins {

    id 'kotlin-kapt'
}

DataBinding支持

    defaultConfig {

        dataBinding{
            enabled = true
        }
    }

添加依赖

    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    //依赖协程核心库 ,提供Android UI调度器
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
    //依赖当前平台所对应的平台库 (必须)
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01'
    implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta03'
    implementation "androidx.activity:activity-ktx:1.1.0"
    implementation "androidx.fragment:fragment-ktx:1.2.5"
2.根据服务器返回数据创建实体类

服务器数据:

{
    "has_more":true,
    "subjects":[
        {
            "id":35076714,
            "title":"扎克·施奈德版正义联盟",
            "cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2634360594.webp",
            "rate":"8.9"
        },
        {
            "id":26935283,
            "title":"侍神令",
            "cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2629260713.webp",
            "rate":"5.8"
        },
        {
            "id":35145068,
            "title":"双层肉排",
            "cover":"https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2633977758.webp",
            "rate":"6.7"
        },
        {
            "id":33433405,
            "title":"大地",
            "cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2628845704.webp",
            "rate":"6.6"
        },
        {
            "id":35167535,
            "title":"租来的朋友",
            "cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2616903233.webp",
            "rate":"6.1"
        }
    ]
}

package com.aruba.paging3application.entity

/**
 * Created by aruba on 2021/9/22.
 */
data class Movie(
    val id: Int,
    val title: String,
    val cover: String,
    val rate: String
)
package com.aruba.paging3application.entity

import com.google.gson.annotations.SerializedName

/**
 * Created by aruba on 2021/9/22.
 */
data class Movies(
    @SerializedName("subjects")
    val movies: List,
    @SerializedName("has_more")
    val hasMore: Boolean
)
3.定义网络相关

Api:

package com.aruba.flowapplyapplication.net

import com.aruba.paging3application.entity.Movies
import retrofit2.http.GET
import retrofit2.http.Query

/**
 * Created by aruba on 2021/9/21.
 */
interface Api {

    @GET("pkds.do")
    suspend fun getMovies(
        @Query("page") page: Int,
        @Query("pagesize") pagesize: Int
    ): Movies

}

retrofit工具类:

package com.aruba.flowapplyapplication.net

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

/**
 * Created by aruba on 2021/9/21.
 */
object RetrofitClient {
    private val instance: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl("http://192.168.17.114:8080/pagingserver_war/")
            .client(OkHttpClient.Builder().build())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun  getApi(clazz: Class): T {
        return instance.create(clazz) as T
    }
}
4.定义RecyclerView的Adapter相关

布局文件:




    


        

        

        

        

        

    

    

        
    

BingingAdapter,自定义标签image

package com.aruba.paging3application.bindingAdapter

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.aruba.paging3application.R
import com.squareup.picasso.Picasso

/**
 * Created by aruba on 2021/9/22.
 */
@BindingAdapter("image")
fun setImage(imageView: ImageView, imageUrl: String) {
    Picasso.get().load(imageUrl).placeholder(R.drawable.ic_launcher_foreground)
        .error(R.drawable.ic_launcher_foreground)
        .into(imageView);
}

Adapter,继承于PagingDataAdapter,和paging2一样需要DiffUtil.ItemCallback

package com.aruba.paging3application.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.DifferCallback
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import com.aruba.paging3application.databinding.ItemBinding
import com.aruba.paging3application.entity.Movie

/**
 * Created by aruba on 2021/9/22.
 */
class MoviePagingAdapter : PagingDataAdapter(
    object : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return oldItem == newItem
        }
    }
) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val binding = ItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
        (holder.binding as ItemBinding).movie = getItem(position)
    }

}
5.定义PagingSource

继承PagingSource,实现load函数,返回值为LoadResult,可以使用LoadResult.Page实例化,入参为继承时定义的第二个泛型,和上一页和下一页的两个Key,Key对应的第一个泛型

package com.aruba.paging3application.paging

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aruba.flowapplyapplication.net.Api
import com.aruba.flowapplyapplication.net.RetrofitClient
import com.aruba.paging3application.entity.Movie
import com.aruba.paging3application.entity.Movies

/**
 * Pagind获取数据层
 * Created by aruba on 2021/9/22.
 */
class MoviePagingSource : PagingSource() {
    companion object {
        const val pageSize = 10
    }

    //该办法只在初始加载成功且加载页面的列表不为空的情况下被调用。
    override fun getRefreshKey(state: PagingState): Int? {
        return null
    }

    override suspend fun load(params: LoadParams): LoadResult {
        val currentPage = params.key ?: 1
        val movies = RetrofitClient.getApi(Api::class.java).getMovies(currentPage, pageSize)

        //上一页的key
        val prevKey: Int? = (currentPage - 1).run {
            if (this == 0) {//说明当前是第一页数据
                null
            } else {
                this
            }
        }

        //下一页的key
        val nextKey: Int? = when {
            params.loadSize > pageSize -> {//第一次加载的会多一些
                // public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER, 
                // 默认PagingConfig为pager分配初始获取数据的大小为pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
                // 所以Pager配置时,如果initialLoadSize不指定,那么第一次加载数据并不是我们定义的pageSize
                val count = params.loadSize / pageSize
                count + 1
            }
            movies.hasMore -> {
                currentPage + 1
            }
            else -> {
                null
            }
        }

        return LoadResult.Page(
            data = movies.movies,
            prevKey = prevKey,
            nextKey = nextKey
        )
    }
}
6.定义Pager

Pager可以返回一个Flow,使用一个ViewModel获取Pager的Flow,下流就可以收集

package com.aruba.paging3application.viewmodel

import androidx.lifecycle.ViewModel
import androidx.paging.Pager
import androidx.paging.PagingConfig
import com.aruba.paging3application.paging.MoviePagingSource

/**
 * Created by aruba on 2021/9/22.
 */
class MovieViewModel : ViewModel() {

    //绑定page的flow,返回是一个flow对象
    fun bindPage() = Pager(config = PagingConfig(
        initialLoadSize = MoviePagingSource.pageSize,//初次加载的数据量大小
        pageSize = MoviePagingSource.pageSize,
        enablePlaceholders = false
    ),
        pagingSourceFactory = {
            MoviePagingSource()
        }
    ).flow
}
7.Activity中实例化ViewModel、配置RecyclerView等
package com.aruba.paging3application

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.aruba.paging3application.adapter.MoviePagingAdapter
import com.aruba.paging3application.databinding.ActivityMainBinding
import com.aruba.paging3application.viewmodel.MovieViewModel
import kotlinx.coroutines.flow.collect

class MainActivity : AppCompatActivity() {
    private val movieViewModel by viewModels()
    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val adapter = MoviePagingAdapter()
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        
        lifecycleScope.launchWhenCreated {
            movieViewModel.bindPage().collect {
                adapter.submitData(it)
            }
        }
    }
}

二、加载更多

效果:


LoadStateAdapter

PagingDataAdapter支持设置一个LoadStateAdapter,来显示加载更多
定义Adapter继承于LoadStateAdapter:

package com.aruba.paging3application.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import com.aruba.paging3application.databinding.MovieLoadmoreBinding

/**
 * Created by aruba on 2021/9/22.
 */
class LoadMoreAdapter : LoadStateAdapter() {
    override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindingViewHolder {
        val binding = MovieLoadmoreBinding.inflate(
            LayoutInflater.from(parent.context),
            parent, false
        )
        return BindingViewHolder(binding)
    }
}

布局为:




    

    

    

        

        

    

PagingDataAdapter设置下就可以了

    val adapter = MoviePagingAdapter()
    binding.recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())

三、下拉刷新

效果:


在布局中为RecyclerView套一层SwipeRefreshLayout后,在Activity中设置刷新监听

        binding.apply {
            recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())
            recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)

            //开始刷新
            refreshLayout.setOnRefreshListener {
                adapter.refresh()
            }
        }

Adapter的状态进行监听,来更新SwipeRefreshLayout的刷新状态

    //监听adapter状态
    adapter.loadStateFlow.collect {
        //根据刷新状态来通知swiprefreshLayout是否刷新完毕
        binding.refreshLayout.isRefreshing = it.refresh is LoadState.Loading
    }

完整代码:

package com.aruba.paging3application

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager
import com.aruba.paging3application.adapter.LoadMoreAdapter
import com.aruba.paging3application.adapter.MoviePagingAdapter
import com.aruba.paging3application.databinding.ActivityMainBinding
import com.aruba.paging3application.viewmodel.MovieViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    private val movieViewModel by viewModels()
    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val adapter = MoviePagingAdapter()
        binding.apply {
            recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())
            recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)

            //开始刷新
            refreshLayout.setOnRefreshListener {
                adapter.refresh()
            }
        }

        lifecycleScope.launchWhenCreated {
            launch {
                movieViewModel.bindPage().collect {
                    adapter.submitData(it)
                }   
            }
            
            launch {
                //监听adapter状态
                adapter.loadStateFlow.collect {
                    //根据刷新状态来通知swiprefreshLayout是否刷新完毕
                    binding.refreshLayout.isRefreshing = it.refresh is LoadState.Loading
                }
            }
        }
    }
}

四、瞬态数据缓存

目前我们写的代码是不带瞬态数据缓存的:


想要实现瞬态数据缓存,则要将Flow变为ViewModel的属性,并为它设置作用域

package com.aruba.paging3application.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import com.aruba.paging3application.paging.MoviePagingSource

/**
 * Created by aruba on 2021/9/22.
 */
class MovieViewModel : ViewModel() {
    private val pagerFlow by lazy {
        Pager(config = PagingConfig(
            initialLoadSize = MoviePagingSource.pageSize * 2,//初次加载的数据量大小
            pageSize = MoviePagingSource.pageSize,
            enablePlaceholders = false
        ),
            pagingSourceFactory = {
                MoviePagingSource()
            }
        ).flow.cachedIn(viewModelScope)
    }

    //绑定page的flow,返回是一个flow对象
    fun bindPage() = pagerFlow
}

效果:


Demo地址:https://gitee.com/aruba/paging3-application.git

你可能感兴趣的:(JetPack--Paging3)