为了解决RecyclerView
的加载更多的功能,Google
推出了Jetpack
的Paging
组件。经过Paging
组件,能够更流畅无缝的加载更多的数据。java
Paging
是利用DataSource
进行数据的更新。根据用途的不一样分为三种。android
PagaKeyedDataSource<Key, Value>
: 适用于目标数据是按照页数说去数据的场景。key
是页数, 请求的数据参数重包含next/previous
页数的信息。ItemKeyedDataSource<Key, Value>
: 适用于目标数据的加载依赖特定Item的信息,key
包含Item中信息,好比须要根据第N项信息加载第N+1信息。常常应用于评论信息请求。PositionalDataSource<T>
: 适用于目标总数固定,经过特定的位置加载数据,key是位置信息。添加paging
库的依赖:git
implementation "androidx.paging:paging-runtime:2.1.1"
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
复制代码
首先,这部分教程是你们能够熟练使用Room
和ViewModel
为前提,因此不会有对该部分知识进行详细的说明。github
@Entity(tableName = "users_table")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
val lastName: String,
@ColumnInfo(name = "birthday")
val birthday: String,
@ColumnInfo(name = "nationality")
val nationality: String
)
复制代码
这里须要说明的一点是返回值不是List<User>
而是DataSource.Factory
。 其中左边是key
,右边是value
。 key
是用于内部的PositionalDataSource
, 来提示当前数据的位置也就是页数。 而value
显而易见的咱们须要使用的数据。数据库
@Dao
interface UserDao {
@Query("SELECT * FROM users_table ORDER BY id ASC")
fun getAllByLivePage(): DataSource.Factory<Int, User>
}
复制代码
@Database(version = 1, entities = [User::class])
abstract class UserDataBase : RoomDatabase() {
companion object {
private var INSTANCE: UserDataBase? = null
fun getInstance(context: Context): UserDataBase? {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context,
UserDataBase::class.java, DATABASE_NAME
).build()
}
return INSTANCE
}
fun destroyInstance() {
INSTANCE = null
}
const val DATABASE_NAME = "user_database.db"
}
abstract fun getUserDao(): UserDao
}
复制代码
不一样于以往继承RecyclerAdapter
或者ListAdapter
, 这里咱们须要继承PagedListAdapter
。 重写的内容跟ListAdapter
是如出一辙,没有什么特别之处,以下。app
class UserAdapter : PagedListAdapter<User, UserAdapter.UserViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemUserBinding =
DataBindingUtil.inflate(inflater, R.layout.item_user, parent, false)
return UserViewHolder(binding)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
getItem(position)?.also {
holder.binding.txtBirthday.text = it.birthday
holder.binding.txtFirstName.text = it.firstName
holder.binding.txtLastName.text = it.lastName
holder.binding.txtNationality.text = it.nationality
}
}
class UserViewHolder(var binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<User>() {
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem === newItem
}
}
}
}
复制代码
咱们须要在ViewModel
中设置Paging
的Config
。我尝试过在Activity
中进行过设置,不能正常运行,不知道个人写法是否哪里问题。ide
class ShowViewModel(application: Application) : AndroidViewModel(application) {
val dao =
UserDataBase.getInstance(
application.applicationContext
)?.getUserDao()
val allUsers = dao!!.getAllByLivePage()
.toLiveData(Config(pageSize = 10, enablePlaceholders = true, maxSize = 50))
}
复制代码
从DB中得到的数据要转换成咱们熟悉的LiveData
,以及同时须要设置Config
。函数
pageSize
: 一次从数据库加载的数据量。enablePlaceholders
: 若是加载失败的话,是否显示。默认是true
。maxSize
: 存储在内存中的最大数据量。默认时Int的最大值。prefetchDistance
: 距离最后一个数据有多远的距离时,进行提早获取数据。默认是pageSize
。initialLoadSizeHint
: 加载到pagedList
中的初始数据量。默认是pageSize
的3倍。一般是大约正常一页的数据量。最后一步是在Activity
中监视,当数据有变化时传入到Adapter中进行更新。fetch
viewModel.allUsers.observe(this, Observer {
adapter.submitList(it)
})
复制代码
讲完Paging + Room
之后,咱们继续尝试Paging + Network
的组合。 首先须要说明一下的是,我没有找到比较合适的Api,因此我在这里进行本地模拟。
还有PagedListAdapter
和上面是彻底同样的,因此就再也不重复赘述了。
咱们须要继承PageKeyedDataSource<Key,Value>
,而后重写三个方法。
咱们首先介绍一下重写函数中传入的数据。
params: LoadInitialParams<Int>
: 该参数是初始加载的参数。包含两个变量,requestedLoadSize
: 要求的数据的量。placeholdersEnabled
是跟上面讲到的是同样的。callback: LoadInitialCallback<Key, Value>
: 是初始加载完成后的回调。params: LoadParams<Int>
: 是向前和向后翻页时传入的参数。包含两个变量,requestedLoadSize
是跟上面同样,key
是当前所在的页数。callback: LoadCallback<Key, Value>)
: 是向前和向后翻页完成后的回调。接下来介绍一下要重写的参数。
loadInitial
: 为起始加载。callback
的做用是提醒Paging
数据已经加载完成。onResult
的第一个参数是已加载好的数据。第二参数是前一页的key,若是加载的数据没有前面的数据,则能够设置为null
。第三个参数是后一页的key,我这里是已0为起始,因此在这里传入1。loadBefore
: 是向前翻页时的加载。onResult
中须要传入两个参数,第一个也是请求得到的数据,第二个数向前加载时的页数,我这里设置的是params.key-1
。loadAfter
: 是向后翻页时的加载。onResult
中也是须要传入两个参数,第一个也是数据,第二个时向后翻页加载时的页数,这里设置的是params.key+1
class UsersDataSource : PageKeyedDataSource<Int, User>() {
// 起始加载
override fun loadInitial( params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, User> ) {
callback.onResult(getList(0, params.requestedLoadSize), null, 1)
}
// 向前加载
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
callback.onResult(getList(params.key, params.requestedLoadSize), params.key - 1)
}
// 向后加载
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
callback.onResult(getList(params.key, params.requestedLoadSize), params.key + 1)
}
}
复制代码
咱们需用经过DataSource.Factory
来生成刚才制做的UsersDataSource
。 咱们须要继承在上面Paging+Room
出现过的DataSourec.Factory<Key, Value>
。
class CustomPageDataSourceFactory() : DataSource.Factory<Int, User>() {
override fun create(): DataSource<Int, User> {
return UsersDataSource()
}
}
复制代码
最后咱们要在Activity
中设置Paging
。LivePagedListBuilder
中须要传入两个参数。第一个是DataSource.Factory<Key,Value>
,就是上面制做的class。第二个是PagedList.Config
,由于上面有介绍因此略过。
// 经过LivePagedListBuilder生成LiveData
val data = LivePagedListBuilder(
CustomPageDataSourceFactory(),
PagedList.Config.Builder()
.setPageSize(20)
.setInitialLoadSizeHint(60)
.build()
).build()
// 监视数据, 数据有变更时传递给adapter
data.observe(this, Observer {
adapter.submitList(it)
})
复制代码
经过使用Paging
库,可使数据加载无缝进行,能有更好的用户体验。尤为对Room
有更好的支持,能减小不少的开发量。