文章目录
1. Paging是什么
Paging 库可加载和显示来自本地存储或网络中更大的数据集中的数据页面。是一个分页库。
2. Paging的优势
- 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
- 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
- 可配置的
RecyclerView
适配器,会在用户滚动到已加载数据的末尾时自动请求数据。 - 对 Kotlin 协程和 Flow 以及
LiveData
和RxJava的一流支持。 - 内置对错误处理功能的支持,包括刷新和重试功能。
3. Paging中的三个重要类
3.1 PagingSource数据源
数据来源,可以是本地数据库源,也可以是从网络数据源。实现load()方法,PagingSource<Key, Value>
有两种类型参数:Key
和 Value
。键定义了用于加载数据的标识符,值是数据本身的类型。
LoadParams
对象包含有关要执行的加载操作的信息,其中包括要加载的键和要加载的项数。
LoadResult
对象包含加载操作的结果。LoadResult
是一个密封的类,根据 load()
调用是否成功,采用如下两种形式之一:
- 如果加载成功,则返回
LoadResult.Page
对象。 - 如果加载失败,则返回
LoadResult.Error
对象。
主要的数据源:
-
本地数据库源,Room可直接支持,比如下面的代码:
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) @Transaction fun insertUsers(users: List<User>) //支持返回一个数据源 @Query("SELECT * FROM user ORDER BY id COLLATE NOCASE ASC") fun allUserById(): PagingSource<Int, User> }
-
网络数据源,例如从网络获取一个app下载列表:
class NetworkPagingSource : PagingSource<Int, AppData>() { override fun getRefreshKey(state: PagingState<Int, AppData>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AppData> { //当前页 val currentPage = params.key ?: 1 //上一页 val prevKey: Int? = if (currentPage == 1) null else currentPage - 1 //下一页,这里只加载50页,可根据业务情况修改 val nextKey: Int? = if (currentPage < 50) currentPage + 1 else null return try { //从网络获取数据 val result = NetworkService.rankAppListApi.getAppList(currentPage) if (result?.data != null) { LoadResult.Page( data = result.data, prevKey = prevKey, nextKey = nextKey ) } else { LoadResult.Error(IllegalStateException("数据为空")) } } catch (e: Exception) { LoadResult.Error(e) } } }
3.2 Pager 数据流
上面的PageSource是数据源,Pager则是提供的方法可显示来自 PagingSource
的 PagingData
对象的响应式流。Paging 库支持使用多种流类型,包括 Flow
、LiveData
以及 RxJava 中的 Flowable
和 Observable
类型。
通常在ViewModel中创建一个Pager流,
fun loadNews(): Flow<PagingData<AppData>> {
return Pager(
config = PagingConfig(
pageSize = 20//每页加载20个数据
),
pagingSourceFactory = { NetworkPagingSource() }//设置数据源
).flow.cachedIn(viewModelScope)//缓存在ViewModelScope作用域中
}
数据接收,在Activity中接收数据:
lifecycleScope.launchWhenCreated {
mViewModel.loadNews().collectLatest {
LogUtils.d("receive===$it")
}
}
3.3 PagingDataAdapter数据显示适配器
上面已定义了数据源,数据流,那怎样将数据显示出来呢?需要使用PagingDataAdapter,pagingAdapter调用submitData(pagingData)方法将数据显示到RecyclerView容器中。
pagingAdapter.submitData(pagingData)
4. 分页显示数据库数据示例
4.1 导包
//协程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "com.blankj:utilcodex:1.29.0"
//paging
implementation "androidx.paging:paging-runtime-ktx:3.0.0"
testImplementation "androidx.paging:paging-common-ktx:3.0.0"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
//room包
implementation "androidx.room:room-runtime:2.4.0-alpha04"
kapt "androidx.room:room-compiler:2.4.0-alpha04"
testImplementation "androidx.room:room-testing:2.4.0-alpha04"
//room使用协程
implementation "androidx.room:room-ktx:2.2.4"
4.2 数据库与PagingSource
User实例类
@Entity(tableName = "user")
class User {
@Ignore
constructor(id: Int) {
this.id = id
}
@Ignore
constructor(firstName: String?, age: Int) {
this.firstName = firstName
this.age = age
}
constructor(id: Int, firstName: String?, age: Int) {
this.id = id
this.firstName = firstName
this.age = age
}
@PrimaryKey
@ColumnInfo(name = "id")
var id: Int = 0
@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
var firstName: String? = null
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
var age: Int = 0
}
UserDao类提供PagingSource
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
@Transaction
fun insertUsers(users: List<User>)
//PagingSource数据源
@Query("SELECT * FROM user ORDER BY id COLLATE NOCASE ASC")
fun allUserById(): PagingSource<Int, User>
}
UserDataBase类
@Database(
entities = [User::class],
version = 1,
exportSchema = false,
)
abstract class UserDataBase : RoomDatabase() {
companion object {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var INSTANCE: UserDataBase? = null
fun init(context: Context) {
if (INSTANCE == null) {
synchronized(UserDataBase::class.java) {
if (INSTANCE == null) {
create(context)
}
}
}
}
//创建数据库
private fun create(context: Context) {
INSTANCE =
Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
fillInDb(context.applicationContext)
}
}).build()
}
fun getDB(): UserDataBase {
return INSTANCE ?: throw NullPointerException("UserDataBase 未初始化")
}
private fun fillInDb(context: Context) {
coroutineScope.launch {
getDB().getUserDao().insertUsers(getUserList())
}
}
//插入测试的5000条数据
private fun getUserList(): List<User> {
val userList = mutableListOf<User>()
for (id in 1..5000) {
userList.add(User(id, "userName_$id", age = id))
}
return userList
}
}
//增加抽象方法,Room框架会自动实现这个方法
abstract fun getUserDao(): UserDao
}
4.3 Pager
定义在MainViewModel中
class MainViewModel : ViewModel() {
private val userDao = UserDataBase.getDB().getUserDao()
val allUsers = Pager(
PagingConfig(
pageSize = 50,
enablePlaceholders = true,
maxSize = 200
)
) {
userDao.allUserById()
}.flow.cachedIn(viewModelScope)
}
4.4 PagingDataAdapter
UserAdapter适配器类:
class UserAdapter : PagingDataAdapter<User, UserAdapter.UserViewHolder>(
object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
) {
class UserViewHolder(val binding: UserItemBinding) : RecyclerView.ViewHolder(binding.root)
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
getItem(position)?.let { user ->
holder.binding.userId.text = "id_${user.id}"
holder.binding.userName.text = "${user.firstName}"
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val binding = UserItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return UserViewHolder(binding)
}
}
4.5 Pager与PagingDataAdapter绑定
class MainActivity : AppCompatActivity() {
private val dataBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(dataBinding.root)
//recyclerView与UserAdapter绑定
dataBinding.recyclerView.layoutManager = LinearLayoutManager(this)
val userAdapter = UserAdapter()
dataBinding.recyclerView.adapter = userAdapter
//监听数据
lifecycleScope.launchWhenResumed {
@OptIn(ExperimentalCoroutinesApi::class)
viewModel.allUsers.collectLatest { userAdapter.submitData(it) }//将数据提交到Adapter中
}
lifecycleScope.launch {
//监听数据加载的变化
userAdapter.loadStateFlow.collectLatest { state ->
LogUtils.d("state==$state")
}
}
}
}