23

i want to group my android RecyclerView items with a header made based on date like this:

    1 week ago
    - item
    - item
    - item
    - item
    2 weeks ago
    - item
    - item
    - item

that sort of thing or grouping based on some element.

ρяσѕρєя K
  • 132,198
  • 53
  • 198
  • 213
kinsley kajiva
  • 1,840
  • 1
  • 21
  • 26

5 Answers5

42

Here is a solution i came by with the aid of alot research over the net and this blog link as well Kartikey Kuswhaha so its not all my credit but i just want to give more clarity to it. below is the code: create the following files:PojoOfJsonArray,MainActivity, ListItem ,GeneralItem ,DateItem , Adapter

PojoOfJsonArray.java -this file wil represent your POJO class or whatever pojo you got going on in your app so:

 public class PojoOfJsonArray  {

    public PojoOfJsonArray(String name, String date) {
        this.name = name;
        this.date = date;
    }

    private String name,date;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}

MainActivity.java this is the activity that you will use to implement you recyclerview :

public class MainActivity extends AppCompatActivity {
    private List<PojoOfJsonArray> myOptions = new ArrayList<>();
    List<ListItem> consolidatedList = new ArrayList<>();

    private RecyclerView mRecyclerView;
    private Adapter adapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        mRecyclerView.setHasFixedSize(true);

        myOptions.add(new PojoOfJsonArray("name 1", "2016-06-21"));
        myOptions.add(new PojoOfJsonArray("name 2", "2016-06-05"));
        myOptions.add(new PojoOfJsonArray("name 2", "2016-06-05"));
        myOptions.add(new PojoOfJsonArray("name 3", "2016-05-17"));
        myOptions.add(new PojoOfJsonArray("name 3", "2016-05-17"));
        myOptions.add(new PojoOfJsonArray("name 3", "2016-05-17"));
        myOptions.add(new PojoOfJsonArray("name 3", "2016-05-17"));
        myOptions.add(new PojoOfJsonArray("name 2", "2016-06-05"));
        myOptions.add(new PojoOfJsonArray("name 3", "2016-05-17"));

        HashMap<String, List<PojoOfJsonArray>> groupedHashMap = groupDataIntoHashMap(myOptions);


        for (String date : groupedHashMap.keySet()) {
            DateItem dateItem = new DateItem();
            dateItem.setDate(date);
            consolidatedList.add(dateItem);


            for (PojoOfJsonArray pojoOfJsonArray : groupedHashMap.get(date)) {
                GeneralItem generalItem = new GeneralItem();
                generalItem.setPojoOfJsonArray(pojoOfJsonArray);//setBookingDataTabs(bookingDataTabs);
                consolidatedList.add(generalItem);
            }
        }


        adapter = new Adapter(this, consolidatedList);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setAdapter(adapter);


    }

    private HashMap<String, List<PojoOfJsonArray>> groupDataIntoHashMap(List<PojoOfJsonArray> listOfPojosOfJsonArray) {

        HashMap<String, List<PojoOfJsonArray>> groupedHashMap = new HashMap<>();

        for (PojoOfJsonArray pojoOfJsonArray : listOfPojosOfJsonArray) {

            String hashMapKey = pojoOfJsonArray.getDate();

            if (groupedHashMap.containsKey(hashMapKey)) {
                // The key is already in the HashMap; add the pojo object
                // against the existing key.
                groupedHashMap.get(hashMapKey).add(pojoOfJsonArray);
            } else {
                // The key is not there in the HashMap; create a new key-value pair
                List<PojoOfJsonArray> list = new ArrayList<>();
                list.add(pojoOfJsonArray);
                groupedHashMap.put(hashMapKey, list);
            }
        }


        return groupedHashMap;
    }

}

the myOptions is where one would use to feed your data into. ListItem.java

public abstract class ListItem {

    public static final int TYPE_DATE = 0;
    public static final int TYPE_GENERAL = 1;

    abstract public int getType();
}

GeneralItem.java

public class GeneralItem extends ListItem {
    private PojoOfJsonArray pojoOfJsonArray;

    public PojoOfJsonArray getPojoOfJsonArray() {
        return pojoOfJsonArray;
    }

    public void setPojoOfJsonArray(PojoOfJsonArray pojoOfJsonArray) {
        this.pojoOfJsonArray = pojoOfJsonArray;
    }

    @Override
    public int getType() {
        return TYPE_GENERAL;
    }


}

DateItem.java

public class DateItem extends ListItem {

    private String date;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    @Override
    public int getType() {
        return TYPE_DATE;
    }
}

