Android Paging组件Demo

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() {

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

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

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

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

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

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

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

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

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

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

}

PageKeyedDataSource

一般使用页码加载

class DataSourceByPage : PageKeyedDataSource() {

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

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

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

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

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

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

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

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

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

}

LivePagedListBuilder

private val dataSourceFactory = object : DataSource.Factory() {

    override fun create(): DataSource {
        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(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() {

            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

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

定义加载状态

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

数据实体类 

class DataBean  {

    var id = 0
    var string = "string"

}

Activity布局文件



    

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(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() {

            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()

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

    private val dataSourceFactory = object : DataSource.Factory() {

        override fun create(): DataSource {
            //初始化创建数据源对象,加载上一页或下一页不会创建
            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(), IDataSource {

    val result = MutableLiveData()

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

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
        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, callback: LoadCallback) {
        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, callback: LoadCallback) {
    }

    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 {
        return result
    }
}

模拟请求远程数据 

object RemoteData {

    private var errorCount = 0

    fun getById(id: Int, size: Int, callback: (isSuccess: Boolean, list: ArrayList) -> Unit) {
        //获取大于id的size条记录,当id大于100模拟一次错误请求,当id大于200返回空记录
        Thread {
            log("id:$id,size:$size")
            val list = arrayListOf()
            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

 

你可能感兴趣的:(android,kotlin)