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有更好的支持,能减小不少的开发量。

相关文章
相关标签/搜索