过滤器本身也可以正常工作,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);
}
}
为什么会引发此错误,我该如何解决?
问题原因:在进行快速滚动时,将已经计算出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_DRAGGING
,SCROLL_STATE_IDLE
和SCROLL_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
中的两个变量mItemFilteredList
和ItemListAdapter
最初具有相同的值,然后一直使用第二个变量。如果您不需要,可以摆脱它。getFilter()
方法内部,您写了filterResults.values = mItemListFiltered;
然后,稍后在publishResults()
中,您写道mItemListFiltered = (ArrayList<Item>) filterResults.values;
我认为这是多余的。