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);
}
}
@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