前言
本篇文章主要讲述Android列表分页的原理和实现过程,希望可以帮助到那些对Android列表分页尚不熟悉的朋友!!!
一、自定义列表监听机制实现分页
1.定义列表滑动监听
mJokeListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
//上拉滑动的监听事件
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
totalItemCount = mLayoutManager?.itemCount
//列表中最后一个可见Item的position、列表Item的个数
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == (totalItemCount!! - 1)) {
currentPage += 1
mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_MORE)
Toast.makeText(context, "正在加载更多数据", Toast.LENGTH_SHORT).show()
//上拉滑动触发的具体事件
mPresenter.getData(currentPage, 1)
}
}
//滑动的时候,获取列表中最后一个可见的Item的position
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastVisibleItemPosition = mLayoutManager?.findLastVisibleItemPosition()
}
})
2.定义列表初始化方法
//初始化列表,创建NewsAdapter实例,后续只需要改变mList的值
//notifyDataSetChanged()也可以不要、
//notifyItemRangeInserted(positionStart, data.size)也可以不需要
//只需要改变mList的值,就可以触发列表的数据更新,不需要其它任何操作
private fun initList() {
mLayoutManager = LinearLayoutManager(this)
mJokeListView.layoutManager = mLayoutManager
mJokeListView.layoutManager = LinearLayoutManager(this)
mJokeAdapter = NewsAdapter(mList)
//设置适配器
mJokeListView.adapter = mJokeAdapter
}
3.定义页面刷新方法
override fun refreshView(orientation: Int, data: List<Data>) {
if (orientation == 0) {
Log.d("hy55", "data=$data")
//列表要清空
mList.clear()
mList.addAll(data)
Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
//适配器要全部刷新,不要也没有出现列表错乱情况
// mJokeAdapter.notifyDataSetChanged()
} else if (orientation == 1) {
//上一次的最大值
val positionStart = mList.size
mList.addAll(data)
Log.d("hy55", "positionStart=$positionStart、mList.size=${mList.size}、data.size=${data.size}")
Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
//局部刷新,不要也没有出现列表错乱情况
// mJokeAdapter.notifyItemRangeInserted(positionStart, data.size)
}
}
override fun loadFinish() {
Toast.makeText(this, "没有数据了", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_END)
}
4.定义适配器的两种Holder类型
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var tvContent: TextView = view.findViewById(R.id.tvContent)
var tvPlay: TextView = view.findViewById(R.id.tvPlay)
}
inner class FooterViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var content: TextView = view.findViewById(R.id.content)
}
5.定义适配器的两种Holder样式(普通数据、Footer加载框)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if(viewType == Type_Footer) {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_footer_item, parent, false)
return FooterViewHolder(view)
}
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.layout_joke_list_item, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is NewsAdapter.FooterViewHolder) {
when(load_more_status) {
PULLUP_LOAD_MORE -> {
holder.content.text = "上拉加载更多"
}
LOADING_MORE -> {
holder.content.text = "正在加载更多数据..."
}
LOADING_END -> {
holder.content.text = "没有更多数据了..."
}
}
} else if(holder is NewsAdapter.ViewHolder) {
holder.tvContent.text = list[position].content
holder.tvPlay.text = "播放"
}
}
6.定义底部加载框的触发条件
override fun getItemCount(): Int = list.size + 1
override fun getItemViewType(position: Int): Int = if(position + 1 == itemCount) Type_Footer else Type_Item
fun changMoreStatus(status: Int) {
load_more_status = status
notifyDataSetChanged()
}
二、使用ListAdapter结合SmartRefreshLayout实现分页
1.初始化列表的适配器和布局管理器
private fun initList() {
//刷新组件
refreshLayout?.setRefreshHeader(ClassicsHeader(this))
refreshLayout?.setRefreshFooter(ClassicsFooter(this))
//设置刷新组件的监听事件
refreshLayout?.setOnRefreshListener(this)
refreshLayout?.setOnRefreshLoadMoreListener(this)
mJokeListView?.layoutManager = LinearLayoutManager(this)
//设置适配器
mJokeListView?.adapter = mJokeAdapter
}
2.设置上拉和下拉刷新的监听事件
override fun onRefresh(refreshLayout: RefreshLayout) {
currentPage = 1
mPresenter.getData(currentPage, tempList, 0)
}
override fun onLoadMore(refreshLayout: RefreshLayout) {
positiontemp = tempList.size
currentPage += 1
mPresenter.getData(currentPage, tempList, 1)
}
3.设置列表刷新事件
override fun refreshView(orientation: Int, data: List<Data>) {
if (orientation == 0) {
refreshLayout?.finishRefresh()
Log.d("hy55", "第一获取数据后的tempLsit:${tempList.size}")
//列表要清空
mList.clear()
mList.addAll(data)
mJokeAdapter.submitList(data)
//适配器要全部刷新
mJokeAdapter.notifyDataSetChanged()
} else if (orientation == 1) {
refreshLayout?.finishLoadMore()
Log.d("hy55", "接口请求传回来的数据:${data.size}")
Log.d("hy55", "下拉刷新后的tempLsit:${tempList.size}")
//上一次的最大值
val positionStart = mList.size
Log.d("hy55", "positiontemp:$positiontemp")
//在已有的数据的基础之上添加数据,通过Diff类进行重复item判断
mList.addAll(data)
mJokeAdapter.submitList(tempList)
//局部刷新
mJokeAdapter.notifyItemRangeInserted(positiontemp!!, data.size)
}
}
4.注意事项:定义适配器中的Diff类
class NewsAdapter : ListAdapter<Data, NewsAdapter.ViewHolder>(Diff()) {
//构建ListView的数据比较结果,Item之间的比较参数绝对不能为空(绝对不能重复)
class Diff : DiffUtil.ItemCallback<Data>() {
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.hashId == newItem.hashId
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.content == newItem.content
}
}
三、使用Paging库实现分页
1.创建适配器
private var dataRecycleViewAdapter = DataRecycleViewAdapter { position, it, adapter ->
it?.author = "hy${position}"
adapter.notifyDataSetChanged()
}
2.给适配器设置上拉监听事件
rv_data.adapter = dataRecycleViewAdapter.withLoadStateFooter(footer = LoadStateFooterAdapter(retry = {
dataRecycleViewAdapter.retry()
}))
3.给适配器设置上拉刷新的数据(使用try/catch)
lifecycleScope.launch {
try {
ApiService.getData().collectLatest {
dataRecycleViewAdapter.submitData(it)
}
} catch (e: Exception) {
Log.d("测试错误数据 view层", "---错误了怎么办呢")
}
}
4.设置列表刷新状态的监听事件
//列表刷新状态的监听
dataRecycleViewAdapter.addLoadStateListener {
when (it.refresh) {
//没有加载的时候
is LoadState.NotLoading -> {
Log.d(TAG, "is NotLoading")
}
is LoadState.Loading -> {
Log.d(TAG, "is Loading")
}
//加载出现错误的时候可以弹出提示消息
is LoadState.Error -> {
Log.d(TAG, "is Error:")
when ((it.refresh as LoadState.Error).error) {
is IOException -> {
Log.d(TAG, "IOException")
}
else -> {
Log.d(TAG, "others exception")
}
}
}
}
}
5.创建适配器DataRecycleViewAdapter和LoadStateFooterAdapter
class DataRecycleViewAdapter(val itemUpdate: (Int, DemoReqData.DataBean.DatasBean?, DataRecycleViewAdapter) -> Unit) :
PagingDataAdapter<DemoReqData.DataBean.DatasBean, RecyclerView.ViewHolder>(object :
DiffUtil.ItemCallback<DemoReqData.DataBean.DatasBean>() {
override fun areItemsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem == newItem
}
}) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val dataBean = getItem(position)
(holder as DataViewHolder).binding.demoReaData = dataBean
holder.binding.btnUpdate.setOnClickListener {
itemUpdate(position, dataBean, this)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ItemDataBinding =
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_data,
parent,
false
)
return DataViewHolder(binding)
}
inner class DataViewHolder(private val dataBindingUtil: ItemDataBinding) :
RecyclerView.ViewHolder(dataBindingUtil.root) {
var binding = dataBindingUtil
}
}
class LoadStateFooterAdapter(private val retry: () -> Unit) :
LoadStateAdapter<LoadStateViewHolder>() {
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
Log.d("MainActivity", "---去绑定 onBindViewHolder")
holder.bindState(loadState)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
return LoadStateViewHolder(parent, retry)
}
}
class LoadStateViewHolder(parent: ViewGroup, var retry: () -> Unit) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_loadstate, parent, false)
) {
var itemLoadStateBindingUtil: ItemLoadstateBinding = ItemLoadstateBinding.bind(itemView)
fun bindState(loadState: LoadState) {
when (loadState) {
is LoadState.Error -> {
itemLoadStateBindingUtil.btnRetry.visibility = View.VISIBLE
itemLoadStateBindingUtil.btnRetry.setOnClickListener {
retry()
}
Log.d("MainActivity", "error了吧")
}
is LoadState.Loading -> {
itemLoadStateBindingUtil.llLoading.visibility = View.VISIBLE
}
else -> {
Log.d("MainActivity", "--其他的错误")
}
}
}
}
6.使用DataBinding,添加布局文件
1.Project的build.gradle文件中设置DataBinding
plugins {
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
android {
.......
dataBinding {
enabled true
}
}
kapt {
generateStubs = true
}
dependencies {
def paging_version = "3.0.0-alpha03"
testImplementation "androidx.paging:paging-common:$paging_version"
implementation "androidx.paging:paging-runtime:$paging_version"
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
//DataBind依赖,可加可不加
kapt "com.android.databinding:compiler:2.3.0"
......
2.布局文件中设置DataBinding
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="demoReaData"
type="com.imooc.pagingtest.DemoReqData.DataBean.DatasBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{demoReaData.author}"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{demoReaData.title}"
android:textSize="18sp" />
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="修改数据" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#fff000">
</View>
<Button
android:visibility="gone"
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除数据" />
</LinearLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ll_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正在加载数据... ..."
android:textSize="18sp" />
<ProgressBar
android:layout_width="20dp"
android:layout_height="20dp" />
</LinearLayout>
<Button
android:id="@+id/btn_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加载失败,重新请求"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_loading" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
7.定义获取数据的方法以及定义接口分页请求
object ApiService {
private var netWork = RetrofitService.createService(
DataApi::class.java
)
fun getData() = Pager(PagingConfig(pageSize = 1)) {
DataSource()
}.flow
/**
* 查询护理数据
*/
suspend fun loadData(
pageId: Int
): DemoReqData {
return netWork.getData(pageId)
}
}
class DataSource : PagingSource<Int, DemoReqData.DataBean.DatasBean>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
return try {
//页码未定义置为1
var currentPage = params.key ?: 1
//仓库层请求数据
Log.d("MainActivity", "请求第${currentPage}页")
var demoReqData = ApiService.loadData(currentPage)
//当前页码 小于 总页码 页面加1
var nextPage = if (currentPage < demoReqData.data?.pageCount ?: 0) {
currentPage + 1
} else {
//没有更多数据
null
}
LoadResult.Page(
data = demoReqData.data.datas,
prevKey = null,
nextKey = nextPage
)
} catch (e: Exception) {
if (e is IOException) {
Log.d("测试错误数据", "-------连接失败")
}
Log.d("测试错误数据", "-------${e.message}")
LoadResult.Error(throwable = e)
}
}
}
8.获取分页数据的接口和数据类
interface DataApi {
/**
* 获取数据
*/
@GET("wenda/list/{pageId}/json")
suspend fun getData(@Path("pageId") pageId: Int): DemoReqData
}
public class DemoReqData {
private DataBean data;
private int errorCode;
private String errorMsg;
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public static class DataBean {
private int curPage;
private int offset;
private boolean over;
private int pageCount;
private int size;
private int total;
private List<DatasBean> datas;
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this.curPage = curPage;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public boolean isOver() {
return over;
}
public void setOver(boolean over) {
this.over = over;
}
public int getPageCount() {
return pageCount;
}
public void setPageCount(int pageCount) {
this.pageCount = pageCount;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<DatasBean> getDatas() {
return datas;
}
public void setDatas(List<DatasBean> datas) {
this.datas = datas;
}
//implementation 'androidx.room:room-runtime:2.2.5'
//annotationProcessor 'androidx.room:room-compiler:2.2.5'
@Entity(tableName = "DataBean") //这是Room组件
public static class DatasBean {
private String apkLink;
private int audit;
private String author;
private boolean canEdit;
private int chapterId;
private String chapterName;
private boolean collect;
private int courseId;
private String desc;
private String descMd;
private String envelopePic;
private boolean fresh;
private int id;
private String link;
private String niceDate;
private String niceShareDate;
private String origin;
private String prefix;
private String projectLink;
private long publishTime;
private int realSuperChapterId;
private int selfVisible;
private long shareDate;
private String shareUser;
private int superChapterId;
private String superChapterName;
private String title;
private int type;
private int userId;
private int visible;
private int zan;
private List<TagsBean> tags;
public String getApkLink() {
return apkLink;
}
public void setApkLink(String apkLink) {
this.apkLink = apkLink;
}
public int getAudit() {
return audit;
}
public void setAudit(int audit) {
this.audit = audit;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public boolean isCanEdit() {
return canEdit;
}
public void setCanEdit(boolean canEdit) {
this.canEdit = canEdit;
}
public int getChapterId() {
return chapterId;
}
public void setChapterId(int chapterId) {
this.chapterId = chapterId;
}
public String getChapterName() {
return chapterName;
}
public void setChapterName(String chapterName) {
this.chapterName = chapterName;
}
public boolean isCollect() {
return collect;
}
public void setCollect(boolean collect) {
this.collect = collect;
}
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId) {
this.courseId = courseId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDescMd() {
return descMd;
}
public void setDescMd(String descMd) {
this.descMd = descMd;
}
public String getEnvelopePic() {
return envelopePic;
}
public void setEnvelopePic(String envelopePic) {
this.envelopePic = envelopePic;
}
public boolean isFresh() {
return fresh;
}
public void setFresh(boolean fresh) {
this.fresh = fresh;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getNiceDate() {
return niceDate;
}
public void setNiceDate(String niceDate) {
this.niceDate = niceDate;
}
public String getNiceShareDate() {
return niceShareDate;
}
public void setNiceShareDate(String niceShareDate) {
this.niceShareDate = niceShareDate;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getProjectLink() {
return projectLink;
}
public void setProjectLink(String projectLink) {
this.projectLink = projectLink;
}
public long getPublishTime() {
return publishTime;
}
public void setPublishTime(long publishTime) {
this.publishTime = publishTime;
}
public int getRealSuperChapterId() {
return realSuperChapterId;
}
public void setRealSuperChapterId(int realSuperChapterId) {
this.realSuperChapterId = realSuperChapterId;
}
public int getSelfVisible() {
return selfVisible;
}
public void setSelfVisible(int selfVisible) {
this.selfVisible = selfVisible;
}
public long getShareDate() {
return shareDate;
}
public void setShareDate(long shareDate) {
this.shareDate = shareDate;
}
public String getShareUser() {
return shareUser;
}
public void setShareUser(String shareUser) {
this.shareUser = shareUser;
}
public int getSuperChapterId() {
return superChapterId;
}
public void setSuperChapterId(int superChapterId) {
this.superChapterId = superChapterId;
}
public String getSuperChapterName() {
return superChapterName;
}
public void setSuperChapterName(String superChapterName) {
this.superChapterName = superChapterName;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getVisible() {
return visible;
}
public void setVisible(int visible) {
this.visible = visible;
}
public int getZan() {
return zan;
}
public void setZan(int zan) {
this.zan = zan;
}
public List<TagsBean> getTags() {
return tags;
}
public void setTags(List<TagsBean> tags) {
this.tags = tags;
}
public static class TagsBean {
/**
* name : 本站发布
* url : /article/list/0?cid=440
*/
private String name;
private String url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
}
}
}
9.创建Retrofit对象
object RetrofitService {
/**
* okhttp client
*/
lateinit var okHttpClient: OkHttpClient
/**
* 主Url地址
*/
private const val BASEAPI = "https://www.wanandroid.com/";
/**
* 创建service对象
*/
fun <T> createService(mClass: Class<T>): T {
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
val interceptor =
HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
//打印日志
Log.d("黄临清",it)
})
interceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpClient = builder.addInterceptor(interceptor).build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASEAPI)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(mClass) as T
}
}
总结
以上就是所有关于列表分页的内容,希望对大家对于列表分页的理解有所帮助!!!