Skip to content

Instantly share code, notes, and snippets.

@gabrielemariotti
Last active June 18, 2024 07:12
Show Gist options
  • Save gabrielemariotti/e81e126227f8a4bb339c to your computer and use it in GitHub Desktop.
Save gabrielemariotti/e81e126227f8a4bb339c to your computer and use it in GitHub Desktop.
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();
}
}
@walmyrcarvalho
Copy link

Thanks for the gist, Gabriele, pretty useful! 👍

@ppamorim
Copy link

@ChristopheVersieux
Copy link

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!

@harshal2711
Copy link

How can we perform search in this adapter ?

@asadsatti
Copy link

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

@asadsatti
Copy link

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

@nguyenngan
Copy link

how to scrollToPosition(position) with RecyclerView thanks

@robinvanyang
Copy link

I think there's something wrong with item decoration

@mghouse-mca
Copy link

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

@theanilpaudel
Copy link

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

@kikermo
Copy link

kikermo commented May 1, 2017

Nice, thanks a lot.

@milhauscz
Copy link

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
Copy link

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?

@DebdeepG
Copy link

DebdeepG commented Jun 6, 2017

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. :(

@peter1408
Copy link

how to change the section size at run time

@VivekRajNei
Copy link

Thanks...

@SiddarthG
Copy link

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

@khizarhayat
Copy link

Its great and simple. Thanks man!

@Mustafax06
Copy link

How do I make header sticky?

@naveen3186
Copy link

how to set onclikitem?

@sinandizdarevic
Copy link

@MathewMobi
Copy link

Thanks! saved my day. :)

@kundanSingh11
Copy link

Nice thank you

@rubens23
Copy link

still working in 2022, now I'll see if I can adapt this layout to what I want to do in my app

@MaximAfanasenko
Copy link

Great solution, works fine also with Xamarin.Android

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