package android.support.v7.widget; import android.support.annotation.NonNull; import android.util.SparseArray; import android.view.ViewGroup; import java.util.Arrays; import java.util.List; /** * Adapter which concatenates the items of multiple adapters. * Doesn't support stable ids, but properly delegates changes notifications. * <p> * Adapters may provide multiple view types but they must not overlap. * It's recommended to always use the item layout id as view type. * <p> * Warning: You need to use ConcatAdapter.getAdapterPosition(ViewHolder) * in place of ViewHolder.getAdapterPosition() inside child adapters. * * @author Christophe Beyls */ public class ConcatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final RecyclerView.Adapter<RecyclerView.ViewHolder>[] adapters; private final RecyclerView.AdapterDataObserver[] adapterObservers; final int[] offsets; int totalItemCount = -1; private final SparseArray<RecyclerView.Adapter<RecyclerView.ViewHolder>> viewTypeAdapters = new SparseArray<>(); private class InternalObserver extends RecyclerView.AdapterDataObserver { private final int adapterIndex; InternalObserver(int adapterIndex) { this.adapterIndex = adapterIndex; } @Override public void onChanged() { totalItemCount = -1; notifyDataSetChanged(); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { if (totalItemCount != -1) { notifyItemRangeChanged(positionStart + offsets[adapterIndex], itemCount); } } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { if (totalItemCount != -1) { notifyItemRangeChanged(positionStart + offsets[adapterIndex], itemCount, payload); } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { if (totalItemCount != -1) { for (int i = adapterIndex + 1, size = offsets.length; i < size; ++i) { offsets[i] += itemCount; } totalItemCount += itemCount; notifyItemRangeInserted(positionStart + offsets[adapterIndex], itemCount); } } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { if (totalItemCount != -1) { for (int i = adapterIndex + 1, size = offsets.length; i < size; ++i) { offsets[i] -= itemCount; } totalItemCount -= itemCount; notifyItemRangeRemoved(positionStart + offsets[adapterIndex], itemCount); } } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { if (totalItemCount != -1) { final int offset = offsets[adapterIndex]; notifyItemMoved(fromPosition + offset, toPosition + offset); } } } @SuppressWarnings("unchecked") public ConcatAdapter(RecyclerView.Adapter<? extends RecyclerView.ViewHolder>... adapters) { this.adapters = (RecyclerView.Adapter<RecyclerView.ViewHolder>[]) adapters; final int size = adapters.length; adapterObservers = new RecyclerView.AdapterDataObserver[size]; for (int i = 0; i < size; ++i) { adapterObservers[i] = new InternalObserver(i); } offsets = new int[size]; } /** * @return The adapter position relative to the child adapter, if any. */ public static int getAdapterPosition(@NonNull RecyclerView.ViewHolder holder) { int position = holder.getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { RecyclerView.Adapter adapter = holder.mOwnerRecyclerView.getAdapter(); if (adapter instanceof ConcatAdapter) { final int[] offsets = ((ConcatAdapter) adapter).offsets; final int index = Arrays.binarySearch(offsets, position); if (index >= 0) { position = 0; } else { position -= offsets[~index - 1]; } } } return position; } private int getAdapterIndexForPosition(int position) { int index = Arrays.binarySearch(offsets, position); if (index < 0) { return ~index - 1; } // If the array contains multiple identical values (empty adapters), return the index of the last one do { ++index; } while ((index < offsets.length) && (offsets[index] == position)); return --index; } @Override public int getItemViewType(int position) { final int index = getAdapterIndexForPosition(position); RecyclerView.Adapter<RecyclerView.ViewHolder> adapter = adapters[index]; int viewType = adapter.getItemViewType(position - offsets[index]); if (viewTypeAdapters.get(viewType) == null) { viewTypeAdapters.put(viewType, adapter); } return viewType; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return viewTypeAdapters.get(viewType).onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { final int index = getAdapterIndexForPosition(position); adapters[index].onBindViewHolder(holder, position - offsets[index]); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) { final int index = getAdapterIndexForPosition(position); adapters[index].onBindViewHolder(holder, position - offsets[index], payloads); } @Override public int getItemCount() { if (totalItemCount == -1) { int count = 0; for (int i = 0, size = adapters.length; i < size; ++i) { offsets[i] = count; count += adapters[i].getItemCount(); } totalItemCount = count; } return totalItemCount; } @Override public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { if (!hasObservers()) { for (int i = 0, size = adapters.length; i < size; ++i) { adapters[i].registerAdapterDataObserver(adapterObservers[i]); } } super.registerAdapterDataObserver(observer); } @Override public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { super.unregisterAdapterDataObserver(observer); if (!hasObservers()) { for (int i = 0, size = adapters.length; i < size; ++i) { adapters[i].unregisterAdapterDataObserver(adapterObservers[i]); } } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { for (RecyclerView.Adapter<RecyclerView.ViewHolder> adapter : adapters) { adapter.onAttachedToRecyclerView(recyclerView); } } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { for (RecyclerView.Adapter<RecyclerView.ViewHolder> adapter : adapters) { adapter.onDetachedFromRecyclerView(recyclerView); } } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { viewTypeAdapters.get(holder.getItemViewType()).onViewAttachedToWindow(holder); } @Override public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { viewTypeAdapters.get(holder.getItemViewType()).onViewDetachedFromWindow(holder); } @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { viewTypeAdapters.get(holder.getItemViewType()).onViewRecycled(holder); } @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { return viewTypeAdapters.get(holder.getItemViewType()).onFailedToRecycleView(holder); } }