爲了解決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
有更好的支持,能減小不少的開發量。