Adapter.java this adapter is for the recyclerview if your not well informed on how to make simple sectioned recyclerview then i suggest you read on those and be good in the area because this is abit more tricky anyways:

public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {


    private Context mContext;
    List<ListItem> consolidatedList = new ArrayList<>();

    public Adapter(Context context, List<ListItem> consolidatedList) {
        this.consolidatedList = consolidatedList;
        this.mContext = context;


    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,  int viewType) {

        RecyclerView.ViewHolder viewHolder = null;
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        switch (viewType) {

            case ListItem.TYPE_GENERAL:
                View v1 = inflater.inflate(R.layout.items, parent,
                        false);
                viewHolder = new GeneralViewHolder(v1);
                break;

            case ListItem.TYPE_DATE:
                View v2 = inflater.inflate(R.layout.itemsh, parent, false);
                viewHolder = new DateViewHolder(v2);
                break;
        }

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {

        switch (viewHolder.getItemViewType()) {

            case ListItem.TYPE_GENERAL:

                GeneralItem generalItem   = (GeneralItem) consolidatedList.get(position);
                GeneralViewHolder generalViewHolder= (GeneralViewHolder) viewHolder;
                generalViewHolder.txtTitle.setText(generalItem.getPojoOfJsonArray().getName());

                break;

            case ListItem.TYPE_DATE:
                DateItem dateItem = (DateItem) consolidatedList.get(position);
                DateViewHolder dateViewHolder = (DateViewHolder) viewHolder;

                dateViewHolder.txtTitle.setText(dateItem.getDate());
                // Populate date item data here

                break;
        }
    }





    // ViewHolder for date row item
    class DateViewHolder extends RecyclerView.ViewHolder {
        protected TextView txtTitle;

        public DateViewHolder(View v) {
            super(v);
            this.txtTitle = (TextView) v.findViewById(R.id.txt);

        }
    }

    // View holder for general row item
    class GeneralViewHolder extends RecyclerView.ViewHolder {
        protected TextView txtTitle;

        public GeneralViewHolder(View v) {
            super(v);
            this.txtTitle = (TextView) v.findViewById(R.id.txt);

        }
    }

    @Override
    public int getItemViewType(int position) {
        return consolidatedList.get(position).getType();
    }

    @Override
    public int getItemCount() {
        return consolidatedList != null ? consolidatedList.size() : 0;
    }

}

and this has two layout being used . thus all

kinsley kajiva
  • 1,840
  • 1
  • 21
  • 26
6

I just report here a Kotlin version of kinsley kajiva response.

Note: This version makes use of data biding, so remember to enable it on app build.gradle:

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

PojoOfJsonArray.kt

data class PojoOfJsonArray(
    val name: String,
    val date: String
)

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.list.setHasFixedSize(true)

        val myOptions = listOf(
            PojoOfJsonArray("name 1", "2016-06-21"),
            PojoOfJsonArray("name 2", "2016-06-05"),
            PojoOfJsonArray("name 2", "2016-06-05"),
            PojoOfJsonArray("name 3", "2016-05-17"),
            PojoOfJsonArray("name 3", "2016-05-17"),
            PojoOfJsonArray("name 3", "2016-05-17"),
            PojoOfJsonArray("name 3", "2016-05-17"),
            PojoOfJsonArray("name 2", "2016-06-05"),
            PojoOfJsonArray("name 3", "2016-05-17")
        )

        val groupedMapMap: Map<String, List<PojoOfJsonArray>> = myOptions.groupBy {
            it.date
        }

        val consolidatedList = mutableListOf<ListItem>()
        for (date:String in groupedMapMap.keys){
            consolidatedList.add(DateItem(date))
            val groupItems: List<PojoOfJsonArray>? = groupedMapMap[date]
            groupItems?.forEach {
                consolidatedList.add(GeneralItem(it.name))
            }
        }

        val adapter = Adapter(consolidatedList)
        val layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        binding.list.layoutManager = layoutManager
        binding.list.adapter = adapter
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:listitem="@layout/general_item"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

ListItem.kt

open class ListItem(
    val type: Int
) {
    companion object {
        const val TYPE_DATE = 0
        const val TYPE_GENERAL = 1
    }
}

GeneralItem.kt

class GeneralItem(
    var name: String,
) : ListItem(TYPE_GENERAL)

DateItem.kt

class DateItem(
    val date: String
) : ListItem(TYPE_DATE)

Adapter.kt

class Adapter(
    private val items: List<ListItem>,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            ListItem.TYPE_DATE ->
                DateViewHolder(DateItemBinding.inflate(layoutInflater))
            else ->
                GeneralViewHolder(GeneralItemBinding.inflate(layoutInflater))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            ListItem.TYPE_DATE -> (holder as DateViewHolder).bind(
                item = items[position] as DateItem,
            )
            ListItem.TYPE_GENERAL -> (holder as GeneralViewHolder).bind(
                item = items[position] as GeneralItem
            )
        }
    }

    override fun getItemViewType(position: Int): Int {
        return items[position].type
    }

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

    inner class DateViewHolder(val binding: DateItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: DateItem) {
            binding.txtDate.text = item.date
        }
    }

    inner class GeneralViewHolder(val binding: GeneralItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: GeneralItem) {
            binding.txtTitle.text = item.name
        }
    }

}

