项目需求实现让用户无感的加载更多的功能,不重复造轮子,在Github找到如下几种方案
- SmartRefreshLayout 目前我用这个,但是只实现了demo,还没导入到项目中,后面靠测试部的同事了
- SwipeRecyclerView 以前的项目用这个框架实现过列表项左滑删除的功能,但下拉刷新没用过。这个框架很长时间没更新了,所以没选用
- Ultra-Pull-To-Refresh 没用过这个,而且停止维护了,所以没选
- BaseRecyclerViewAdapterHelper 有现成的加载更多和预加载功能,但是我的adapter里面东西有点多,移过去怕少东西,就没用,这个还是蛮好用的。
因为在SmartRefreshLayout中调用autoLoadMore方法后,是会直接把footer显示在布局下面,而不是滚动到列表最后面开始,或隐藏动画加载。
自定义实现的autoLoadMoreNoSense方法对于autoloadmore修改并不多,只是把显示footer的动画操作移除,然后状态改变后,没有调用setStateLoading方法,使用的自定义的setStateLoadingNoSpinner方法。
需要注意的是在setStateLoadingNoSpinner方法中的这段:
mFooterLocked = false
在调用setStateDirectLoading后,会将该属性设为true,锁定footer控件,然后再滚动的时候会触发嵌套滑动,将触摸事件传递到父控件去。所以在该方法执行完后要将mfooterLocked属性改回来。触发这里的嵌套滑动,有兴趣的可以去查一下“嵌套滑动”的机制。
以下代码是PreloadSmartLayout的实现。
class PreloadSmartLayout constructor(context: Context, attrs: AttributeSet?) :
SmartRefreshLayout(context, attrs) {
constructor(context: Context) : this(context, null)
fun autoLoadMoreNosense(): Boolean {
if (mState == RefreshState.None && (isEnableRefreshOrLoadMore(mEnableLoadMore) && !mFooterNoMoreData)) {
setViceState(RefreshState.Loading)
reboundAnimator = reboundAnimator?.run {
duration = 0
cancel()
null
}
mKernel.setState(RefreshState.PullUpToLoad)
if (mRefreshFooter != null) {
if (mState != RefreshState.ReleaseToLoad) {
mKernel.setState(RefreshState.ReleaseToLoad)
}
setStateLoadingNoSpinner()
} else {
mKernel.setState(RefreshState.None)
}
return true
} else {
return false
}
}
private fun setStateLoadingNoSpinner() {
notifyStateChanged(RefreshState.LoadReleased)
setStateDirectLoading(true)
// 在调用setStateDirectLoading方法后,mFooterLocked会变为true,表示footer被锁定,
// 此时上拉会触发嵌套滚动,滚动的是父控件而非列表,会让footer提前展示出来
mFooterLocked = false
val maxDragHeight: Float =
if (mFooterMaxDragRate < 10) mFooterHeight * mFooterMaxDragRate else mFooterMaxDragRate
if (mRefreshFooter is ClassicsFooter) {
(mRefreshFooter as ClassicsFooter).onReleased(
this,
mFooterHeight,
maxDragHeight.toInt()
)
}
}
}
然后怎么使用是参考了 BaseRecyclerViewAdapterHelper中的实现,在onBindViewHolder中的position到达自己设定的预加载标志时回调事件,然后调用autoLoadMoreNoSence。如下是BRVAH中的实现
注意:在收到adapter中预加载回调时要判断当前的加载状态,因为该回调会被调用多次。
下面是我的Fragment中的列表实现:
private fun bindList() {
binding.listParent.apply {
setRefreshHeader(
ClassicsHeader(requireContext()),
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setRefreshFooter(
ClassicsFooter(requireContext()),
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setEnableAutoLoadMore(false) // 关闭列表滚动时自动触发加载更多
setEnableOverScrollDrag(false) // 设置是否启用越界拖动(仿苹果效果)
setEnableOverScrollBounce(false) // 是否启用越界回弹
}.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
override fun onRefresh(refreshLayout: RefreshLayout) {
viewModel.refreshList()
}
override fun onLoadMore(refreshLayout: RefreshLayout) {
LogUtils.d("onLoadMore()")
viewModel.loadMore()
}
})
binding.recyclerList.adapter = MyAdapter(R.layout.layout_item)
.apply {
setOnItemClickListener { item, _ ->
navToDetail(item.id)
}
openPreload(5, object : OnPreLoadListener {
override fun onPreload() {
binding.listParent.run {
if (!isLoading) { // 这里需要判断加载状态,因为会被多次调用
autoLoadMoreNosense()
}
}
}
})
}
}
以上功能目前只实现了demo,没有导入到正式项目中验证。如果有发现BUG请及时指出来,赠人玫瑰手有余香,互相帮助。
End
版权声明:本文为akebrt原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。