Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
A SectionedGridRecyclerViewAdapter: use this class to realize a simple sectioned grid `RecyclerView.Adapter`.

You can use this class to realize a simple sectioned grid RecyclerView.Adapter without changing your code.

Screen

The RecyclerView has to use a GridLayoutManager.

This is a porting of the class SimpleSectionedListAdapter provided by Google

If you are looking for a sectioned list RecyclerView.Adapter you can take a look here

Example:

         //Your RecyclerView
        mRecyclerView = (RecyclerView) getActivity().findViewById(R.id.list);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(),4));

        //Your RecyclerView.Adapter
        mAdapter = new SimpleAdapter(getActivity());

        //This is the code to provide a sectioned grid
        List<SectionedGridRecyclerViewAdapter.Section> sections =
                new ArrayList<SectionedGridRecyclerViewAdapter.Section>();

        //Sections
        sections.add(new SectionedGridRecyclerViewAdapter.Section(0,"Section 1"));
        sections.add(new SectionedGridRecyclerViewAdapter.Section(5,"Section 2"));
        sections.add(new SectionedGridRecyclerViewAdapter.Section(12,"Section 3"));
        sections.add(new SectionedGridRecyclerViewAdapter.Section(14,"Section 4"));
        sections.add(new SectionedGridRecyclerViewAdapter.Section(20,"Section 5"));

        //Add your adapter to the sectionAdapter
        SectionedGridRecyclerViewAdapter.Section[] dummy = new SectionedGridRecyclerViewAdapter.Section[sections.size()];
        SectionedGridRecyclerViewAdapter mSectionedAdapter = new
                SectionedGridRecyclerViewAdapter(getActivity(),R.layout.section,R.id.section_text,mRecyclerView,mAdapter);
        mSectionedAdapter.setSections(sections.toArray(dummy));

        //Apply this adapter to the RecyclerView
        mRecyclerView.setAdapter(mSectionedAdapter);

