简介
- 该篇文章使用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上也有讨论
链接
网友评论