Android Paging组件Demo

  • Post author:
  • Post category:其他


Android Paging组件的作用

Android官方的分页加载组件,可以RecyclerView上实现在数据分页加载,无限滚动的效果。官方例子:

https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample

需要添加的依赖


implementation "com.android.support:appcompat-v7:28.0.0"
implementation "com.android.support:recyclerview-v7:28.0.0"
implementation "com.android.support:swiperefreshlayout:28.0.0"
implementation "android.arch.paging:runtime:1.0.1"
implementation "android.arch.lifecycle:extensions:1.1.1"

//使用Androidx的依赖
//implementation "androidx.appcompat:appcompat:1.0.2"
//implementation "androidx.recyclerview:recyclerview:1.0.0"
//implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
//implementation "androidx.paging:paging-runtime:2.0.0"
//implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"

主要用到这几个类

PagedListAdapter:RecyclerView使用的适配器

DataSource:数据源,常用的子类ItemKeyedDataSource、PageKeyedDataSource

LivePagedListBuilder:实现对象需要传入数据源工厂和请求的页面长度

ItemKeyedDataSource

一般使用id或位置标识加载数据

class DataSourceByItem: ItemKeyedDataSource<Int, DataBean>() {

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<DataBean>) {
        //初始化加载数据

        params.requestedInitialKey//初始化key
        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>())//回调结果
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<DataBean>) {
        //获取下一页的数据

        params.key//通过getKey()获取RecyclerView最后一个key
        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>())//回调结果
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<DataBean>) {
        //获取上一页的数据

        params.key//通过getKey()获取RecyclerView第一个key
        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>())//回调结果
    }

    override fun getKey(item: DataBean): Int {
        //获取id(位置标识)
        return item.id
    }

}

PageKeyedDataSource

一般使用页码加载

class DataSourceByPage : PageKeyedDataSource<Int, DataBean>() {

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, DataBean>) {
        //初始化加载数据

        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>(),/*上一页的页码*/0,/*下一页的页面*/2)//回调结果
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, DataBean>) {
        //获取下一页的数据

        params.key//需要请求页面的页码
        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>(),/*下一页的页码*/params.key + 1)//回调结果
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, DataBean>) {
        //获取上一页的数据

        params.key//需要请求页面的页码
        params.requestedLoadSize//请求数据的长度

        callback.onResult(ArrayList<DataBean>(), /*上一页的页码*/params.key - 1)//回调结果
    }

}

LivePagedListBuilder

private val dataSourceFactory = object : DataSource.Factory<Int, DataBean>() {

    override fun create(): DataSource<Int, DataBean> {
        return DataSourceByItem()//初始化创建数据源对象,加载上一页或下一页不会创建
    }
}

private val config = PagedList.Config.Builder()
    .setInitialLoadSizeHint(60)//初始化请求数据的长度(就是DataSource.loadInitial方法中的params.requestedLoadSize)
    .setPageSize(30)//请求数据的长度(loadAfter和loadBefore方法)
    .build()

private val livePagedList = LivePagedListBuilder(dataSourceFactory, config).build()

PagedListAdapter

class ListAdapter : PagedListAdapter<DataBean, RecyclerView.ViewHolder>(comparator) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    }

    companion object {

        private val comparator = object : DiffUtil.ItemCallback<DataBean>() {

            override fun areItemsTheSame(oldItem: DataBean, newItem: DataBean): Boolean {
                //比较唯一id
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: DataBean, newItem: DataBean): Boolean {
                //比较包含对象
                return oldItem == newItem
            }

        }
    }

}

Demo

gif

使用SwipeRefreshLayout和RecyclerView实现下拉刷新和分页加载

定义加载状态

enum class LoadingState {
    Normal, //
    Loading, //加载中
    Failed, //出错,重试
    NotMore,//没有更多数据
    Empty//空视图
}

数据实体类

class DataBean  {

    var id = 0
    var string = "string"

}

Activity布局文件

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipeRefreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager" />

</android.support.v4.widget.SwipeRefreshLayout>

activity

class MainActivity : AppCompatActivity() {

    private val viewModel by lazy { ViewModelProviders.of(this)[ListViewModel::class.java] }

    private val adapter = ListAdapter { viewModel.retry() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView.adapter = adapter

        viewModel.result.observe(this, Observer {
            it?.let { result ->
                if (result.isInit) {
                    if (result.loadingState == LoadingState.Empty) {
                        //初始化加载数据为空,设置recyclerView空视图
                        recyclerView.post { adapter.setEmptyView() }
                    }
                    //更新下拉刷新的状态
                    swipeRefreshLayout.post {
                        swipeRefreshLayout.isRefreshing = (result.loadingState == LoadingState.Loading)
                    }
                } else {
                    recyclerView.post { adapter.setLoadingState(result.loadingState) }
                }
            }
        })

        viewModel.livePagedList.observe(this, Observer {
            //初始化时,添加数据到适配器
            adapter.submitList(it)
        })

        swipeRefreshLayout.setOnRefreshListener {
            //下拉刷新
            viewModel.refresh()
        }
    }

}
class ListAdapter(private val retryCallback: () -> Unit) : PagedListAdapter<DataBean, RecyclerView.ViewHolder>(comparator) {

    private var listState = LoadingState.Normal

    private fun isShowLastRow(): Boolean {
        //是否显示加载视图
        return when (listState) {
            LoadingState.Loading, LoadingState.Failed, LoadingState.NotMore -> true
            else -> false
        }
    }

    private fun isShowEmptyView(): Boolean {
        //是否显示空视图
        return super.getItemCount() == 0 && listState == LoadingState.Empty
    }

