RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法
今天使用 RecyclerView ,刪除某個(gè)元素后,再點(diǎn)擊后面的元素,會(huì)奔潰:
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 的意思是數(shù)組越界,之前兩個(gè)元素,刪除一個(gè)后,第二個(gè)不應(yīng)該變成第一個(gè)嗎?怎么點(diǎn)擊時(shí)對(duì)應(yīng)的 position 還是 2 ?
點(diǎn)擊事件的注冊(cè)是在 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 獲取數(shù)據(jù)}}});//...}上面代碼在點(diǎn)擊事件 onClick() 中使用 onBindViewHolder() 方法中的參數(shù) position 來(lái)獲取數(shù)據(jù), Android Studio 中有個(gè)提示:
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 中的數(shù)據(jù)有位置改變(比如刪除)時(shí)一般不會(huì)重新調(diào)用 onBindViewHolder() 方法,除非這個(gè)元素不可用。
也就是說(shuō) onBindViewHolder() 方法中的位置參數(shù) position 不是實(shí)時(shí)更新的,所以在我們刪除元素后,item 的 position 沒(méi)有改變。
為了實(shí)時(shí)獲取元素的位置,RecyclerView 為我們提供了 ViewHolder.getAdapterPosition() 方法。
當(dāng)我把上面奔潰的代碼中的 position 換成 holder.getAdapterPosition() 就解決了問(wèn)題。
ViewHolder 和 RecyclerView 的關(guān)系我們知道,就是存儲(chǔ)、復(fù)用指定位置對(duì)于的 ItemView。
RecyclerView 一般情況下不會(huì)處理任何 adapter 的更新,除非重新繪制界面。這導(dǎo)致有時(shí)候用戶想象中的和實(shí)際 RecyclerView 呈現(xiàn)的不一致。
下面看看 ViewHolder.getAdapterPosition 的源碼:
public final int getAdapterPosition() {if (mOwnerRecyclerView == null) {return NO_POSITION;}return mOwnerRecyclerView.getAdapterPositionFor(this);}ViewHolder.getAdapterPosition 方法返回當(dāng)前 ViewHolder 在整個(gè) adapter 中的位置,實(shí)時(shí)更新,用來(lái)獲取數(shù)據(jù)比較靠譜。只有當(dāng)重新繪制、未繪制的時(shí)候會(huì)返回 -1,不過(guò)這只在繪制效率比較低的時(shí)候才會(huì)發(fā)生。
ViewHolder.getAdapterPosition 方法 調(diào)用了 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); }可以看到,第一個(gè) if 里就是處理當(dāng)前 ViewHolder 不可用的狀態(tài),正常情況下調(diào)用 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) { //不同的更新邏輯,對(duì) position 進(jìn)行不同的修改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: // 移動(dòng)了元素后,針對(duì)當(dāng)前位置前后的元素,有不同的處理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() 方法里針對(duì)我們對(duì) RecyclerView Item 不同的操作,對(duì)元素的位置有了響應(yīng)的加減,保證拿到的是最準(zhǔn)確的位置。
總結(jié)
以上是生活随笔為你收集整理的RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 黑马程序员--IO总结(含2个设计模式)
- 下一篇: UVA 11991 Easy Prob