当前,我尝试使用DiffUtil通知项目中RecyclerView的项目。
同时,我编写了一个自定义适配器,可以添加页眉和页脚。 我使用了此适配器,并添加了更多的页脚,以便在向下滚动时列表可以加载更多内容。这导致了一个问题:我的列表可能会加载更多内容,因此将至少容纳一项(加载更多页脚)。
为避免造成误解,下面的“项目”一词将专门表示这些项目不是页眉或页脚。
然后,当我通过diffUtil将项目从n(n> 0)个项目通知为零时,出现了问题,我的应用程序崩溃了。
这里是例外:java.lang.IndexOutOfBoundsException:检测到不一致。无效的视图持有人适配器positionViewHolder
值得一提的是,如果我使用自定义适配器没有页眉或页脚,一切都会好的。
我一直在寻找解决方案,但是他们的情况对我来说都不一样。
以下是自定义适配器的代码(Java),diffUtil将在其中分发 innerAdapter 的更新:
public class WrapperAdapter extends RecyclerView.Adapter {
// 0x40000000 为 1位 flag 位,1为 HEADER ,0 为 FOOTER
private static final int HEADER_FOOTER_TYPE_MASK = 0x40000000;
// 0x3f0000 为 6位 index 位,对应的 HEADER FOOTER 数组的 index ,也就是说最多保存64个 HEADER 、64个 FOOTER
private static final int HEADER_FOOTER_INDEX_MASK = 0x3f000000;
// 后面的 24 位为 view 的 hash code 位,可以保证每个 HEADER、FOOTER 都能有不同的 viewType
private static final int HEADER_FOOTER_HASH_MASK = 0x00ffffff;
private RecyclerView.Adapter innerAdapter;
private RecyclerView.AdapterDataObserver innerObserver = new AdapterDataObserverProxy();
private List<View> headers = new ArrayList<>();
private List<View> footers = new ArrayList<>();
public WrapperAdapter(@NonNull RecyclerView.Adapter adapter) {
innerAdapter = adapter;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType < 0) {
if ((viewType & HEADER_FOOTER_TYPE_MASK) != 0) {
// HEADER
int headerIndex = (viewType & HEADER_FOOTER_INDEX_MASK) >> 24;
return new InnerViewHolder(headers.get(headerIndex));
} else {
// FOOTER
int footerIndex = (viewType & HEADER_FOOTER_INDEX_MASK) >> 24;
return new InnerViewHolder(footers.get(footerIndex));
}
}
return innerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (isHeader(position) || isFooter(position)) {
return;
}
innerAdapter.onBindViewHolder(holder, position - headers.size());
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) {
if (isHeader(position) || isFooter(position)) {
return;
}
innerAdapter.onBindViewHolder(holder, position - headers.size(), payloads);
}
@Override
public int getItemViewType(int position) {
if (isHeader(position)) {
return Integer.MIN_VALUE | HEADER_FOOTER_TYPE_MASK | ((position & (HEADER_FOOTER_INDEX_MASK >> 24)) << 24) | (headers.get(position).hashCode() & HEADER_FOOTER_HASH_MASK);
}
if (isFooter(position)) {
int footerIndex = position - innerAdapter.getItemCount() - headers.size();
return Integer.MIN_VALUE | ((footerIndex & (HEADER_FOOTER_INDEX_MASK >> 24)) << 24) | (footers.get(footerIndex).hashCode() & HEADER_FOOTER_HASH_MASK);
}
int innerViewType = innerAdapter.getItemViewType(position - headers.size());
if (innerViewType < 0) {
throw new IllegalArgumentException("View type cannot be negative, which is claimed by HEADER and FOOTER");
}
return innerViewType;
}
@Override
public int getItemCount() {
if (innerAdapter.getItemCount() == 0) {
return headers.size();
} else {
return innerAdapter.getItemCount() + headers.size() + footers.size();
}
}
private boolean isHeader(int position) {
return position < headers.size();
}
private boolean isFooter(int position) {
return position > getItemCount() - footers.size() - 1;
}
private class InnerViewHolder extends RecyclerView.ViewHolder {
InnerViewHolder(@NonNull View itemView) {
super(itemView);
}
}
public void addHeader(@NonNull View header) {
if (!headers.contains(header)) {
headers.add(header);
notifyItemInserted(headers.size() - 1);
}
}
public void removeHeader(@NonNull View header) {
if (headers.contains(header)) {
int index = headers.indexOf(header);
headers.remove(index);
notifyItemRemoved(index);
}
}
public void addFooter(@NonNull View footer) {
if (!footers.contains(footer)) {
footers.add(footer);
notifyItemInserted(getItemCount() - 1);
}
}
public void removeFooter(@NonNull View footer) {
if (footers.contains(footer)) {
int index = footers.indexOf(footer);
footers.remove(index);
notifyItemRemoved(headers.size() + innerAdapter.getItemCount() + index);
}
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof InnerViewHolder) {
super.onViewRecycled(holder);
} else {
innerAdapter.onViewRecycled(holder);
}
}
@Override
public boolean onFailedToRecycleView(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof InnerViewHolder) {
return super.onFailedToRecycleView(holder);
} else {
return innerAdapter.onFailedToRecycleView(holder);
}
}
@Override
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof InnerViewHolder) {
super.onViewAttachedToWindow(holder);
} else {
innerAdapter.onViewAttachedToWindow(holder);
}
}
@Override
public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof InnerViewHolder) {
super.onViewDetachedFromWindow(holder);
} else {
innerAdapter.onViewDetachedFromWindow(holder);
}
}
@Override
public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
super.registerAdapterDataObserver(observer);
innerAdapter.registerAdapterDataObserver(innerObserver);
}
@Override
public void unregisterAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
super.unregisterAdapterDataObserver(observer);
innerAdapter.unregisterAdapterDataObserver(innerObserver);
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
innerAdapter.onAttachedToRecyclerView(recyclerView);
}
@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
innerAdapter.onDetachedFromRecyclerView(recyclerView);
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(hasStableIds);
innerAdapter.setHasStableIds(hasStableIds);
}
@Override
public long getItemId(int position) {
if (isHeader(position)) return super.getItemId(position);
if (isFooter(position)) return super.getItemId(position);
return innerAdapter.getItemId(position);
}
private class AdapterDataObserverProxy extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {
WrapperAdapter.this.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeChanged(positionStart + WrapperAdapter.this.headers.size(), itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
WrapperAdapter.this.notifyItemRangeChanged(positionStart + WrapperAdapter.this.headers.size(), itemCount, payload);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeInserted(positionStart + WrapperAdapter.this.headers.size(), itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeRemoved(positionStart + WrapperAdapter.this.headers.size(), itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
WrapperAdapter.this.notifyItemMoved(fromPosition + WrapperAdapter.this.headers.size(), toPosition + WrapperAdapter.this.headers.size());
}
}
}
以下是使用自定义适配器的recyclerView的主要代码(科特琳):
private var wrapperAdapter: WrapperAdapter? = null
override fun setAdapter(adapter: Adapter<*>?) {
wrapperAdapter = if (adapter != null) {
WrapperAdapter(adapter)
} else {
null
}
super.setAdapter(wrapperAdapter)
}
fun addHeader(header: View) {
wrapperAdapter?.addHeader(header)
}
fun addFooter(footer: View) {
wrapperAdapter?.addFooter(footer)
}
fun removeHeader(header: View) {
wrapperAdapter?.removeHeader(header)
}
fun removeFooter(footer: View) {
wrapperAdapter?.removeFooter(footer)
}
this question最有用的答案是为我工作的。 但是我认为这只是避免崩溃,而不是解决它以确保它不再引起异常。所以我认为这不是一个好方法。所以我来寻求帮助。
答案 0 :(得分:1)
从getItemCount()
的实现来看,似乎只在有项目时才显示页脚。当最后一个项目被删除时,您必须通知包装适配器该页脚也被删除。
说有2个页眉,1个项目,3个页脚:getItemCount()
返回6。如果删除内部项目,则仅从内部适配器传播1个删除内容,但是getItemCount()
现在返回2预期的5
我建议修改AdapterDataObserverProxy
:
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeRemoved(positionStart + WrapperAdapter.this.headers.size(), itemCount);
if (itemCount > 0 && innerAdapter.getItemCount() == 0 && footers.size() > 0) {
// no more inner items, notify the removal of the footers
int firstFooterPosition = headers.size();
int footerCount = footers.size();
WrapperAdapter.this.notifyItemRangeRemoved(firstFooterPosition, footerCount);
}
}