    override fun getItemCount(): Int {
        return super.getItemCount() +
                if (isShowLastRow() || isShowEmptyView()) {
                    1
                } else {
                    0
                }
    }

    override fun getItemViewType(position: Int): Int {
        return if (isShowLastRow() && position == itemCount - 1) {
            R.layout.list_item_loading
        } else if (isShowEmptyView()) {
            R.layout.list_item_empty
        } else {
            R.layout.list_item
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
        return when (viewType) {
            R.layout.list_item -> ItemViewHolder(view)
            R.layout.list_item_loading -> LoadingViewHolder(view, retryCallback) { listState }
            else -> object : RecyclerView.ViewHolder(view) {}
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            R.layout.list_item -> getItem(position)?.let { (holder as ItemViewHolder).updateView(it) }
            R.layout.list_item_loading -> (holder as LoadingViewHolder).updateView()
        }
    }

    fun setLoadingState(loadingState: LoadingState) {
        //更新分页加载状态
        if (this.listState == loadingState)
            return

        val lastItemCount = itemCount
        this.listState = loadingState

        if (itemCount - 1 == lastItemCount) {
            notifyItemInserted(lastItemCount - 1)
        } else if (itemCount + 1 == lastItemCount) {
            notifyItemRemoved(lastItemCount - 1)
        } else if (itemCount == lastItemCount) {
            notifyItemChanged(lastItemCount - 1)
        }

//        notifyItemChanged(lastItemCount - 1)
    }

    fun setEmptyView() {
        //显示空数据视图
        if (this.listState == LoadingState.Empty)
            return

        this.listState = LoadingState.Empty

        notifyDataSetChanged()
    }

    companion object {

        private val comparator = object : DiffUtil.ItemCallback<DataBean>() {

            override fun areItemsTheSame(oldItem: DataBean, newItem: DataBean) = (oldItem.id == newItem.id)

            override fun areContentsTheSame(oldItem: DataBean, newItem: DataBean) = (oldItem == newItem)

        }
    }

}
class ListViewModel : ViewModel() {

    private val pageSize = 30

    private val config = PagedList.Config.Builder()
        .setInitialLoadSizeHint(pageSize * 2)
        .setPageSize(pageSize)
        .build()

    private val dataSource = MutableLiveData<IDataSource>()

    val result = switchMap(dataSource) { it.getResultBean() }!!

    private val dataSourceFactory = object : DataSource.Factory<Int, DataBean>() {

        override fun create(): DataSource<Int, DataBean> {
            //初始化创建数据源对象,加载上一页或下一页不会创建
            return DataSourceByItem().apply { dataSource.postValue(this) }
        }
    }

    val livePagedList = LivePagedListBuilder(dataSourceFactory, config).build()

    fun refresh() {
        //刷新页面
        dataSource.value?.refresh()
    }

    fun retry() {
        //重试
        dataSource.value?.retry()
    }

}
class DataSourceByItem : ItemKeyedDataSource<Int, DataBean>(), IDataSource {

    val result = MutableLiveData<ResultBean>()

    private var retry: (() -> Unit)? = null

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<DataBean>) {
        result.postValue(ResultBean(true, LoadingState.Loading))
        var isReturn = 0
        //异步请求远程数据
        RemoteData.getById(0, params.requestedLoadSize) { isSuccess, list ->
            if (isSuccess) {
                result.postValue(ResultBean(true, if (list.isEmpty()) LoadingState.Empty else LoadingState.Normal))
                callback.onResult(list)
            } else {
                result.postValue(ResultBean(true, LoadingState.Failed))
                retry = {
                    loadInitial(params, callback)
                }
            }
            isReturn = 1
        }
        //等待请求完成
        while (isReturn == 0) {
            Thread.sleep(100)
        }
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<DataBean>) {
        result.postValue(ResultBean(false, LoadingState.Loading))
        RemoteData.getById(params.key, params.requestedLoadSize) { isSuccess, list ->
            if (isSuccess) {
                result.postValue(ResultBean(false, if (list.isEmpty()) LoadingState.NotMore else LoadingState.Normal))
                callback.onResult(list)
            } else {
                result.postValue(ResultBean(false, LoadingState.Failed))
                retry = {
                    loadAfter(params, callback)
                }
            }
        }
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<DataBean>) {
    }

    override fun getKey(item: DataBean): Int {
        return item.id
    }

    override fun retry() {
        retry?.let {
            //重试请求
            retry = null
            it()
        }
    }

    override fun refresh() {
        //初始化刷新
        invalidate()
    }

    override fun getResultBean(): MutableLiveData<ResultBean> {
        return result
    }
}

模拟请求远程数据

object RemoteData {

    private var errorCount = 0

    fun getById(id: Int, size: Int, callback: (isSuccess: Boolean, list: ArrayList<DataBean>) -> Unit) {
        //获取大于id的size条记录,当id大于100模拟一次错误请求,当id大于200返回空记录
        Thread {
            log("id:$id,size:$size")
            val list = arrayListOf<DataBean>()
            val start = id + 1
            val end = id + size
            for (i in start..end) {
                list.add(DataBean().apply {
                    this.id = i
                    this.string = "item$i"
                })
            }

            val isSuccess = if (id < 100) {
                errorCount = 1
                true
            } else {
                --errorCount < 0
            }
            Thread.sleep(2000)
            callback(isSuccess, if (id > 200) arrayListOf() else list)
        }.start()
    }

}

完整的项目:

https://github.com/dingjianlun/PagingDemo



版权声明:本文为dingjianlun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。