Paging在Android中的應用

1. 簡介

爲了解決RecyclerView的加載更多的功能,Google推出了JetpackPaging組件。經過Paging組件,能夠更流暢無縫的加載更多的數據。java

Paging是利用DataSource進行數據的更新。根據用途的不一樣分爲三種。android

  1. PagaKeyedDataSource<Key, Value>: 適用於目標數據是按照頁數說去數據的場景。key是頁數, 請求的數據參數重包含next/previous頁數的信息。
  2. ItemKeyedDataSource<Key, Value>: 適用於目標數據的加載依賴特定Item的信息,key包含Item中信息,好比須要根據第N項信息加載第N+1信息。常常應用於評論信息請求。
  3. 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'
複製代碼

2. Paging + Room

首先,這部分教程是你們能夠熟練使用RoomViewModel爲前提,因此不會有對該部分知識進行詳細的說明。github

2.1 製做數據庫

2.1.1 Entity
@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
)
複製代碼
2.1.2 Dao

這裏須要說明的一點是返回值不是List<User>而是DataSource.Factory。 其中左邊是key,右邊是valuekey是用於內部的PositionalDataSource, 來提示當前數據的位置也就是頁數。 而value顯而易見的咱們須要使用的數據。數據庫

@Dao
interface UserDao {
    @Query("SELECT * FROM users_table ORDER BY id ASC")
    fun getAllByLivePage(): DataSource.Factory<Int, User>
}
複製代碼
2.1.3 Database
@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
}
複製代碼

2.2 製做PagedListAdapter

不一樣於以往繼承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
            }
        }
    }
}
複製代碼

2.3 製做Paging的Config

咱們須要在ViewModel中設置PagingConfig。我嘗試過在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函數

  1. pageSize: 一次從數據庫加載的數據量。
  2. enablePlaceholders: 若是加載失敗的話,是否顯示。默認是true
  3. maxSize: 存儲在內存中的最大數據量。默認時Int的最大值。
  4. prefetchDistance: 距離最後一個數據有多遠的距離時,進行提早獲取數據。默認是pageSize
  5. initialLoadSizeHint: 加載到pagedList中的初始數據量。默認是pageSize的3倍。一般是大約正常一頁的數據量。

2.4 監視以及傳入Adapter

最後一步是在Activity中監視,當數據有變化時傳入到Adapter中進行更新。fetch

viewModel.allUsers.observe(this, Observer {
    adapter.submitList(it)
})
複製代碼

2.5 github

RoomAndPageListDemo大數據

3. Paging + Network

講完Paging + Room之後,咱們繼續嘗試Paging + Network的組合。 首先須要說明一下的是,我沒有找到比較合適的Api,因此我在這裏進行本地模擬。

還有PagedListAdapter和上面是徹底同樣的,因此就再也不重複贅述了。

3.1 製做DataSource

咱們須要繼承PageKeyedDataSource<Key,Value>,而後重寫三個方法。

咱們首先介紹一下重寫函數中傳入的數據。

  1. params: LoadInitialParams<Int>: 該參數是初始加載的參數。包含兩個變量,requestedLoadSize: 要求的數據的量。placeholdersEnabled是跟上面講到的是同樣的。
  2. callback: LoadInitialCallback<Key, Value>: 是初始加載完成後的回調。
  3. params: LoadParams<Int>: 是向前和向後翻頁時傳入的參數。包含兩個變量,requestedLoadSize是跟上面同樣,key是當前所在的頁數。
  4. callback: LoadCallback<Key, Value>): 是向前和向後翻頁完成後的回調。

接下來介紹一下要重寫的參數。

  1. loadInitial: 爲起始加載。callback的做用是提醒Paging數據已經加載完成。onResult的第一個參數是已加載好的數據。第二參數是前一頁的key,若是加載的數據沒有前面的數據,則能夠設置爲null。第三個參數是後一頁的key,我這裏是已0爲起始,因此在這裏傳入1。
  2. loadBefore: 是向前翻頁時的加載。onResult中須要傳入兩個參數,第一個也是請求得到的數據,第二個數向前加載時的頁數,我這裏設置的是params.key-1
  3. 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)
    }
}
複製代碼

3.2 製做DataSourceFactory

咱們需用經過DataSource.Factory來生成剛纔製做的UsersDataSource。 咱們須要繼承在上面Paging+Room出現過的DataSourec.Factory<Key, Value>

class CustomPageDataSourceFactory() : DataSource.Factory<Int, User>() {
    override fun create(): DataSource<Int, User> {
        return UsersDataSource()
    }
}
複製代碼

3.3 經過LivePagedListBuilder生成LiveData

最後咱們要在Activity中設置PagingLivePagedListBuilder中須要傳入兩個參數。第一個是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)
})
複製代碼

3.4 github

PagingDemo

4. 結論

經過使用Paging庫,可使數據加載無縫進行,能有更好的用戶體驗。尤爲對Room有更好的支持,能減小不少的開發量。

相關文章
相關標籤/搜索