美文网首页Android-recyclerview
RecyclerView基础篇-自动加载更多

RecyclerView基础篇-自动加载更多

作者: dashingqi | 来源:发表于2020-09-02 14:55 被阅读0次
Android_Banner.jpg

简介

  • 该篇文章使用RecyclerView实现一个自动加载更多数据的功能
  • 实例效果


    rv_auto.gif
  • 由于是从本地直接拿到的数据,是同步的,看起来很顺滑的。

实现

分析
  • 该效果的实现主要监听Rv滑动过程中的回调,在回调中我们根据条件从而去更新Adapter中的数据源
条件的分析
  • 我自己的做法是 当滑动到最有一条数据时,拿到它的top与rv的bottom,进行对比,如果小于就填充数据
  • 怎么判断是最后一条数据呢?
    • 首先获取到item的总数,itemCount
    • 接着获取到当前可见区域内最后一条数据对应的lastPosition
    • 然后根据lastPosition拿到对应的ItemView
    • 判断itemView.top 与 rv.bottom的关系,同时 itemCount == lastPosition
贴代码了
  • activity-auto.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.dashingqi.module.recyclerview.RvAutoViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RvAutoActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/autoRv"
            itemBinding="@{viewModel.itemBinding}"
            items="@{viewModel.items}"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • AutoActivity.kt代码
class RvAutoActivity : AppCompatActivity() {
    val manager by lazy {
        autoRv.layoutManager as LinearLayoutManager
    }
    val viewModel by lazy {
        ViewModelProvider(this)[RvAutoViewModel::class.java]
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val rvAutoBinding =
            DataBindingUtil.setContentView<ActivityRvAutoBinding>(this, R.layout.activity_rv_auto)
        rvAutoBinding.viewModel = viewModel

        autoRv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                //获取到Adapter中当前设置的数据数量
                val itemCount = manager.itemCount
                //当前可见区域的最后一个ItemView的角标
                val lastVisiblePos = manager.findLastVisibleItemPosition()
                //获取到可见区域的最后一个ItemView
                val itemView = manager.getChildAt(manager.childCount - 1)
                //当itemView不为空的时候
                itemView?.let {
                    val lastIteViewTop = it.top
                    val autoRvBottom = autoRv.bottom
                    if (lastIteViewTop < autoRvBottom && lastVisiblePos == itemCount - 1) {
                        //加载更多数据
                        autoRv.post {
                            viewModel.loadMoreData()
                        }
                    }
                }
            }
        })
    }
}
  • AutoViewModel.kt
class RvAutoViewModel : ViewModel() {

    private var imgUrls = mutableListOf(
        "https://img.fulaishiji.com/images/goods/19883/big/03957c4d-6869-4cef-ad3e-824852f9da2b_800x800.png",
        "https://img.fulaishiji.com/images/goods/19307/big/e2635d9a-d5eb-4acf-b08c-44483a8554e2_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/17249/big/3389ee28-5b14-4b55-8f5d-64a00d5deb37_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/12461/big/b0be6cbd-3164-470b-b355-d9b41b0ce0e6_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/16743/big/65843bf9-7ba1-48d0-884e-aefeebf9b635_964x964.jpg",
        "https://img.fulaishiji.com/images/goods/12534/big/3dabd1b5-f37f-4320-9832-fccbdc8e1ecf_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/14897/big/e1c774c9-7003-4a4a-9e12-1bdf1d729457_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/12753/middle/95f064c5-8bfc-44d6-a7d8-5058cb3f93e7_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/10396/middle/e174305c-b32c-4b9d-a0b1-a14de0ae011a_3648x3648.jpg",
        "https://img.fulaishiji.com/images/goods/17824/middle/19d6253c-1ef9-48f0-b63f-d01d2f354eee_2728x2728.jpg",
        "https://img.fulaishiji.com/images/goods/17337/middle/9974521a-34c4-4daf-9c07-717dface9cce_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/13223/middle/a0a32982-3af5-45ae-9252-a554503ec829_800x800.jpg",
        "https://img.fulaishiji.com/images/goods/19906/middle/41dca304-69b2-490f-b001-0b99fa4fcc60_1008x1008.jpg",
        "https://img.fulaishiji.com/images/goods/19653/middle/c31cca8b-7468-4d5f-a06c-8427a7d01656_800x800.jpg"
    )

    val items = ObservableArrayList<Food>()
    val itemBinding = ItemBinding.of<Food>(BR.item, R.layout.rv_item)

    init {
        for (index in 0 until 10) {
            val food = Food()
            food.name = "name $index"
            food.desc = "desc $index"
            val position = (Math.random() * (imgUrls.size - 1)).toInt()
            food.imgUrl = imgUrls[position]
            items.add(food)
        }
    }

