当前位置: 代码迷 >> 综合 >> JetPack--Paging3
  详细解决方案

JetPack--Paging3

热度:88   发布时间:2023-09-14 05:29:21.0

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

Paging2->Paging3三个模块改为:

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

一、paging3上手

效果:

JetPack--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.entityimport com.google.gson.annotations.SerializedName/*** Created by aruba on 2021/9/22.*/
data class Movies(@SerializedName("subjects")val movies: List<Movie>,@SerializedName("has_more")val hasMore: Boolean
)
3.定义网络相关

Api:

package com.aruba.flowapplyapplication.netimport 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.netimport 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 <T> getApi(clazz: Class<T>): T {return instance.create(clazz) as T}
}
4.定义RecyclerView的Adapter相关

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:paddingVertical="10dip"><ImageViewandroid:id="@+id/imageView"android:layout_width="100dip"android:layout_height="100dip"app:image="@{movie.cover}"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toStartOf="@+id/guideline2"app:layout_constraintHorizontal_bias="0.432"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.054"tools:srcCompat="@tools:sample/avatars" /><TextViewandroid:id="@+id/textViewTitle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{movie.title}"android:textSize="16sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.0"app:layout_constraintStart_toStartOf="@+id/guideline"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.255"tools:text="泰坦尼克号" /><TextViewandroid:id="@+id/textViewRate"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="24dp"android:text="@{movie.rate}"android:textSize="16sp"app:layout_constraintStart_toStartOf="@+id/guideline"app:layout_constraintTop_toBottomOf="@+id/textViewTitle"tools:text="评分:8.9分" /><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/guideline2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"app:layout_constraintGuide_percent="0.4" /><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/guideline"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"app:layout_constraintGuide_percent="0.5" /></androidx.constraintlayout.widget.ConstraintLayout><data><variablename="movie"type="com.aruba.paging3application.entity.Movie" /></data>
</layout>

BingingAdapter,自定义标签image

package com.aruba.paging3application.bindingAdapterimport 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.adapterimport 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<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 {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.pagingimport 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<Int, Movie>() {companion object {const val pageSize = 10}//该办法只在初始加载成功且加载页面的列表不为空的情况下被调用。override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {return null}override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {val currentPage = params.key ?: 1val movies = RetrofitClient.getApi(Api::class.java).getMovies(currentPage, pageSize)//上一页的keyval prevKey: Int? = (currentPage - 1).run {if (this == 0) {//说明当前是第一页数据null} else {this}}//下一页的keyval nextKey: Int? = when {params.loadSize > pageSize -> {//第一次加载的会多一些// public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER, // 默认PagingConfig为pager分配初始获取数据的大小为pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER// 所以Pager配置时,如果initialLoadSize不指定,那么第一次加载数据并不是我们定义的pageSizeval count = params.loadSize / pageSizecount + 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.viewmodelimport 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.paging3applicationimport 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.collectclass MainActivity : AppCompatActivity() {private val movieViewModel by viewModels<MovieViewModel>()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 = adapterbinding.recyclerView.layoutManager = LinearLayoutManager(this)lifecycleScope.launchWhenCreated {movieViewModel.bindPage().collect {adapter.submitData(it)}}}
}

二、加载更多

效果:

JetPack--Paging3
LoadStateAdapter

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

package com.aruba.paging3application.adapterimport 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<BindingViewHolder>() {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)}
}

布局为:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:padding="10dp"><ProgressBarandroid:id="@+id/progressBar"android:layout_width="20dp"android:layout_height="20dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toStartOf="@+id/tv_loading" /><TextViewandroid:id="@+id/tv_loading"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="正在加载数据... ..."android:textSize="18sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

PagingDataAdapter设置下就可以了

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

三、下拉刷新

效果:

JetPack--Paging3

在布局中为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.paging3applicationimport 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.launchclass MainActivity : AppCompatActivity() {private val movieViewModel by viewModels<MovieViewModel>()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}}}}
}

四、瞬态数据缓存

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

JetPack--Paging3

想要实现瞬态数据缓存,则要 将Flow变为ViewModel的属性,并为它设置作用域
package com.aruba.paging3application.viewmodelimport 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
}

效果:

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