Ability to to sort tabular data by columns is a handy option for the users to have. In this tutorial, we are going to implement a recyclerview and add capability to sort its items by columns.
First step is to add recyclerview dependency library in the app modules’ build.gradle file.
dependencies { ... implementation "androidx.recyclerview:recyclerview:1.1.0" ... }
In this example we are going to deal with data of some persons containing their name and age. Our model looks like the following one.
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Lets create the layout of the recyclerview row.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="4"> <TextView android:id="@+id/tvName" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textAppearance="?android:attr/textAppearanceMedium" android:paddingLeft="8dp" tools:text="Alexander Sherikov" /> <TextView android:id="@+id/tvAge" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textAppearance="?android:attr/textAppearanceMedium" android:textAlignment="center" tools:text="30" /> </LinearLayout>
Now we need to create the adapter for the recyclerview.
At first step we have to use the layout view that will show person’s name and age.
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_item, parent, false); return new PersonViewHolder(view); }
Next we will have to create a view holder class
class PersonViewHolder extends RecyclerView.ViewHolder { TextView tvName, tvAge; public PersonViewHolder(@NonNull View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tvName); tvAge = itemView.findViewById(R.id.tvAge); } }
We are going to use the view holder class inside onBindViewHolder method
@Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { Person item = persons.get(position); PersonViewHolder personViewHolder = (PersonViewHolder) holder; personViewHolder.tvName.setText(item.getName()); personViewHolder.tvAge.setText(String.valueOf(item.getAge())); }
Our data class has two data types one is string (name) and other one is integer (age). We are going to write two methods one will sort the data by name and the other one will sort the data by age.
Let’s look at the method that sorts the data by name
/** * Sorts the data by name * @param sortType integer that represents ascending or descending */ public void sortByName(final int sortType) { Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if (sortType == DESCENDING) { return o2.getName().compareTo(o1.getName()); } return o1.getName().compareTo(o2.getName()); } }); }
The above method sorts the persons ArrayList by descending and ascending order, we obtained this by overriding the comparator of Collections.sort method. The compareTo method returns negative value if the first element is smaller than the second element and positive value if the second element is greather than the first one. If the two elements are equal this method returns 0. So, we need two put the second element in first place while comparing when we need to sort thevalues in descending order, for sorting the value ascending order we have to do the opposite.
The method that sorts the data by age is similar but here we have to deal with integer data.
/** * Sorts the data by age * @param sortType integer that represendts ascending or descending */ public void sortByAge(final int sortType) { Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if(o1.getAge() < o2.getAge()) { if(sortType == DESCENDING) { return 1; } return -1; }else if(o1.getAge() > o2.getAge()) { if(sortType == DESCENDING) { return -1; } return 1; } return 0; } }); }
We need to show the original unsorted data whenever the user wants to view it. The following method saves the original data.
/** * Saves the original unsorted data */ public void saveOriginalData() { personsOriginal = new ArrayList<>(); personsOriginal.addAll(persons); }
To show the original data we are going to use the following method
/** * Shows the original unsorted data */ public void showOriginalData() { persons.clear(); persons.addAll(personsOriginal); }
The complete adapter class looks like the following one
import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class PersonAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public static final int DESCENDING = 1, ASCENDING = 2; private ArrayList<Person> persons, personsOriginal; public PersonAdapter(ArrayList<Person> persons) { this.persons = persons; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_item, parent, false); return new PersonViewHolder(view); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { Person item = persons.get(position); PersonViewHolder personViewHolder = (PersonViewHolder) holder; personViewHolder.tvName.setText(item.getName()); personViewHolder.tvAge.setText(String.valueOf(item.getAge())); } @Override public int getItemCount() { return persons.size(); } class PersonViewHolder extends RecyclerView.ViewHolder { TextView tvName, tvAge; public PersonViewHolder(@NonNull View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tvName); tvAge = itemView.findViewById(R.id.tvAge); } } /** * Sorts the data by name * @param sortType integer that represents ascending or descending */ public void sortByName(final int sortType) { Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if (sortType == DESCENDING) { return o2.getName().compareTo(o1.getName()); } return o1.getName().compareTo(o2.getName()); } }); } /** * Sorts the data by age * @param sortType integer that represendts ascending or descending */ public void sortByAge(final int sortType) { Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if(o1.getAge() < o2.getAge()) { if(sortType == DESCENDING) { return 1; } return -1; }else if(o1.getAge() > o2.getAge()) { if(sortType == DESCENDING) { return -1; } return 1; } return 0; } }); } /** * Saves the original unsorted data */ public void saveOriginalData() { personsOriginal = new ArrayList<>(); personsOriginal.addAll(persons); } /** * Shows the original unsorted data */ public void showOriginalData() { persons.clear(); persons.addAll(personsOriginal); } }
Its now time to create our Activity class. The activity class will contain header for data and the recycleview that contains the data.
We need to create the layout of the header that will contain text and image indicator showing the sorting status like the following one
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="4"> <Button android:id="@+id/btnName" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textAppearance="?android:attr/textAppearanceMedium" android:textAlignment="center" android:textColor="@android:color/white" android:background="@color/colorPrimaryDark" android:drawableRight="@drawable/ic_unsorted" android:drawablePadding="-4dp" android:textAllCaps="false" android:onClick="onNameClicked" android:text="Name" /> <Button android:id="@+id/btnAge" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textAppearance="?android:attr/textAppearanceMedium" android:textAlignment="center" android:textColor="@android:color/white" android:background="@color/colorPrimaryDark" android:drawableRight="@drawable/ic_unsorted" android:drawablePadding="-4dp" android:textAllCaps="false" android:onClick="onAgeClicked" android:text="Age" /> </LinearLayout>
The header item contains two buttons with images which will be used to sort the items and the images will be changed accordingly to show the sorting status of the data.
Now we need to include the header layout and the recyclerview in the activity layout.
The complete layout is given below.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <include android:id="@+id/tableHeader" layout="@layout/header_item" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rvPerson" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tableHeader" /> </androidx.constraintlayout.widget.ConstraintLayout>
Now lets look at our Activity class.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnName = findViewById(R.id.btnName); btnAge = findViewById(R.id.btnAge); rvPerson = findViewById(R.id.rvPerson); rvPerson.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); rvPerson.setLayoutManager(layoutManager); persons = new ArrayList<>(); personAdapter = new PersonAdapter(persons); rvPerson.setAdapter(personAdapter); //load the items loadItems(); }
The header buttons and recyclerview have been initialized in the oncreate method.
private void loadItems() { persons.add(new Person("Alexander Sherikov", 30)); persons.add(new Person("Robert Krug", 35)); persons.add(new Person("Krzysztof Charusta", 33)); persons.add(new Person("Ivan Kalaykov", 60)); persons.add(new Person("Dimitar Dimitrov", 40)); persons.add(new Person("Boyko Iliev", 44)); persons.add(new Person("Achim Lilienthal", 43)); persons.add(new Person("Erik Berglund", 41)); persons.add(new Person("Todor Stoyanov", 35)); personAdapter.notifyDataSetChanged(); personAdapter.saveOriginalData(); // saving original data }
Some data have been added and saveOriginalData() method of the adapter class is called to store the original data.
Now we need to implement the method that will be called when name button is clicked. To get the sorting status of the data we shall check the what image is currently displayed. If currently unsorted icon is shown then we shall sort the data in descending order and show icon to represent descending order, whenever the descending order icon is shown we shall sort the data in acending order and show the respective icon, finally if the ascending order icon is shown we shall show the original data and icon accordingly.
The following method is used to handle button click on name header click.
public void onNameClicked(View view) { if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_unsorted).getConstantState() ) { //unsorted make sort in descending order by name and set icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_descending,0); //as data is not sorted by age put unsorted icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByName(PersonAdapter.DESCENDING); }else if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_descending).getConstantState() ) { //current state is in descending order, sort it to ascending and put icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_acending,0); //as data is not sorted by age put unsorted icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByName(PersonAdapter.ASCENDING); }else if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_acending).getConstantState() ) { //currently set as ascending order so show the original data and put icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.showOriginalData(); } personAdapter.notifyDataSetChanged(); }
Similary the onAgeClicked method is used for handling the sorting by age.
public void onAgeClicked(View view) { if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_unsorted).getConstantState() ) { //unsorted make sort in descending order by name and set icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_descending,0); //as data is not sorted by name put unsorted icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByAge(PersonAdapter.DESCENDING); }else if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_descending).getConstantState() ) { //current state is in descending order, sort it to ascending and put icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_acending,0); //as data is not sorted by name put unsorted icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByAge(PersonAdapter.ASCENDING); }else if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_acending).getConstantState() ) { //currently set as ascending order so show the original data and put icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.showOriginalData(); } personAdapter.notifyDataSetChanged(); }
The complete MainActivity class is given below
import android.os.Bundle; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; /** * Created by Asif Moinul Islam on 12/12/2018. */ public class MainActivity extends AppCompatActivity { private RecyclerView rvPerson; private PersonAdapter personAdapter; private ArrayList<Person> persons; private Button btnName, btnAge; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnName = findViewById(R.id.btnName); btnAge = findViewById(R.id.btnAge); rvPerson = findViewById(R.id.rvPerson); rvPerson.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); rvPerson.setLayoutManager(layoutManager); persons = new ArrayList<>(); personAdapter = new PersonAdapter(persons); rvPerson.setAdapter(personAdapter); //load the items loadItems(); } private void loadItems() { persons.add(new Person("Alexander Sherikov", 30)); persons.add(new Person("Robert Krug", 35)); persons.add(new Person("Krzysztof Charusta", 33)); persons.add(new Person("Ivan Kalaykov", 60)); persons.add(new Person("Dimitar Dimitrov", 40)); persons.add(new Person("Boyko Iliev", 44)); persons.add(new Person("Achim Lilienthal", 43)); persons.add(new Person("Erik Berglund", 41)); persons.add(new Person("Todor Stoyanov", 35)); personAdapter.notifyDataSetChanged(); personAdapter.saveOriginalData(); // saving original data } public void onNameClicked(View view) { if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_unsorted).getConstantState() ) { //unsorted make sort in descending order by name and set icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_descending,0); //as data is not sorted by age put unsorted icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByName(PersonAdapter.DESCENDING); }else if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_descending).getConstantState() ) { //current state is in descending order, sort it to ascending and put icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_acending,0); //as data is not sorted by age put unsorted icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByName(PersonAdapter.ASCENDING); }else if( btnName.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_acending).getConstantState() ) { //currently set as ascending order so show the original data and put icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.showOriginalData(); } personAdapter.notifyDataSetChanged(); } public void onAgeClicked(View view) { if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_unsorted).getConstantState() ) { //unsorted make sort in descending order by name and set icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_descending,0); //as data is not sorted by name put unsorted icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByAge(PersonAdapter.DESCENDING); }else if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_descending).getConstantState() ) { //current state is in descending order, sort it to ascending and put icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_sorted_acending,0); //as data is not sorted by name put unsorted icon btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.sortByAge(PersonAdapter.ASCENDING); }else if( btnAge.getCompoundDrawables()[2].getConstantState() == getResources().getDrawable(R.drawable.ic_sorted_acending).getConstantState() ) { //currently set as ascending order so show the original data and put icon btnAge.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); btnName.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_unsorted,0); personAdapter.showOriginalData(); } personAdapter.notifyDataSetChanged(); } }
The resulting app looks like the following
The source code of the app can be downloaded from here.