    fun loadMoreData() {
        for (index in 0 until 10) {
            val food = Food()
            food.name = "name $index 这是加载更多的数据"
            food.desc = "desc $index 加载更多的数据"
            val position = (Math.random() * (imgUrls.size - 1)).toInt()
            food.imgUrl = imgUrls[position]
            items.add(food)
        }
    }
}
遇到的问题
  • 在滑动的过程中,看到log打印出了如下异常
Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.
    java.lang.IllegalStateException:  androidx.recyclerview.widget.RecyclerView{c5c5ea2 VFED..... ......ID 0,0-1080,2133 #7f070040 app:id/autoRv}, adapter:me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter@b75d733, layout:androidx.recyclerview.widget.LinearLayoutManager@d305af0, context:com.dashingqi.module.recyclerview.RvAutoActivity@90fe28d
        at androidx.recyclerview.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:3061)
        at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeInserted(RecyclerView.java:5555)
        at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyItemRangeInserted(RecyclerView.java:12278)
        at androidx.recyclerview.widget.RecyclerView$Adapter.notifyItemRangeInserted(RecyclerView.java:7498)
        at me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter$WeakReferenceOnListChangedCallback.onItemRangeInserted(BindingRecyclerViewAdapter.java:307)
        at androidx.databinding.ListChangeRegistry$1.onNotifyCallback(ListChangeRegistry.java:48)
        at androidx.databinding.ListChangeRegistry$1.onNotifyCallback(ListChangeRegistry.java:39)
        at androidx.databinding.CallbackRegistry.notifyCallbacks(CallbackRegistry.java:201)
        at androidx.databinding.CallbackRegistry.notifyFirst64(CallbackRegistry.java:122)
        at androidx.databinding.CallbackRegistry.notifyRemainder(CallbackRegistry.java:169)
        at androidx.databinding.CallbackRegistry.notifyRecurse(CallbackRegistry.java:145)
        at androidx.databinding.CallbackRegistry.notifyCallbacks(CallbackRegistry.java:91)
        at androidx.databinding.ListChangeRegistry.notifyCallbacks(ListChangeRegistry.java:136)
        at androidx.databinding.ListChangeRegistry.notifyInserted(ListChangeRegistry.java:94)
        at androidx.databinding.ObservableArrayList.notifyAdd(ObservableArrayList.java:118)
        at androidx.databinding.ObservableArrayList.add(ObservableArrayList.java:45)
        at com.dashingqi.module.recyclerview.RvAutoViewModel.loadMoreData(RvAutoViewModel.kt:52)
        at com.dashingqi.module.recyclerview.RvAutoActivity$onCreate$1.onScrolled(RvAutoActivity.kt:43)
        at androidx.recyclerview.widget.RecyclerView.dispatchOnScrolled(RecyclerView.java:5173)
        at androidx.recyclerview.widget.RecyclerView.scrollByInternal(RecyclerView.java:1971)
        at androidx.recyclerview.widget.RecyclerView.onTouchEvent(RecyclerView.java:3391)
        at android.view.View.dispatchTouchEvent(View.java:13552)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3082)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2767)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:498)
        at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1853)
        at android.app.Activity.dispatchTouchEvent(Activity.java:4059)
2020-09-02 14:33:07.887 30280-30280/com.dashingqi.module.recyclerview W/RecyclerView:     at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:456)
        at android.view.View.dispatchPointerEvent(View.java:13813)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5633)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5433)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4934)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4987)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4953)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5093)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4961)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5150)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4934)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4987)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4953)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4961)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4934)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7687)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7656)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7617)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7817)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:251)
        at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
        at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:213)
        at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:7763)
        at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:7841)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1031)
        at android.view.Choreographer.doCallbacks(Choreographer.java:854)
        at android.view.Choreographer.doFrame(Choreographer.java:782)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1016)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7584)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
  • 看起来好长,其实有用的信息就是
//信息一
Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.

//信息二
 at com.dashingqi.module.recyclerview.RvAutoViewModel.loadMoreData(RvAutoViewModel.kt:52)
        at com.dashingqi.module.recyclerview.RvAutoActivity$onCreate$1.onScrolled(RvAutoActivity.kt:43)
  • 信息一:说我不能在滑动的回调中调用这个方法,因为在这之后可能rv正在测量(measure)或者在布局(layout),这时候我们不能修改rv中的数据,结合信息二中定位的代码中来看,我实在onScrolled中直接去填充数据到数据源了。
  • 上述是信息一中前部分描述出错的原因,不过后部分也提供了解决办法,任何修改rv的结构或者适配器中的数据都应该在下一帧操作。
Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.
  • 所以针对上述分析,我在onScrolled中,使用了post发出一个message,在回调中进行数据填充
autoRv.post {
      viewModel.loadMoreData()
 }
  • 关于上述出现的异常,stack overflow上也有讨论
    链接

相关文章

网友评论

    本文标题:RecyclerView基础篇-自动加载更多

    本文链接:https://www.haomeiwen.com/subject/gxqwsktx.html