gpt4 book ai didi

android - 使用 DiffUtil 和自定义适配器导致检测到不一致

转载 作者:太空狗 更新时间:2023-10-29 13:44:35 25 4
gpt4 key购买 nike

目前我尝试在我的项目中使用DiffUtil来通知RecyclerView的项目。

与此同时,我编写了一个可以添加页眉和页脚的自定义适配器。我使用了这个适配器并添加了一个加载更多的页脚,这样我的列表在向下滚动时可以加载更多。这引出了一个观点:我的可以加载更多内容的列表将始终包含至少一项(加载更多页脚)。

为避免误解,下面的“项目”一词将特别表示这些项目不是页眉或页脚。

然后当我通过 diffUtil 通知项目从 n (n > 0) 个项目到零时,我的应用程序崩溃了。

这是异常:java.lang.IndexOutOfBoundsException:检测到不一致。无效的 View 持有者适配器 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 的主要代码(Kotlin):

    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为我工作。但我认为它只是避免崩溃而不是解决它以确保这不会再导致异常。所以我认为这不是一个好方法。所以我来寻求帮助。

最佳答案

getItemCount()的实现来看,页脚似乎只有在有item的时候才会显示。当最后一项被移除时,您必须通知包装器适配器页脚也被移除。

假设有 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);
}
}

关于android - 使用 DiffUtil 和自定义适配器导致检测到不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56787731/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com