date_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/txt_date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="01/01/2021"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

general_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/txt_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Name"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Final result

Khal91
  • 157
  • 1
  • 8
3

You can use the library SectionedRecyclerViewAdapter to easily group your data into sections and add a header to each section.

First create a Section class:

class MySection extends StatelessSection {

    String title;
    List<String> list;

    public MySection(String title, List<String> list) {
        // call constructor with layout resources for this Section header, footer and items 
        super(R.layout.section_header, R.layout.section_item);

        this.title = title;
        this.list = list;
    }

    @Override
    public int getContentItemsTotal() {
        return list.size(); // number of items of this section
    }

    @Override
    public RecyclerView.ViewHolder getItemViewHolder(View view) {
        // return a custom instance of ViewHolder for the items of this section
        return new MyItemViewHolder(view);
    }

    @Override
    public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyItemViewHolder itemHolder = (MyItemViewHolder) holder;

        // bind your view here
        itemHolder.tvItem.setText(list.get(position));
    }

    @Override
    public RecyclerView.ViewHolder getFooterViewHolder(View view) {
        return new MyFooterViewHolder(view);
    }

    @Override
    public void onBindFooterViewHolder(RecyclerView.ViewHolder holder) {
        MyFooterViewHolder footerHolder = (MyFooterViewHolder) holder;

        // bind your footer view here
        footerHolder.tvItem.setText(title);
    }
}

Then you set up the RecyclerView with your Sections:

// Create an instance of SectionedRecyclerViewAdapter 
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();

// Create your sections with the list of data for each year
MySection section1 = new MySection("1 week ago", week1data);
MySection section2 = new MySection("2 weeks ago", week2data);

// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
Gustavo Pagani
  • 6,583
  • 5
  • 40
  • 71
0

I solved this problem with the help of Section RecyclerView library and using these functions (Gist) to check if date is "Today", "Yesterday", and so on.

I'm hoping to build a end-to-end library for this. Let me know if your interested then I can build and open-source it.

Sample screenshot below:

Screenshot of UI showing date sections

Akshay Chordiya
  • 4,761
  • 3
  • 40
  • 52
0

I want to just optimize the following things in kinsley kajiva's answer:-

  1. Kartikey Kuswhaha used two main loops and some nested loops to consolidate the list while this can be done by going through only one loop and without grouping data into a HashMap.The following method uses ability of Set to store only unique objects inside it.

.

    /** this method takes a List of PojoOfJsonArray and consolidate the list in this format
 *  item's Date
item
item
item
item
item's Date
item
item
item
 * @param list PojoOfJsonArray to be consolidated
 * @return consolidate list
 */

private static List<ListItem> consolidateList(List<PojoOfJsonArray> list){
    Set<String> set = new HashSet<String>();
    List<ListItem> consolidatedList = new ArrayList<>();

    for(PojoOfJsonArray item: list) {
        
        if(set.add(item.getDate())) {
            //add DateItem to the consolidatedList only when 
            //the item's date can be inserted in the set
            //(as Set only allows unique object inside it)
            //this implies that the item's date is not previously inserted into the list
            
            DateItem dateItem = new DateItem();
            dateItem.setDate(item.getDate());
            
            
            consolidatedList.add(dateItem);
        }
        
        //add general item into the consolidated list
        GeneralItem generalItem = new GeneralItem();
        generalItem.setPojoOfJsonArray(item);
        consolidatedList.add(generalItem);
    }
    return consolidatedList;
}
  1. Moreover, he used HashMap to first group data which when taking key set returns key (In this case the dates) in random order. This can be resolved by using LinkedHashMap. However if you are using the 1st option (of this answer)
Prem Thakur
  • 72
  • 1
  • 8