How to implement RecyclerView with section header depending on category?

44,778

Solution 1

The most simple way to split your recycler view into sections is by using a layout with the header and the item already in place and then changing the visibility if the header is the same.

Layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tvHeader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="start"
        android:padding="16dp"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/primary"
        android:textStyle="bold"
        tools:text="A" />

    <TextView
        android:id="@+id/tvName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvHeader"
        android:background="?android:attr/selectableItemBackground"
        android:padding="16dp"
        android:textAppearance="?android:attr/textAppearanceMedium"
        tools:text="Adam" />

</RelativeLayout>

Adapter (2018 Kotlin Edition):

class ContactAdapter @Inject constructor() : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {

    var onItemClick: ((Contact) -> Unit)? = null
    var contacts = emptyList<Contact>()

    override fun getItemCount(): Int {
        return contacts.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_contact, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val name = contacts[position].name
        holder.header.text = name.substring(0, 1)
        holder.name.text = name

        // if not first item, check if item above has the same header
        if (position > 0 && contacts[position - 1].name.substring(0, 1) == name.substring(0, 1)) {
            holder.headerTextView.visibility = View.GONE
        } else {
            holder.headerTextView.visibility = View.VISIBLE
        }
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val headerTextView: TextView = itemView.tvHeader
        val nameTextView: TextView = itemView.tvName

        init {
            itemView.setOnClickListener {
                onItemClick?.invoke(contacts[adapterPosition])
            }
        }
    }
}


Might be helpful as well: RecyclerView itemClickListener in Kotlin



Old Java Adapter Version:

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.DataViewHolder> {

    private List<Contact> mData;

    @Inject
    public RecyclerAdapter() {
        mData = new ArrayList<>();
    }

    public void setData(List<Contact> data) {
        mData = data;
    }

    public Contact getItem(int position){
        return mData.get(position);
    }

    @Override
    public DataViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
        return new DataViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final DataViewHolder holder, int position) {
        Contact contact = mData.get(position);
        holder.headerTextView.setText(contact.getName().substring(0, 1));
        holder.nameTextView.setText(contact.getName());

        // if not first item check if item above has the same header
        if (position > 0 && mData.get(position - 1).getName().substring(0, 1).equals(contact.getName().substring(0, 1))) {
            holder.headerTextView.setVisibility(View.GONE);
        } else {
            holder.headerTextView.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    public class DataViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.text_header)
        TextView headerTextView;
        @BindView(R.id.text_name)
        TextView nameTextView;

        public DataViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

Solution 2

You can implement it with the library SectionedRecyclerViewAdapter as I explained in this post.

In order to implement the SwipeLayout, don't extend RecyclerSwipeAdapter, extend SectionedRecyclerViewAdapter and implement the SwipeLayout in ItemViewHolder / onBindItemViewHolder as you have done.

Solution 3

You can do it youself by hard codding.There ar smart ways to do this. follow these links. and choose one for you.

https://github.com/afollestad/sectioned-recyclerview https://github.com/truizlop/SectionedRecyclerView http://android-pratap.blogspot.in/2015/12/sectioned-recyclerview-in-android_1.html

You can search more by "sectioned recyclerViews android libraries"

Share:
44,778

Related videos on Youtube

Admin
Author by

Admin

Updated on June 25, 2020

Comments

  • Admin
    Admin about 4 years

    I want to implement sections in my list. I have a list of tasks. List has a custom adapter which extends recyclerview swipe adapter as I have implemented swipe gesture to the recyclerview.

    So now tasks list is shown together with completed and pending tasks. Each list item has a check box which shows task is completed or pending.

    If check box is checked then task is completed and vise versa. Now I want to make two sections in this with header. One For completed tasks and another for pending tasks.

    So completed tasks should be shown inside completed section and vise versa. Also if the task is unchecked i.e pending and if user checks the check box then the item should get removed from the pending section and should get added to the completed section and vise versa.

    I checked with one library for sections.

    https://github.com/afollestad/sectioned-recyclerview

    But when I tried to implement the library I got the error that adapter can not extend two classes as I have extended recyclerview swipe library before.

    Adapter :

        public class IAdapter extends RecyclerSwipeAdapter<IAdapter.ItemViewHolder> {
    
        public ArrayList<Task> items;
        Context conext;
        public int _mId;
    
        List<Task> itemsPendingRemoval = new ArrayList<>();
    
        public IAdapter(Context context, ArrayList<Task> item) {
            this.conext=context;
           this.items=item;
        }
    
    
        @Override
        public int getItemCount() {
            return items.size();
    
        }
    
        public static class ItemViewHolder extends RecyclerView.ViewHolder {
            Task task;
            CheckBox cb;
            SwipeLayout swipeLayout;
            TaskTableHelper taskTableHelper;
            ItemViewHolder(final View itemView) {
                super(itemView);
    
                taskTableHelper= new TaskTableHelper(itemView.getContext());
                swipeLayout = (SwipeLayout) itemView.findViewById(R.id.swipe);
                cb = (CheckBox) itemView.findViewById(R.id.checkbox);
    
            }
        }
    
        @Override
        public void onBindViewHolder(final ItemViewHolder itemViewHolder,final int i) {
    
            itemViewHolder.cb.setText(items.get(i).getTitle());
    
            itemViewHolder.task = items.get(i);
    
            int taskId = itemViewHolder.task.getId();
    
            itemViewHolder.task = itemViewHolder.taskTableHelper.getTask(taskId);
    
            int status = itemViewHolder.task.getStatus();
    
            if(status == 0)
            {
                itemViewHolder.cb.setTextColor(Color.WHITE);
            }
    
            else {
    
                itemViewHolder.cb.setChecked(true);
    
                itemViewHolder.cb.setTextColor(Color.parseColor("#B0BEC5"));
    
            }
    
    
           itemViewHolder.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
                    if (isChecked) {
    
                        itemViewHolder.cb.setTextColor(Color.parseColor("#B0BEC5"));
    
                        itemViewHolder.task.setStatus(1);
    
                        itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
    
                    }
                    else
    
                    {
    
                        itemViewHolder.cb.setTextColor(Color.WHITE);
    
                        itemViewHolder.task.setStatus(0);
    
                        itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
    
                    }
    
                }
    
            });
    
    
    
            final Task item = items.get(i);
            itemViewHolder.swipeLayout.addDrag(SwipeLayout.DragEdge.Right,itemViewHolder.swipeLayout.findViewById(R.id.bottom_wrapper_2));
            itemViewHolder.swipeLayout.setShowMode(SwipeLayout.ShowMode.LayDown);
    
            itemViewHolder.swipeLayout.setOnDoubleClickListener(new SwipeLayout.DoubleClickListener() {
                @Override
                public void onDoubleClick(SwipeLayout layout, boolean surface) {
                    Toast.makeText(conext, "DoubleClick", Toast.LENGTH_SHORT).show();
                }
            });
            itemViewHolder.swipeLayout.findViewById(R.id.trash2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mItemManger.removeShownLayouts(itemViewHolder.swipeLayout);
                    items.remove(i);
                    notifyItemRemoved(i);
                    notifyItemRangeChanged(i, items.size());
                    mItemManger.closeAllItems();
    
                    itemViewHolder.taskTableHelper.deleteTask(item);
    
                    _mId = item.getAlertId();
    
                    cancelNotification();
    
                    Toast.makeText(view.getContext(), "Deleted " + itemViewHolder.cb.getText().toString() + "!", Toast.LENGTH_SHORT).show();
                }
            });
    
            itemViewHolder.swipeLayout.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    itemViewHolder.task.setStatus(1);
                    itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
                    itemViewHolder.cb.setChecked(true);
                    Toast.makeText(conext, "Task Completed.", Toast.LENGTH_SHORT).show();
                }
            });
    
            itemViewHolder.swipeLayout.getSurfaceView().setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    boolean mEditMode;
    
                    int id = item.getId();
                    int priority = item.getTaskPriority();
                    String title = item.getTitle();
                    String alertDate = item.getAlertDate();
                    String alertTime = item.getAlertTime();
                    String dueDate = item.getDueDate();
                    String dueTime = item.getDueTime();
                    _mId = item.getAlertId();
    
                    int listId = item.getList();
    
                    mEditMode = true;
    
                    Intent i = new Intent(conext, AddTaskActivity.class);
    
                    i.putExtra("taskId", id);
                    i.putExtra("taskTitle", title);
                    i.putExtra("taskPriority", priority);
                    i.putExtra("taskAlertTime", alertTime);
                    i.putExtra("taskAlertDate", alertDate);
                    i.putExtra("taskDueDate", dueDate);
                    i.putExtra("taskDueTime", dueTime);
                    i.putExtra("taskListId", listId);
                    i.putExtra("EditMode", mEditMode);
                    i.putExtra("AlertId",_mId);
    
                    conext.startActivity(i);
    
                }
            });
    
    
            mItemManger.bindView(itemViewHolder.itemView, i);
    
        }
    
        @Override
        public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup,int position) {
    
                View itemView = LayoutInflater.
                        from(viewGroup.getContext()).
                        inflate(R.layout.card_layout, viewGroup, false);
                return new ItemViewHolder(itemView);
    
        }
    
    
    
            @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
        }
    
    
        public void remove(int position) {
           Task item = items.get(position);
            if (itemsPendingRemoval.contains(item)) {
                itemsPendingRemoval.remove(item);
            }
            if (items.contains(item)) {
                items.remove(position);
                notifyItemRemoved(position);
            }
        }
    
        public void cancelNotification()
        {
            AlarmManager alarmManager = (AlarmManager)conext.getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(conext, NotificationReceiver.class);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(conext,_mId, intent, 0);
            alarmManager.cancel(pendingIntent);
        }
        @Override
        public int getSwipeLayoutResourceId(int position) {
            return R.id.swipe;
        }
    }
    

    EDIT:

    onBindViewHolder method after extending sectionedRecyclerview adapter:

     @Override
    public void onBindViewHolder(ItemViewHolder itemViewHolder, int section, int i, int absolutePosition) {
    
        itemViewHolder.cb.setText(items.get(i).getTitle());
    
        itemViewHolder.task = items.get(i);
    
        int taskId = itemViewHolder.task.getId();
    
        itemViewHolder.task = itemViewHolder.taskTableHelper.getTask(taskId);
    
        int status = itemViewHolder.task.getStatus();
    
        if(status == 0)
        {
            itemViewHolder.cb.setTextColor(Color.WHITE);
        }
    
        else {
    
            itemViewHolder.cb.setChecked(true);
    
            itemViewHolder.cb.setTextColor(Color.parseColor("#B0BEC5"));
    
        }
    
    
       itemViewHolder.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
                if (isChecked) {
    
                    itemViewHolder.cb.setTextColor(Color.parseColor("#B0BEC5"));
    
                    itemViewHolder.task.setStatus(1);
    
                    itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
    
                }
                else
    
                {
    
                    itemViewHolder.cb.setTextColor(Color.WHITE);
    
                    itemViewHolder.task.setStatus(0);
    
                    itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
    
                }
    
            }
    
        });
    
    
    
        final Task item = items.get(i);
        itemViewHolder.swipeLayout.addDrag(SwipeLayout.DragEdge.Right,itemViewHolder.swipeLayout.findViewById(R.id.bottom_wrapper_2));
        itemViewHolder.swipeLayout.setShowMode(SwipeLayout.ShowMode.LayDown);
    
        itemViewHolder.swipeLayout.setOnDoubleClickListener(new SwipeLayout.DoubleClickListener() {
            @Override
            public void onDoubleClick(SwipeLayout layout, boolean surface) {
                Toast.makeText(conext, "DoubleClick", Toast.LENGTH_SHORT).show();
            }
        });
        itemViewHolder.swipeLayout.findViewById(R.id.trash2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mItemManger.removeShownLayouts(itemViewHolder.swipeLayout);
                items.remove(i);
                notifyItemRemoved(i);
                notifyItemRangeChanged(i, items.size());
                mItemManger.closeAllItems();
    
                itemViewHolder.taskTableHelper.deleteTask(item);
    
                _mId = item.getAlertId();
    
                cancelNotification();
    
                Toast.makeText(view.getContext(), "Deleted " + itemViewHolder.cb.getText().toString() + "!", Toast.LENGTH_SHORT).show();
            }
        });
    
        itemViewHolder.swipeLayout.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
    
                itemViewHolder.task.setStatus(1);
                itemViewHolder.taskTableHelper.updateStatus(itemViewHolder.task);
                itemViewHolder.cb.setChecked(true);
                Toast.makeText(conext, "Task Completed.", Toast.LENGTH_SHORT).show();
            }
        });
    
        itemViewHolder.swipeLayout.getSurfaceView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
    
                boolean mEditMode;
    
                int id = item.getId();
                int priority = item.getTaskPriority();
                String title = item.getTitle();
                String alertDate = item.getAlertDate();
                String alertTime = item.getAlertTime();
                String dueDate = item.getDueDate();
                String dueTime = item.getDueTime();
                _mId = item.getAlertId();
    
                int listId = item.getList();
    
                mEditMode = true;
    
                Intent i = new Intent(conext, AddTaskActivity.class);
    
                i.putExtra("taskId", id);
                i.putExtra("taskTitle", title);
                i.putExtra("taskPriority", priority);
                i.putExtra("taskAlertTime", alertTime);
                i.putExtra("taskAlertDate", alertDate);
                i.putExtra("taskDueDate", dueDate);
                i.putExtra("taskDueTime", dueTime);
                i.putExtra("taskListId", listId);
                i.putExtra("EditMode", mEditMode);
                i.putExtra("AlertId",_mId);
    
                conext.startActivity(i);
    
            }
        });
    
    
        mItemManger.bindView(itemViewHolder.itemView, i);
    
    }
    

    How can I do this? Can anyone help with this please?

    Thank you..

  • Admin
    Admin about 8 years
    Yes I have gone through these links. But Can I extend this library with recyclerview swipe library? When I tried to implement one of these I got an error that class can not extend multiple classes. @vabhi vab
  • Admin
    Admin about 8 years
    I am getting an error as adapter must implement method onBindViewHolder(VH,int) when i tried to extend the sectionedRecyclerview adapter. I am updating onBindViewHolder method please check. @vabhi vab
  • ramya
    ramya about 7 years
    how to use this line" mItemManger.bindView(itemViewHolder.itemView, i)" in the above method as it is sectionRecyclerview and cannot have unique position for each item.
  • Gustavo
    Gustavo about 7 years
    which object is mItemManger?
  • ramya
    ramya about 7 years
    sorry its an object of RecyclerSwipeAdapter class of this library " github.com/daimajia/AndroidSwipeLayout"
  • Debjit
    Debjit over 6 years
    What about if the items are in grid ?
  • Mehmet Katircioglu
    Mehmet Katircioglu over 5 years
    @denwehrie Good idea, but im planning to implement it for my chat app, I'm going to show headers for each day, there are lots of messages so would it be an overkill to have an extra view each time, even if they are not visible?
  • Rahul Ahuja
    Rahul Ahuja over 5 years
    @denwehrle what is the use of 'onItemClick?.invoke(contacts[adapterPosition])' in the above Kotlin snippet? Also, could you please explain 'var onItemClick: ((Contact) -> Unit)? = null' a link for reference would be great!
  • denwehrle
    denwehrle over 5 years
    Hi @RahulAhuja, these two lines of code are part of the onClickListener in explain here: stackoverflow.com/questions/29424944/… They are not needed for for the section headers but were part of my own code, sorry for the confusion! Reference: kotlinlang.org/docs/reference/…
  • sejn
    sejn about 3 years
    @MehmetKatircioglu I need to show a chat screen with grouping of dates with the messages like whatsapp. Does it help? Can you give any solution. I am using java