You can customize the section layout, changing the layout section.xml and changing the code in the SectionedGridRecyclerViewAdapter.SectionViewHolder class and SectionedGridRecyclerViewAdapter#onBindViewHolder method.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="130dp"
android:gravity="center"
android:layout_margin="4dp"
android:textColor="#999999"
android:textStyle="bold"
android:textSize="22sp"
android:background="@drawable/item_background"/>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:singleLine="true"
android:textAllCaps="true"
android:background="#90CAF9"
android:textSize="16sp"
android:id="@+id/section_text"
android:textStyle="bold" />
/**
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class SectionedGridRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context mContext;
private static final int SECTION_TYPE = 0;
private boolean mValid = true;
private int mSectionResourceId;
private int mTextResourceId;
private LayoutInflater mLayoutInflater;
private RecyclerView.Adapter mBaseAdapter;
private SparseArray<Section> mSections = new SparseArray<Section>();
private RecyclerView mRecyclerView;
public SectionedGridRecyclerViewAdapter(Context context, int sectionResourceId, int textResourceId,RecyclerView recyclerView,
RecyclerView.Adapter baseAdapter) {
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSectionResourceId = sectionResourceId;
mTextResourceId = textResourceId;
mBaseAdapter = baseAdapter;
mContext = context;
mRecyclerView = recyclerView;
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mValid = mBaseAdapter.getItemCount()>0;
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeRemoved(positionStart, itemCount);
}
});
final GridLayoutManager layoutManager = (GridLayoutManager)(mRecyclerView.getLayoutManager());
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (isSectionHeaderPosition(position))? layoutManager.getSpanCount() : 1 ;
}
});
}
public static class SectionViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public SectionViewHolder(View view,int mTextResourceid) {
super(view);
title = (TextView) view.findViewById(mTextResourceid);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
if (typeView == SECTION_TYPE) {
final View view = LayoutInflater.from(mContext).inflate(mSectionResourceId, parent, false);
return new SectionViewHolder(view,mTextResourceId);
}else{
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) {
if (isSectionHeaderPosition(position)) {
((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title);
}else{
mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position));
}
}
@Override
public int getItemViewType(int position) {
return isSectionHeaderPosition(position)
? SECTION_TYPE
: mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ;
}
public static class Section {
int firstPosition;
int sectionedPosition;
CharSequence title;
public Section(int firstPosition, CharSequence title) {
this.firstPosition = firstPosition;
this.title = title;
}
public CharSequence getTitle() {
return title;
}
}
public void setSections(Section[] sections) {
mSections.clear();
Arrays.sort(sections, new Comparator<Section>() {
@Override
public int compare(Section o, Section o1) {
return (o.firstPosition == o1.firstPosition)
? 0
: ((o.firstPosition < o1.firstPosition) ? -1 : 1);
}
});
int offset = 0; // offset positions for the headers we're adding
for (Section section : sections) {
section.sectionedPosition = section.firstPosition + offset;
mSections.append(section.sectionedPosition, section);
++offset;
}
notifyDataSetChanged();
}
public int positionToSectionedPosition(int position) {
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).firstPosition > position) {
break;
}
++offset;
}
return position + offset;
}
public int sectionedPositionToPosition(int sectionedPosition) {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION;
}
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
break;
}
--offset;
}
return sectionedPosition + offset;
}
public boolean isSectionHeaderPosition(int position) {
return mSections.get(position) != null;
}
@Override
public long getItemId(int position) {
return isSectionHeaderPosition(position)
? Integer.MAX_VALUE - mSections.indexOfKey(position)
: mBaseAdapter.getItemId(sectionedPositionToPosition(position));
}
@Override
public int getItemCount() {
return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0);
}
}
/**
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> {
private static final int COUNT = 100;
private final Context mContext;
private final List<Integer> mItems;
private int mCurrentItemId = 0;
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public final TextView title;
public SimpleViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.title);
}
}
public SimpleAdapter(Context context) {
mContext = context;
mItems = new ArrayList<Integer>(COUNT);
for (int i = 0; i < COUNT; i++) {
addItem(i);
}
}
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(SimpleViewHolder holder, final int position) {
holder.title.setText(mItems.get(position).toString());
}
public void addItem(int position) {
final int id = mCurrentItemId++;
mItems.add(position, id);
notifyItemInserted(position);
}
public void removeItem(int position) {
mItems.remove(position);
notifyItemRemoved(position);
}
@Override
public int getItemCount() {
return mItems.size();
}
}

How can I increase the width of each grid item or make each row to hold only three items uniformly didtributed?

Very easy, comfortable. Thanks. :) How I can update the content dynamically? I try to add a new group with header in position 0, but not possible.

ptskyin commented Oct 20, 2015

Great thanks! Saved my life!

I got my answer here..great thanks 👍

Juxtlie commented Nov 10, 2015

Great thanks!

How can I retrieve the position of the row in my original adapter? When I use getPosition(), it gives me the position, including the section, so my rows pull values from the wrong place :(

Just in case anyone else is wondering, I figured out to use sectionedPositionToPosition(int position) :)

How we can add click event to items? I have done something, but can not get clickable items.

mposada commented Jan 25, 2016

How to add Click to items of the list but not to the section item

For the latest comments up here, I want to notify the comment I posted here: https://gist.github.com/gabrielemariotti/4c189fb1124df4556058#gistcomment-1706849

How do I adapt to the head in an image is also displayed?

Thanks for the gist, Gabriele, pretty useful! 👍

Same comment than @lawrence651, The width of each item doesn't seem to match parent, even if I put with="match_parent" in each row layout!

How can we perform search in this adapter ?

When i replace TextView with ImageView in item.xml. It automatically adds very large margin at the top and bottom of each element that I can't change. Any clues

Found the solution. Needed to set ImageView's android:adjustViewBounds="true"

how to scrollToPosition(position) with RecyclerView thanks

I think there's something wrong with item decoration

Hi, thanks for helping us ....... !!
With this same code, how to implement using CursorAdapter. Please need help.

How to add a section later on and update the view, like using the notifyDataSetChanged() way

kikermo commented May 1, 2017

Nice, thanks a lot.

milhauscz commented May 19, 2017

I wonder how this can work. In SectionedGridRecyclerViewAdapter's onBindViewHolder (line 87) the ViewHolder is explicitly cast to SectionViewHolder, but how can you be sure it is a SectionViewHolder? Since onCreateViewHolder creates instances of two different classes, onBindViewHolder is also going to get instances of two different classes as a parameter without regard to the position, right?
EDIT: Got it, it is ensured by the getItemViewType() method. Elegant!

abalta commented Jun 2, 2017

When I tried to remove item from SimpleAdapter. I have an java.lang.IndexOutOfBoundsException. How can I remove an item and notify dataset successfully?

How to add more items with new headers ? I did several new sections.add(new SectionedGridRecyclerViewAdapter.Section(0,"Section x")) and also mSectionsAdapter.notifyDataSetChanged() , it didn't work. :(

how to change the section size at run time

Thanks...

my recyclerview adds blank space between items on scrolling, what might be the reason?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment