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
使用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()
}
}
版权声明:本文为dingjianlun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。