Create a gist now

Instantly share code, notes, and snippets.

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

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

The RecyclerView should use a LinearLayoutManager. You can use this code also with the TwoWayView with the ListLayoutManager (https://github.com/lucasr/twoway-view)

This is a porting of the class SimpleSectionedListAdapter provided by Google

Screen

Example:

        //Your RecyclerView
        mRecyclerView = (RecyclerView) findViewById(R.id.list);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL));
        
        //Your RecyclerView.Adapter
        mAdapter = new SimpleAdapter(this,sCheeseStrings);


        //This is the code to provide a sectioned list
        List<SimpleSectionedRecyclerViewAdapter.Section> sections =
                new ArrayList<SimpleSectionedRecyclerViewAdapter.Section>();
        
        //Sections
        sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0,"Section 1"));
        sections.add(new SimpleSectionedRecyclerViewAdapter.Section(5,"Section 2"));
        sections.add(new SimpleSectionedRecyclerViewAdapter.Section(12,"Section 3"));
        sections.add(new SimpleSectionedRecyclerViewAdapter.Section(14,"Section 4"));
        sections.add(new SimpleSectionedRecyclerViewAdapter.Section(20,"Section 5"));

        //Add your adapter to the sectionAdapter
        SimpleSectionedRecyclerViewAdapter.Section[] dummy = new SimpleSectionedRecyclerViewAdapter.Section[sections.size()];
        SimpleSectionedRecyclerViewAdapter mSectionedAdapter = new
                  SimpleSectionedRecyclerViewAdapter(this,R.layout.section,R.id.section_text,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 SimpleSectionedRecyclerViewAdapter.SectionViewHolder class and SimpleSectionedRecyclerViewAdapter#onBindViewHolder method.

<?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:textColor="@color/demo_theme_status_bar_color"
android:background="@android:color/transparent"
android:textSize="16sp"
android:id="@+id/section_text"
android:textStyle="bold" />
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> {
private final Context mContext;
private List<String> mData;
public void add(String s,int position) {
position = position == -1 ? getItemCount() : position;
mData.add(position,s);
notifyItemInserted(position);
}
public void remove(int position){
if (position < getItemCount() ) {
mData.remove(position);
notifyItemRemoved(position);
}
}
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public final TextView title;
public SimpleViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.simple_text);
}
}
public SimpleAdapter(Context context, String[] data) {
mContext = context;
if (data != null)
mData = new ArrayList<String>(Arrays.asList(data));
else mData = new ArrayList<String>();
}
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).inflate(R.layout.simple_item, parent, false);
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(SimpleViewHolder holder, final int position) {
holder.title.setText(mData.get(position));
holder.title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext,"Position ="+position,Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
}
public class SimpleSectionedRecyclerViewAdapter 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>();
public SimpleSectionedRecyclerViewAdapter(Context context, int sectionResourceId, int textResourceId,
RecyclerView.Adapter baseAdapter) {
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSectionResourceId = sectionResourceId;
mTextResourceId = textResourceId;
mBaseAdapter = baseAdapter;
mContext = context;
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);
}
});
}
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);
}
}

I'm using this to wrap my already created adapter. One issue I found is the position offset... from code in the original (inner) adapter. Any suggestion other than have the inner adapter call the outer adapter's positionToSectionedPosition?

Hi!

I'm trying to implement a simple sectioned list, but with no success.

I've copied the SimpleSectionedRecyclerViewAdapter (just as is), and now I'm trying to add the code on Readme.md file into a ListFragment class.

Within the ListFragment, I guess I have to do the following:

First, load data to be shown on onCreate method:

private List<Map<String,String>> contenido;
...onCreate...
HashMap<String, String> m1 = new HashMap<String,String>();
m1.put("ath", "Athletic");
m1.put("rss", "Real Sociedad");
m1.put("eib", "Eibar");
contenido.add(m1);

    HashMap<String, String> m2 = new HashMap<String,String>();
    m2.put("rma", "Real Madrid");
    m2.put("atl", "Atlético");
    m2.put("get", "Getafe");
    contenido.add(m2);

..onCreate..

Then, on onCreateView do the rest stuff:

..onCreateView..
View rootView;
//View rootView = inflater.inflate(R.layout.fragment_misrutas, container, false);
rootView = inflater.inflate(R.layout.fragment_misrutas_recycler, container, false);

    //Your RecyclerView
    mRecyclerView = (RecyclerView) rootView.findViewById(R.id.misrutas_recycler_view);
    mRecyclerView.setHasFixedSize(true);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
    //mRecyclerView.addItemDecoration(new DividerItemDecoration(mActivity,LinearLayoutManager.VERTICAL));

    //Your RecyclerView.Adapter
    mAdapter = new SimpleAdapter(mActivity, contenido, R.layout.item_ruta_misrutas);

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

    //Sections
    sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0,"Equipos vascos"));
    sections.add(new SimpleSectionedRecyclerViewAdapter.Section(3,"Equipos madrileños"));

    //Add your adapter to the sectionAdapter
    SimpleSectionedRecyclerViewAdapter.Section[] dummy = new SimpleSectionedRecyclerViewAdapter.Section[sections.size()];
    SimpleSectionedRecyclerViewAdapter mSectionedAdapter = new
              SimpleSectionedRecyclerViewAdapter(this,R.layout.item_ruta_misrutas, R.id.ItemRutaMisRutasTextView, mAdapter);
    mSectionedAdapter.setSections(sections.toArray(dummy));

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

