(Android)使用SmartRefreshLayout实现列表预加载

 项目需求实现让用户无感的加载更多的功能,不重复造轮子,在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版权协议,转载请附上原文出处链接和本声明。