RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法
今天使用 RecyclerView ,刪除某個元素后,再點擊后面的元素,會奔潰:
java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)at java.util.ArrayList.get(ArrayList.java:308)at com.yuntu.yaomaiche.common.adapters.BuyCarPlanLoadMoreAdapter.getItem(BuyCarPlanLoadMoreAdapter.java:111)at com.yuntu.yaomaiche.common.adapters.BuyCarPlanLoadMoreAdapter$1.onClick(BuyCarPlanLoadMoreAdapter.java:122)at android.view.View.performClick(View.java:4457)at android.view.View$PerformClick.run(View.java:18496)at android.os.Handler.handleCallback(Handler.java:733)at android.os.Handler.dispatchMessage(Handler.java:95)at android.os.Looper.loop(Looper.java:136)at android.app.ActivityThread.main(ActivityThread.java:5291)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:515)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)at dalvik.system.NativeStart.main(Native Method)看 log 的意思是數組越界,之前兩個元素,刪除一個后,第二個不應該變成第一個嗎?怎么點擊時對應的 position 還是 2 ?
點擊事件的注冊是在 RecyclerView 的 onBindViewHolder 中:
public void onBindViewHolder(BuyCarPlanItemViewHolder holder, int position) {//...holder.rootView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mClickListener != null) {mClickListener.onClick(v, getItem(position)); //這里使用 position 獲取數據}}});//...}上面代碼在點擊事件 onClick() 中使用 onBindViewHolder() 方法中的參數 position 來獲取數據, Android Studio 中有個提示:
Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later less… (?F1)
RecyclerView will not call onBindViewHolder again when the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined.
For this reason, you should only use the position parameter while acquiring the related data item inside this method, and should not keep a copy of it.
If you need the position of an item later on (e.g. in a click listener), use getAdapterPosition() which will have the updated adapter position.
大概意思就是:
RecyclerView 中的數據有位置改變(比如刪除)時一般不會重新調用 onBindViewHolder() 方法,除非這個元素不可用。
也就是說 onBindViewHolder() 方法中的位置參數 position 不是實時更新的,所以在我們刪除元素后,item 的 position 沒有改變。
為了實時獲取元素的位置,RecyclerView 為我們提供了 ViewHolder.getAdapterPosition() 方法。
當我把上面奔潰的代碼中的 position 換成 holder.getAdapterPosition() 就解決了問題。
ViewHolder 和 RecyclerView 的關系我們知道,就是存儲、復用指定位置對于的 ItemView。
RecyclerView 一般情況下不會處理任何 adapter 的更新,除非重新繪制界面。這導致有時候用戶想象中的和實際 RecyclerView 呈現的不一致。
下面看看 ViewHolder.getAdapterPosition 的源碼:
public final int getAdapterPosition() {if (mOwnerRecyclerView == null) {return NO_POSITION;}return mOwnerRecyclerView.getAdapterPositionFor(this);}ViewHolder.getAdapterPosition 方法返回當前 ViewHolder 在整個 adapter 中的位置,實時更新,用來獲取數據比較靠譜。只有當重新繪制、未繪制的時候會返回 -1,不過這只在繪制效率比較低的時候才會發生。
ViewHolder.getAdapterPosition 方法 調用了 RecyclerView.getAdapterPositionFor(this) :
private int getAdapterPositionFor(ViewHolder viewHolder) {if (viewHolder.hasAnyOfTheFlags( ViewHolder.FLAG_INVALID |ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)|| !viewHolder.isBound()) {return RecyclerView.NO_POSITION;}return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); }可以看到,第一個 if 里就是處理當前 ViewHolder 不可用的狀態,正常情況下調用 mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition) :
public int applyPendingUpdatesToPosition(int position) {final int size = mPendingUpdates.size();for (int i = 0; i < size; i ++) {UpdateOp op = mPendingUpdates.get(i);switch (op.cmd) { //不同的更新邏輯,對 position 進行不同的修改case UpdateOp.ADD: // 增加了元素,就增加 positionif (op.positionStart <= position) {position += op.itemCount;}break;case UpdateOp.REMOVE: //刪除了元素就減少if (op.positionStart <= position) {final int end = op.positionStart + op.itemCount;if (end > position) {return RecyclerView.NO_POSITION;}position -= op.itemCount;}break;case UpdateOp.MOVE: // 移動了元素后,針對當前位置前后的元素,有不同的處理if (op.positionStart == position) {position = op.itemCount;//position end} else {if (op.positionStart < position) {position -= 1;}if (op.itemCount <= position) {position += 1;}}break;}}return position; }可以看到在 applyPendingUpdatesToPosition() 方法里針對我們對 RecyclerView Item 不同的操作,對元素的位置有了響應的加減,保證拿到的是最準確的位置。
總結
以上是生活随笔為你收集整理的RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黑马程序员--IO总结(含2个设计模式)
- 下一篇: UVA 11991 Easy Prob