当前位置: 首页 > 知识库问答 >
问题:

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

濮金鑫
2023-03-14

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

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

然后,当我通过diffUtil将项目从n(n>0)项通知为零时,问题发生了,我的应用程序崩溃。

值得一提的是,如果我使用没有页眉或页脚的自定义适配器,一切都会好的。

我一直在寻找解决办法,但他们的情况对我来说都不一样。

下面是自定义适配器的代码(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());
        }
    }
}
    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)
    }

共有1个答案

唐涛
2023-03-14

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);
    }
}
 类似资料: