同时快速滚动和过滤时,RecyclerView崩溃

问题描述 投票:0回答:1

过滤器本身也可以正常工作,RecyclerView也可以。它可以按预期进行快速滚动,直到我快速滚动并更换过滤器为止。我收到以下错误消息:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 63(offset:63).state:185 androidx.recyclerview.widget.RecyclerView{f4c2e9b VFED.V... ........ 0,683-1440,2690 #7f09012c app:id/recylcerViewItems}, adapter:com.blazecode.scrapguide.ItemListAdapter@eae53a0, layout:androidx.recyclerview.widget.LinearLayoutManager@b748b59, context:com.blazecode.scrapguide.MainActivity@1ba08b4
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6183)
    at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
    at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
    at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
    at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
    at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

我已经在mRecyclerView.stopScroll();之前尝试过mAdapter.getFilter().filter(s);,但没有成功。我认为这降低了它崩溃的可能性,但它仍在发生。

ItemListAdapter.class:

public class ItemListAdapter extends RecyclerView.Adapter<ItemListAdapter.ItemViewHolder> implements Filterable {

private ArrayList<Item> mItemList;
private ArrayList<Item> mItemListFiltered;

private ItemsListAdapterListener mItemsListAdapterListener;


public ItemListAdapter(ArrayList<Item> itemArrayList, ItemsListAdapterListener itemsListAdapterListener) {

    mItemList = itemArrayList;
    mItemListFiltered = itemArrayList;
    this.mItemsListAdapterListener = itemsListAdapterListener;
}


public interface ItemsListAdapterListener {
    void onItemClick(Item item);
}



public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

    public ImageView mImageView;
    public TextView mTextView;


    ItemsListAdapterListener mItemsListAdapterListener;

    public ItemViewHolder(@NonNull View itemView, ItemsListAdapterListener itemsListAdapterListener) {
        super(itemView);


        mItemsListAdapterListener = itemsListAdapterListener;

        mImageView = itemView.findViewById(R.id.imageViewItem);
        mTextView = itemView.findViewById(R.id.textViewItem);

        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        mItemsListAdapterListener.onItemClick(mItemListFiltered.get(getAdapterPosition()));
        //Log.i("test", "Adapter click");
    }

}


@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_layout, parent, false);
    ItemViewHolder itemViewHolder = new ItemViewHolder(view, mItemsListAdapterListener);
    return  itemViewHolder;

}

@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {

    Item item = mItemListFiltered.get(position);

    holder.mImageView.setImageResource(item.getImgURL());
    holder.mTextView.setText(item.getItemName());


}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            String charString = charSequence.toString();
            if (charString.isEmpty()) {
                mItemListFiltered = mItemList;
            } else {
                ArrayList<Item> filteredList = new ArrayList<>();
                for (Item row: mItemList) {

                    // name match condition. this might differ depending on your requirement
                    // here we are looking for name or phone number match
                    if (row.getItemName().toLowerCase().contains(charString.toLowerCase()) || row.getItemCategory().toLowerCase().contains(charString.toLowerCase())) {

                        filteredList.add(row);

                    }
                }

                mItemListFiltered = (ArrayList<Item>) filteredList;
            }

            FilterResults filterResults = new FilterResults();
            filterResults.values = mItemListFiltered;
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            mItemListFiltered = (ArrayList<Item>) filterResults.values;
            notifyDataSetChanged();
        }
    };
}

@Override
public int getItemCount() {
    return mItemListFiltered.size();
}
}

ItemFragment.class部分,正在设置过滤器:

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

    mAdapter.getFilter().filter(s);

}

@Override
public void afterTextChanged(Editable s) {

    if(mAdapter.getItemCount() == 0){
        mRecyclerView.setVisibility(View.GONE);
        constraintLayoutMissingItem.setVisibility(View.VISIBLE);
    } else {
        mRecyclerView.setVisibility(View.VISIBLE);
        constraintLayoutMissingItem.setVisibility(View.GONE);
    }
}

为什么会引发此错误,我该如何解决?

android android-recyclerview recycler-adapter
1个回答
1
投票

问题原因:在进行快速滚动时,将已经计算出RecylerView变为IDLE时达到的最终索引。现在,当它尝试访问该索引处的项目时,将找不到它。原因是您已经用较新的mFilteredListItem(可能没有那么多的项目)将mAdapter中的mFilteredListItem重设了。解决方案编号:1您可以使用标记概念来检查是否可以立即显示过滤的结果,以避免崩溃。为此,创建一个布尔变量,如下所示:

 boolean shouldShowFilteredResults = true;  

然后,在OnScrollListener()中添加RecyclerView,以了解其滚动速度有多快,以便相应地启用或禁用我们的标志变量,如:

rcView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                // get rid of every earlier code written here
                // when dx=dy=0, then then method is not called
                // that's why earlier code was not working
                super.onScrolled(recyclerView, dx, dy);
            }

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (!shouldshowFilteredResults) 
                        shouldshowFilteredResults = true;
                } else {
                    if (shouldshowFilteredResults) 
                        shouldshowFilteredResults = false;
                }
                super.onScrollStateChanged(recyclerView, newState);
            }
        });  

旁注:您也可以使用onScrollStateChanged检查RecyclerView状态的变化,例如SCROLL_STATE_DRAGGINGSCROLL_STATE_IDLESCROLL_STATE_SETTLING,以适当地设置我们的标志变量的值。

然后,在onTextChanged()方法内,检查shouldShowFilteredResults变量以确定是否立即显示结果。

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    if(shouldShowFilteredResults)
        mAdapter.getFilter().filter(s);

}

解决方案编号:2您可以使用非平滑版本的scrollToPosition(0)即时滚动到第一个位置,而不用调用stopScrolling()。然后,使用mAdapter.getFilter().filter(s)对象延迟调用Handler方法。建议:(这与答案无关)

  • [我发现mItemList中的两个变量mItemFilteredListItemListAdapter最初具有相同的值,然后一直使用第二个变量。如果您不需要,可以摆脱它。
  • getFilter()方法内部,您写了filterResults.values = mItemListFiltered;然后,稍后在publishResults()中,您写道mItemListFiltered = (ArrayList<Item>) filterResults.values;我认为这是多余的。
© www.soinside.com 2019 - 2024. All rights reserved.