..onCreateView..

But here I'm having some problems with the SimpleAdapter: the constructor needs to be SimpleAdapter(mActivity, contenido, int, String[], int[]) and on the line where "SimpleSectionedRecyclerViewAdapter mSectionedAdapter = new
SimpleSectionedRecyclerViewAdapter(this,R.layout.item_ruta_misrutas, R.id.ItemRutaMisRutasTextView, mAdapter);" the adapter parameter doesn't match the recyclerview adapter constructor.

How can I go on a solve this issue?

Thank you very much.

Owner

gabrielemariotti commented Jan 15, 2015

@russellhoff pay attention to the SimpleAdapter class.

How can I put each sections with the necessary RecyclerView elements inside a cardview.Can you please help

nitinOAB commented Apr 6, 2015

can u please from where to import DividerItemDecoration class it gives error at

listviewCategories.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL));

@nitinOAB

DividerItemDecoration seems to be an external class that you can copy. See for example https://gist.github.com/alexfu/0f464fc3742f134ccd1e

Alternatively, you can simply use this code instead:

        mRecyclerView = (RecyclerView) findViewById(R.id.list);
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager llm = new LinearLayoutManager(this);
        llm.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(llm);

@gabrielemariotti.. Thank You Very much It works perfectly

Could You please tell me. How to create separator after each section?

Hi

I have created one sample app using the above code, but I faced position issue when i click no list for getting detail, I guess section is overwritten in view position, can you please give solution for that

Hi

Could are you said me that I can set Filter for this list ? Searchview ?

sevar83 commented Sep 8, 2015

I notice something strange. Why the AdapterDataObserver does not translate positionStart and itemCount to sectioned positions when notifies the outer adapter? The base adapter positions and the sectioned positions differ, so it notifies about incorrect positions. Isn't it? Anyway, I'm going to give it a try (with a staggered grid too)! 10x

Saketme commented Sep 8, 2015

@sevar83 You're right. That definitely looks like a bug

sevar83 commented Sep 11, 2015

@Saketme The code to transform from base adapter positions to sectioned is not complicated:

int sectionedPositionStart = positionToSectionedPosition(positionStart);
int sectionedItemCount = positionToSectionedPosition(positionStart + itemCount) - sectionedPositionStart;
notifyItemRange****(sectionedPositionStart, sectionedItemCount);

But the trouble comes when you try to animate the section changes. The difficulty with sections is that they are not usual items. They are dynamic and depend on the data in base adapter. Their appearance and disappearance depends on it. After data item is inserted/removed all section positions after must be recalculated in linear manner. This makes pretty hard to maintain section animation in the general case.
The only working approach I've found is: before any changes to the base adapter, i remove all sections with notify (for animations to take place) and after the change I rebuild the sections again and add them with notify.

Anyone had some success with section animations?

mobiRic commented Sep 21, 2015

@RoundSparrow I can confirm what you see. I am wrapping a SelectableAdapter from enoent.fr.

And unfortunately the inner adapter will get its position from RecyclerView.getAdapterPosition() which has been declared final.

Without the inner adapter being coupled to the outer SimpleSectionedRecyclerViewAdapter I can see no way around this issue.

2 ways forward as I see it:

  • combine my 2 adapters into 1 SectionedSelectableAdapter
  • tightly couple the 2 adapters so the inner knows about the outer (bad code, but quicker to implement/hack)

huteri commented Oct 20, 2015

Hi, you have issue with onClickListener in base adapter, if I add the data with position 0, then the on click trigger will return position 0 for that new data but also the data with position 1. OnClickListener is giving wrong position here.

And yes, I know I need to implement onClickListener on the viewholder instead, and use getAdapterPosition() to get the position, but it can't be used with section adapter, since it will return the position including the sections position, and my arraylist does not contain the sections.

Any workaround?

Thanks, It's very useful.

Thanks a lot. But how can I dynamically add or remove item to any section in the list?

Great implementation for a sectioned adapter but I also have the issue of the getAdapterPosition() in the OnClickListener which returns the wrong position unfortunately :/

Use "sectionedPositionToPosition" to get the right index

@Override
public void onClick(View v) {
     mSectionedAdapter.sectionedPositionToPosition(getLayoutPosition());
}

@gabrielemariotti. Can you suggest what sould i do to make the header view sticky?

how to make it sticky ?

