Использование DiffUtil и пользовательского адаптера приводит к обнаружению несоответствия - PullRequest
3 голосов
/ 27 июня 2019

В настоящее время я пытался использовать 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());
        }
    }
}

Вот основные коды (Kotlin) 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)
    }

Самый полезный ответ , этот вопрос работает для меня.Но я думаю, что нужно просто избегать сбоев, а не решать, чтобы гарантировать, что это больше не вызовет исключений.Поэтому я не думаю, что это хороший способ. Так что я пришел за помощью.

1 Ответ

1 голос
/ 27 июня 2019

Из реализации 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...