最近在做需求的时候,需要实现列表的上拉加载功能。在这里进行记录分享。
其实上拉加载功能只需要为RecyclerView的Item布局添加一个FooterView,然后通过判断是否滑动到最后一条Item来控制FooterView的显隐来做到一个上拉加载的功能。先看代码
class PaperListAdapter(private val context: Context, private var paperList: List<PaperListModel>, private val subjectId: Int) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val TAG = "PaperListAdapter"
const val TYPE_ITEM = 1
const val TYPE_FOOTER = 2
}
//加载状态,默认加载中
var loadState = 1
//正在加载
val loading = 1
//加载完成
val loaded = 2
//加载到底
val loadedEnd = 3
inner class ViewHolder(val binding: PaperItemBinding) : RecyclerView.ViewHolder(binding.root)
inner class FooterViewHolder(val footerBinding: PaperItemFooterBinding) :
RecyclerView.ViewHolder(footerBinding.root)
override fun getItemViewType(position: Int): Int {
return if (position + 1 == itemCount) {
TYPE_FOOTER
} else {
TYPE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_ITEM) {
val binding = PaperItemBinding.inflate(LayoutInflater.from(context), parent, false)
ViewHolder(binding)
} else {
val binding =
PaperItemFooterBinding.inflate(LayoutInflater.from(context), parent, false)
FooterViewHolder(binding)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
// 如果当前是footer的位置,那么该item占据4个单元格,正常情况下占据1个单元格
return if (getItemViewType(position) == TYPE_FOOTER) manager.spanCount else 1
}
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder) {
//处理普通item的逻辑
}
if (holder is FooterViewHolder) {
//处理footerView的逻辑
when (loadState) {
loading -> {
holder.footerBinding.paperLoadingAnim.visibility = View.VISIBLE
holder.footerBinding.paperLoadingText.visibility = View.VISIBLE
holder.footerBinding.paperBottomText.visibility = View.GONE
}
loaded -> {
holder.footerBinding.paperLoadingAnim.visibility = View.GONE
holder.footerBinding.paperLoadingText.visibility = View.GONE
holder.footerBinding.paperBottomText.visibility = View.GONE
}
loadedEnd -> {
holder.footerBinding.paperLoadingAnim.visibility = View.GONE
holder.footerBinding.paperLoadingText.visibility = View.GONE
holder.footerBinding.paperBottomText.visibility = View.VISIBLE
}
}
}
}
override fun getItemCount() = paperList.size + 1
fun setLoadingState(state: Int) {
loadState = state
notifyDataSetChanged()
}
}
在getItemCount我们需要返回数据大小 + 1,来为FooterView腾出位置。可以看到,我们定义了两种布局标志,然后重写getItemViewType方法根据position来设置FooterView,然后在onCreateViewHolder方法中根据不同的ViewType来加载不同的布局。
在这里我采用的是网格布局,所以必须想办法让FooterView横跨我网格的列数。那么在这里需要重写一下onAttachedToRecyclerView方法。首先对当前的布局管理器做一个判断,如果是网格布局,那么需要设置一下spanSizeLookup,网格布局的这个方法代表每个Item占据的单元格数,然后设置在FooterView的情况下占据你设置的列数个单元格,我这里是4。普通Item占据一个单元格就可以了。这样我们就将我们的FooterView设置好了。
下面我们要判断一下什么时候展示FooterView,即滑动到最后一个Item的时候,加载下一页数据。因此我们要对RecyclerView设置滑动监听,代码如下
class BottomRecyclerViewOnScrollerListener : RecyclerView.OnScrollListener() {
private var isSlidingUp = false
private var onLoadMore: (() -> Unit)? = null
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val manager = recyclerView.layoutManager as LinearLayoutManager
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val lastItemPosition: Int = manager.findLastCompletelyVisibleItemPosition()
val itemCount = manager.itemCount
// 判断是否滑动到了最后一个item,并且是向上滑动
if (lastItemPosition == itemCount - 1 && isSlidingUp) {
//加载更多
onLoadMore?.invoke()
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// 大于0表示正在向上滑动,小于等于0表示停止或向下滑动
isSlidingUp = dy > 0
}
fun setOnLoadMoreListener(listener: () -> Unit) {
onLoadMore = listener
}
}
代码比较清晰,就不多做解释了,核心就是判断现在是否正在上滑以及是否滑动到了最后一个Item。
只需要给RecyclerView添加监听,做相应的逻辑即可,如下
val bottomListener = BottomRecyclerViewOnScrollerListener()
bottomListener.setOnLoadMoreListener {
if (paperListAdapter != null) {
paperListAdapter!!.setLoadingState(paperListAdapter!!.loading)
//请求下一页的逻辑
} else {
paperListAdapter!!.setLoadingState(paperListAdapter!!.loadedEnd)
}
}
}
paperContentView.addOnScrollListener(bottomListener)
那么到这里,其实已经实现了上拉加载的功能,但是这里有一个小坑,就是在第二页请求数据返回之后,必须追加到第一页数据的List上,不可以重新设置Adapter,否则就每刷新一次就会闪到第一行,不符合预期效果。 除此之外,一般上拉加载同时会伴有下拉刷新以及其他筛选刷新的功能,我们使用的数据源必须是一个List才行,要不然处理逻辑会很麻烦,而且往往达不到我们的效果。
|