Android(安卓)Paging组件Demo
16lz
2021-12-04
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
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- python起点网月票榜字体反爬案例
- Android异步加载图像小结 (含线程池,缓存方法)
- Android(安卓)TabHost使用、动态加载内容
- Android(安卓)--- BaseAdapter
- 在android中policymanager
- 【安卓笔记】android客户端与服务端交互的三种方式
- Android(安卓)主流图片库Picasso Glide Fresco对比分析
- android实践项目一实现简单的验证码和spinner下拉选项效果