package com.anthony.rvhelper.section; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.anthony.rvhelper.base.ViewHolder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; /** * Created by Anthony on 2016/8/17. * Class Note: * A custom RecyclerView with Sections with custom Titles. * Sections are displayed in the same order they were added. * * item view can only be one type!!!! */ public class SectionRVAdapter extends RecyclerView.Adapter<ViewHolder> { public final static int VIEW_TYPE_HEADER = 0; public final static int VIEW_TYPE_FOOTER = 1; public final static int VIEW_TYPE_ITEM_LOADED = 2; public final static int VIEW_TYPE_LOADING = 3; public final static int VIEW_TYPE_FAILED = 4; private LinkedHashMap<String, Section> sections; private HashMap<String, Integer> sectionViewTypeNumbers; private int viewTypeCount = 0; private final static int VIEW_TYPE_QTY = 5; private Context mContext; public SectionRVAdapter(Context context) { this.mContext = context; sections = new LinkedHashMap<>(); sectionViewTypeNumbers = new HashMap<>(); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ViewHolder viewHolder = null; View view = null; for (Map.Entry<String, Integer> entry : sectionViewTypeNumbers.entrySet()) { if (viewType >= entry.getValue() && viewType < entry.getValue() + VIEW_TYPE_QTY) { Section section = sections.get(entry.getKey()); int sectionViewType = viewType - entry.getValue(); switch (sectionViewType) { case VIEW_TYPE_HEADER: { Integer resId = section.getHeaderResourceId(); if (resId == null) throw new NullPointerException("Missing 'header' resource id"); view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false); // get the header viewholder from the section viewHolder = section.getHeaderViewHolder(mContext,view); break; } case VIEW_TYPE_FOOTER: { Integer resId = section.getFooterResourceId(); if (resId == null) throw new NullPointerException("Missing 'footer' resource id"); view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false); // get the footer viewholder from the section viewHolder = section.getFooterViewHolder(mContext,view); break; } case VIEW_TYPE_ITEM_LOADED: { view = LayoutInflater.from(parent.getContext()).inflate(section.getItemResourceId(), parent, false); // get the item viewholder from the section viewHolder = section.getItemViewHolder(view,viewType); break; } case VIEW_TYPE_LOADING: { Integer resId = section.getLoadingResourceId(); if (resId == null) throw new NullPointerException("Missing 'loading state' resource id"); view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false); // get the loading viewholder from the section viewHolder = section.getLoadingViewHolder(mContext,view); break; } case VIEW_TYPE_FAILED: { Integer resId = section.getFailedResourceId(); if (resId == null) throw new NullPointerException("Missing 'failed state' resource id"); view = LayoutInflater.from(parent.getContext()).inflate(resId, parent, false); // get the failed load viewholder from the section viewHolder = section.getFailedViewHolder(mContext,view); break; } default: throw new IllegalArgumentException("Invalid viewType"); } } } return viewHolder; } /** * Add a section to this recyclerview. * * @param tag unique identifier of the section * @param section section to be added */ public void addSection(String tag, Section section) { this.sections.put(tag, section); this.sectionViewTypeNumbers.put(tag, viewTypeCount); viewTypeCount += VIEW_TYPE_QTY; } /** * Add a section to this recyclerview with a random tag; * * @param section section to be added * @return generated tag */ public String addSection(Section section) { String tag = UUID.randomUUID().toString(); addSection(tag, section); return tag; } /** * Return the section with the tag provided * * @param tag unique identifier of the section * @return section */ public Section getSection(String tag) { return this.sections.get(tag); } /** * Remove section from this recyclerview. * * @param tag unique identifier of the section */ public void removeSection(String tag) { this.sections.remove(tag); } /** * Remove all sections from this recyclerview. */ public void removeAllSections() { this.sections.clear(); } @Override public void onBindViewHolder(ViewHolder holder, int position) { int currentPos = 0; for (Map.Entry<String, Section> entry : sections.entrySet()) { Section section = entry.getValue(); // ignore invisible sections if (!section.isVisible()) continue; int sectionTotal = section.getSectionItemsTotal(); // check if position is in this section if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) { if (section.hasHeader()) { if (position == currentPos) { // delegate the binding to the section header getSectionForPosition(position).onBindHeaderViewHolder(holder); return; } } if (section.hasFooter()) { if (position == (currentPos + sectionTotal - 1)) { // delegate the binding to the section header getSectionForPosition(position).onBindFooterViewHolder(holder); return; } } // delegate the binding to the section content getSectionForPosition(position).onBindContentViewHolder(holder, getSectionPosition(position)); return; } currentPos += sectionTotal; } throw new IndexOutOfBoundsException("Invalid position"); } @Override public int getItemCount() { int count = 0; for (Map.Entry<String, Section> entry : sections.entrySet()) { Section section = entry.getValue(); // ignore invisible sections if (!section.isVisible()) continue; count += section.getSectionItemsTotal(); } return count; } @Override public int getItemViewType(int position) { /* Each Section has 5 "viewtypes": 1) header 2) footer 3) items 4) loading 5) load failed */ int currentPos = 0; for (Map.Entry<String, Section> entry : sections.entrySet()) { Section section = entry.getValue(); // ignore invisible sections if (!section.isVisible()) continue; int sectionTotal = section.getSectionItemsTotal(); // check if position is in this section if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) { int viewType = sectionViewTypeNumbers.get(entry.getKey()); if (section.hasHeader()) { if (position == currentPos) { return viewType; } } if (section.hasFooter()) { if (position == (currentPos + sectionTotal - 1)) { return viewType + 1; } } switch (section.getState()) { case LOADED: return viewType + 2; case LOADING: return viewType + 3; case FAILED: return viewType + 4; default: throw new IllegalStateException("Invalid state"); } } currentPos += sectionTotal; } throw new IndexOutOfBoundsException("Invalid position"); } /** * Returns the Section ViewType of an item based on the position in the adapter: * - SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER * - SectionedRecyclerViewAdapter.VIEW_TYPE_FOOTER * - SectionedRecyclerViewAdapter.VIEW_TYPE_ITEM_LOADED * - SectionedRecyclerViewAdapter.VIEW_TYPE_LOADING * - SectionedRecyclerViewAdapter.VIEW_TYPE_FAILED * * @param position position in the adapter * @return SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER, VIEW_TYPE_FOOTER, * VIEW_TYPE_ITEM_LOADED, VIEW_TYPE_LOADING or VIEW_TYPE_FAILED */ public int getSectionItemViewType(int position) { int viewType = getItemViewType(position); return viewType % VIEW_TYPE_QTY; } /** * Returns the Section object for a position in the adapter * * @param position position in the adapter * @return Section object for that position */ public Section getSectionForPosition(int position) { int currentPos = 0; for (Map.Entry<String, Section> entry : sections.entrySet()) { Section section = entry.getValue(); // ignore invisible sections if (!section.isVisible()) continue; int sectionTotal = section.getSectionItemsTotal(); // check if position is in this section if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) { return section; } currentPos += sectionTotal; } throw new IndexOutOfBoundsException("Invalid position"); } /** * Return the item position relative to the section. * * @param position position of the item in the adapter * @return position of the item in the section */ public int getSectionPosition(int position) { int currentPos = 0; for (Map.Entry<String, Section> entry : sections.entrySet()) { Section section = entry.getValue(); // ignore invisible sections if (!section.isVisible()) continue; int sectionTotal = section.getSectionItemsTotal(); // check if position is in this section if (position >= currentPos && position <= (currentPos + sectionTotal - 1)) { return position - currentPos - (section.hasHeader() ? 1 : 0); } currentPos += sectionTotal; } throw new IndexOutOfBoundsException("Invalid position"); } /** * Return a map with all sections of this adapter * * @return a map with all sections */ public LinkedHashMap<String, Section> getSectionsMap() { return sections; } /** * A concrete class of an empty ViewHolder. * Should be used to avoid the boilerplate of creating a ViewHolder class for simple case * scenarios. */ public static class EmptyViewHolder extends ViewHolder { public EmptyViewHolder(Context context, View itemView) { super(context, itemView); } } }