Skip to content

Instantly share code, notes, and snippets.

@gabrielemariotti
Last active March 2, 2024 23:10
Show Gist options
  • Save gabrielemariotti/4c189fb1124df4556058 to your computer and use it in GitHub Desktop.
Save gabrielemariotti/4c189fb1124df4556058 to your computer and use it in GitHub Desktop.
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);
}
}
@davideas
Copy link

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.

@bellus93
Copy link

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

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

@ChristopheVersieux
Copy link

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

seasox commented Oct 27, 2016

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

Ankur008 commented Dec 7, 2016

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.?

Copy link

ghost commented Dec 10, 2016

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

@ahmedmoussa8
Copy link

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

@benju69
Copy link

benju69 commented Jun 14, 2017

Any idea how to notify when sections have changed?

@fredriks
Copy link

@DoruAdryan
Copy link

According to this talk: [https://www.youtube.com/watch?v=KhLVD6iiZQs&t=39m11s](Yigit Boyar: Pro RecyclerView), there is a problem with the onBindViewHolder method. But then, using getAdapterPosition() inside the ViewHolder it returns the position of the item from the SectionedAdapter, which would be wrong to use with some notify** methods for example.
How would a nice solution for this look like?
PS. I'm trying to use these adapters with checkable items.

@jackyhieu1211
Copy link

How to loadmore?

@Hocuri
Copy link

Hocuri commented Jul 5, 2018

Great idea, but maybe you could add a license so that I can be copy it without the risk of a copyright infringement?

@rrifafauzikomara
Copy link

what it's the mean that code "sCheeseStrings" ?

@agueroveraalvaro
Copy link

How could I expand the sections? There is a way : I click in the section and remove all their items and viceversa

Is there a better way? Thanks!

@SAGARSURI
Copy link

Will this work for HORIZONTAL LinearLayoutManager?

@aurangzaibumer777
Copy link

aurangzaibumer777 commented Nov 21, 2019

how can we put a search feature into this? it is going to iterate over headers too?

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