package com.koushikdutta.boilerplate.recyclerview;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.koushikdutta.boilerplate.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
/**
* Created by koush on 4/19/15.
*/
public class AdapterWrapper extends RecyclerView.Adapter {
// start at 1, 0 is header.
int viewTypeMapCount = 1;
Hashtable<Integer, WrappedAdapter> viewTypes = new Hashtable<Integer, WrappedAdapter>();
public Collection<WrappedAdapter> getAdapters() {
return viewTypes.values();
}
private int nextEmptyViewType() {
// empty views are always even view types
if (viewTypeMapCount % 2 == 1)
viewTypeMapCount++;
return viewTypeMapCount++;
}
private int nextViewType() {
// views are always odd view types
if (viewTypeMapCount % 2 == 0)
viewTypeMapCount++;
return viewTypeMapCount++;
}
private int getAdapterStartPosition(RecyclerView.Adapter adapter) {
int count = 0;
for (WrappedAdapter info: adapters) {
if (info.adapter == adapter)
return count;
int adapterCount = info.adapter.getItemCount();
count += adapterCount;
// header check
if (info.isShowingHeader())
count++;
if (info.isShowingEmptyView())
count++;
}
throw new RuntimeException("invalid adapter");
}
public class WrappedAdapter extends RecyclerView.AdapterDataObserver {
String sectionHeader;
View emptyView;
int emptyViewType = -1;
boolean showHeader = true;
private boolean isShowingHeader() {
return showHeader && sectionHeader != null && adapter.getItemCount() > 0;
}
public void showHeader(boolean showHeader) {
this.showHeader = showHeader;
notifyDataSetChanged();
}
private boolean isShowingEmptyView() {
return emptyView != null && adapter.getItemCount() == 0;
}
public WrappedAdapter sectionHeader(String sectionHeader) {
this.sectionHeader = sectionHeader;
notifyDataSetChanged();
return this;
}
public WrappedAdapter sectionHeader(Context context, int string) {
return sectionHeader(context.getString(string));
}
public WrappedAdapter emptyView(View emptyView) {
this.emptyView = emptyView;
emptyViewType = nextEmptyViewType();
notifyDataSetChanged();
return this;
}
public WrappedAdapter emptyView(Context context, int emptyView) {
return emptyView(LayoutInflater.from(context).inflate(emptyView, null));
}
Hashtable<Integer, Integer> viewTypes = new Hashtable<Integer, Integer>();
Hashtable<Integer, Integer> unmappedViewTypes = new Hashtable<Integer, Integer>();
RecyclerView.Adapter adapter;
public RecyclerView.Adapter getAdapter() {
return adapter;
}
@Override
public void onChanged() {
super.onChanged();
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
int startPosition = getAdapterStartPosition(adapter);
notifyItemRangeChanged(startPosition + positionStart, itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
int startPosition = getAdapterStartPosition(adapter);
notifyItemRangeInserted(startPosition + positionStart, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
int startPosition = getAdapterStartPosition(adapter);
notifyItemRangeRemoved(startPosition + positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
int startPosition = getAdapterStartPosition(adapter);
for (int i = 0; i < itemCount; i++) {
notifyItemMoved(fromPosition + startPosition + i, toPosition + startPosition + i);
}
}
}
ArrayList<WrappedAdapter> adapters = new ArrayList<WrappedAdapter>();
public WrappedAdapter wrapAdapter(RecyclerView.Adapter adapter) {
return wrapAdapter(getAdapterCount(), adapter);
}
public WrappedAdapter wrapAdapter(int index, RecyclerView.Adapter adapter) {
if (adapter == null)
throw new AssertionError("adapter must not be null");
WrappedAdapter info = new WrappedAdapter();
info.adapter = adapter;
adapters.add(index, info);
adapter.registerAdapterDataObserver(info);
notifyDataSetChanged();
return info;
}
public void remove(RecyclerView.Adapter adapter) {
for(Iterator<Map.Entry<Integer, WrappedAdapter>> it = viewTypes.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Integer, WrappedAdapter> entry = it.next();
if(entry.getValue().adapter == adapter)
it.remove();
}
for (int i = 0; i < adapters.size(); i++) {
WrappedAdapter wa = adapters.get(i);
if (wa.adapter == adapter) {
adapters.remove(i);
wa.adapter.unregisterAdapterDataObserver(wa);
notifyDataSetChanged();
return;
}
}
}
@Override
public int getItemViewType(int position) {
for (WrappedAdapter info: adapters) {
// header check
if (info.isShowingHeader()) {
if (position == 0)
return 0;
position--;
}
if (position < info.adapter.getItemCount() || (info.isShowingEmptyView() && position == 0)) {
final int viewType;
boolean isEmpty = info.isShowingEmptyView();
if (isEmpty)
viewType = info.emptyViewType;
else
viewType = info.adapter.getItemViewType(position);
if (!info.viewTypes.containsKey(viewType)) {
int mappedViewType = isEmpty ? nextEmptyViewType() : nextViewType();
viewTypes.put(mappedViewType, info);
info.viewTypes.put(viewType, mappedViewType);
info.unmappedViewTypes.put(mappedViewType, viewType);
return mappedViewType;
}
else {
return info.viewTypes.get(viewType);
}
}
position -= info.adapter.getItemCount();
}
throw new RuntimeException("invalid position");
}
private static class HeaderViewHolder extends RecyclerView.ViewHolder implements GridRecyclerView.SpanningViewHolder {
TextView textView;
public HeaderViewHolder(View itemView) {
super(itemView);
textView = (TextView)itemView.findViewById(android.R.id.text1);
}
public void bind(WrappedAdapter info) {
textView.setText(info.sectionHeader);
}
@Override
public int getSpanSize(int spanCount) {
return spanCount;
}
}
private static class EmptyViewHolder extends RecyclerView.ViewHolder implements GridRecyclerView.SpanningViewHolder {
public EmptyViewHolder(View itemView) {
super(itemView);
}
@Override
public int getSpanSize(int spanCount) {
return spanCount;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_wrapper_list_header, parent, false);
return new HeaderViewHolder(header);
}
WrappedAdapter info = viewTypes.get(viewType);
int unmappedViewType = info.unmappedViewTypes.get(viewType);
if (info.emptyViewType == unmappedViewType)
return new EmptyViewHolder(info.emptyView);
return info.adapter.onCreateViewHolder(parent, unmappedViewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof EmptyViewHolder) {
// no op, nothing to bind.
return;
}
for (WrappedAdapter info: adapters) {
// header check
if (info.isShowingHeader()) {
if (position == 0) {
((HeaderViewHolder)holder).bind(info);
return;
}
position--;
}
if (position < info.adapter.getItemCount()) {
info.adapter.onBindViewHolder(holder, position);
return;
}
position -= info.adapter.getItemCount();
}
throw new RuntimeException("invalid position");
}
public boolean isEmptyView(int position) {
return getItemViewType(position) % 2 == 0;
}
public boolean isHeaderView(int position) {
return getItemViewType(position) == 0;
}
public int getAdapterCount() {
return adapters.size();
}
@Override
public int getItemCount() {
int count = 0;
for (WrappedAdapter info: adapters) {
int adapterCount = info.adapter.getItemCount();
count += adapterCount;
// header check
if (info.isShowingHeader())
count++;
if (info.isShowingEmptyView())
count++;
}
return count;
}
}