I've implemented a new way to create Sections with Sticky headers, basically you can handle them by simply assigning the IHeader object to a ISectionable object, all is coherent: add, delete and drag, automatically relink the header to the next ISectionable object.
You can check the project https://github.com/davideas/FlexibleAdapter which contains much more features than only sections.

Not using ItemDecoration, sticky headers are now clickable.

You should also read my answer at this question: http://stackoverflow.com/questions/33018788/how-can-i-set-a-listener-inside-a-recyclerview-header-decor

kz commented Mar 20, 2016

Using this now in https://github.com/kz/balances-android and works well. Thanks!

zuchetto commented Apr 3, 2016

hi guys i need help here please i tried a lot of things without succes so my probleme is :

here is my code
` protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_articles_grid);

    MenuDBHelper menuDBHelper = new MenuDBHelper(this);
    ArrayList<Card> cards = new ArrayList<>();
    mCardArrayAdapter = new CardGridArrayAdapter(this, cards);

    categoriesIds = menuDBHelper.getAllCategoriesIds();
    ArrayList <Integer> separatorPosition=new ArrayList<>();

    CardGridView gridView = (CardGridView) findViewById(R.id.articles_grid);
    for (int j = 0; j < categoriesIds.size(); j++) {
        articles = menuDBHelper.getArticlesObjByCategorie(categoriesIds.get(j).toString());

        // i need to put a subheader here with the name of the category


        for (int i = 0; i < articles.size(); i++) {
            Card card = new Card(this);
            CardHeader header = new CardHeader(this);
            Article current = articles.get(i);
            header.setTitle(current.getName());
            card.setTitle(current.getPrix());
            card.addCardHeader(header);

            CardThumbnail thumb = new CardThumbnail(this);
            thumb.setDrawableResource(R.drawable.victor);
            card.addCardThumbnail(thumb);

            card.setClickable(true);
            card.setOnClickListener(new Card.OnCardClickListener() {
                @Override
                public void onClick(Card card, View view) {

                    int position = mCardArrayAdapter.getPosition(card);
                    Article clickedArticle = articles.get(position);

                    Intent intent = new Intent(getApplicationContext(), ArticleDetailsActivity.class);
                    intent.putExtra("article", clickedArticle);
                    startActivity(intent);

                }
            });
            Log.d("card", card.getTitle());
            cards.add(card);
        }

        CardGridArrayAdapter mCardArrayAdapter = new CardGridArrayAdapter(this, cards);


        gridView.setAdapter(mCardArrayAdapter);


    }



}

}
`

I have same question as @pruthvirajha

davideas commented Jun 6, 2016

@mrThinBone, @pruthvirajha, @zuchetto, @niravkhunt1, @ranjitzade
You can actually interact with the Adapter I made, and animate every single change, also you can enable sticky headers and do lot of cool stuff.

Hi i have to use the CheckedTextView in recycler view, but if i user selects a section I have to select all items inside that section, how i can do it?

asoni960 commented Aug 9, 2016

I'm calling mcontext in onCreateViewHolder ,Simplesectionedrecyclerview from a fragment which giving an error and no getActivity() option there, can you help me with this

Hello,
I currently tried this code and have issue with the position of the clicked item.
Is there a way to access sectionedPositionToPosition from the baseAdapter?

seasox commented Oct 27, 2016 edited

@ChristopheVersieux I attached a RecyclerView.OnItemTouchListener to my recyclerView for touch detection, from my Activity class.

drawerList.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
  View child = recyclerView.findChildViewUnder(motionEvent.getX(),motionEvent.getY());
  int position = recyclerView.getChildAdapterPosition(child);

  if (position != RecyclerView.NO_POSITION 
    && !sectionedAdapter.isSectionHeaderPosition(position) 
    && gestureDetector.onTouchEvent(motionEvent)) {

    position = sectionedAdapter.sectionedPositionToPosition(position);
    /* do something with position */
    return true;
  }
  return false;
}
@Override
public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
  // stub
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
  // stub
}

Where gestureDetector is defined as:

// touch listener
final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
  @Override
  public boolean onSingleTapUp(MotionEvent evt) {
    return true;
  }
});

And sectionedAdapter is an instance of the adapter discussed here.

Ankur008 commented Dec 7, 2016 edited

I think it have anomalies with the section position. There is no symmetric with the section position.

String[] sCheeseStrings=["one","two","three","four","five"];

sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0,"Section 1"));
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(1,"Section 2"));
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(2,"Section 3"));
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(3,"Section 4"));
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(4,"Section 5"));

OUTPUT:

Section 1
one
Section 2
two
Section 3
three
Section 4
four
Section 5
five

when i have continuous section position, then how can item come in between.?

How to specify onBIndItemViewHOlder for multiple item holder and how we use section and position there?

g88k commented Mar 10, 2017

How to access Header view when we click on a child ?

benju69 commented Jun 14, 2017

Any idea how to notify when sections have changed?

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