Using RecyclerView to build lists in Android

recyclerview-featured

RecyclerView is a modern, properly planned and more efficient improvement on the ListView. The ListView (and RecyclerView) are android widgets that can hold (and display) a collection of items. Each item in the list is displayed in an identical manner, and this is achieved by defining a single layout file that is inflated for each list item. Since the total number of items in a list can be arbitrarily large, it would be impractical to inflate the layout for each list item immediately the ListView is created. The ListView was created in such a way that Views that are no longer needed (possibly when the user has scrolled away) can be reused to display other items in the list as necessary. Some of the issues experienced with ListView, which RecyclerView is designed to solve include:

  1. ListView handled layout management. This might seem intuitively correct, however it is more work for the ListView, compared to the RecyclerView, which requires a LayoutManager.
  2. Only vertical scrolling is allowed in ListView. Items in a ListView can be arranged, displayed and scrolled in a vertical list only, whereas RecyclerView’s LayoutManager can create both vertical and horizontal lists (and diagonal lists if you care to implement that).
  3. The ViewHolder pattern is not enforced by ListView. The ViewHolder pattern holds Views in a cache, when created, and reuses Views from this cache as needed. While the ListView encourages the use of this pattern, it did not require it, and so, developers could ignore the ViewHolder pattern and create a new View every time. RecyclerView forces usage of this pattern.
  4. ListView has no animations. Animating removal and/or insertion of new items is not designed into ListView. However, with the increased maturity of the android platform and the material design obsession with aesthetics and animations, RecyclerView, by default, animates adding and removing list items. (Actually, RecyclerView.ItemAnimator handles these animations.)

To recap, the RecyclerView has an Adapter (to manage the items in the list), a ViewHolder (to hold a view representing a single list item), a LayoutManager (to handle the layout and scroll direction of the list) and an ItemAnimator (to handle animations). At this point, you might be thinking “This seems like a lot of work to display a list of items”. It’s actually really simple, but let’s get coding and you draw your own conclusions.

Using RecyclerView

Before using RecyclerView in your android project, you have to import the RecyclerView library as a project dependency. You can do this by either adding the following to your app build.gradle file

dependencies {
    ...
    compile 'com.android.support:RecyclerView-v7:24.2.0'
}

or right click on your project, select “Open Module Settings”, navigate to the “Dependencies” tab, and include the RecyclerView library from there. This way, you can be sure of importing the most recent RecyclerView library version available (as long as your android studio sdks are updated).

Sample Activity

We used the DataBinding pattern while developing the sample activities. If you are not familiar with developing using DataBinding, check out my previous tutorial. All our sample activities are going to display a list of Person objects, and the Person object is defined below.

public class Person {
    private String firstname;
    private String lastname;
    private String role;
    private String description;
    private Drawable image;

    public Person(){}

    public Person(String fname, String lname, String role, String description, Drawable image) {
        this.firstname = fname;
        this.lastname = lname;
        this.role = role;
        this.description = description;
        this.image = image;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getName() {
        return firstname + " " + lastname;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Drawable getImage() {
        return image;
    }

    public void setImage(Drawable image) {
        this.image = image;
    }
}

Also, we have created a Util class to abstract out the creation of List’s of Person objects. It has two static methods getPeopleList(), and getRandomPerson().

Simple List Sample

RecyclerView - Simple List Activity

For our first sample, we’ll create an activity called SimpleListActivity. This Activity would show a list of Person’s, and we will include the Person’s firstname and lastname in bold text, and the person’s role in smaller text. The layout for each list item is shown below, with two TextViews.

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

    <data>
        <import type="com.sample.foo.usingrecyclerview.Person"/>
        <variable
            name="person"
            type="Person"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/nameTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="@{person.firstname + ' ' + person.lastname}"/>

        <TextView
            android:id="@+id/roleTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:text="@{person.role}"/>
    </LinearLayout>
</layout>

The SimpleListActivity layout file contains a single RecyclerView.

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.sample.foo.usingrecyclerview.SimpleListActivity">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"
            />
    </RelativeLayout>
</layout>

Simple List Activity

The SimpleListActivity class itself is also pretty straightforward. We set the contentview using DataBindingUtil, which gets us a reference to the RecyclerView.

public class SimpleListActivity extends AppCompatActivity {

    private ActivitySimpleListBinding mSimpleListBinding;
    private RecyclerView.LayoutManager mLayoutManager;
    private RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTitle("Simple List");
        mSimpleListBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_simple_list);

        List people = Util.getPeopleList(this);

        mLayoutManager = new LinearLayoutManager(this);
        mSimpleListBinding.recyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new SimpleListAdapter(people);
        mSimpleListBinding.recyclerView.setAdapter(mAdapter);
    }
}

We populate our list with the Util.getPeopleList() method.

Note the two important things happening in the Activity.
Firstly, we specified that we want our RecyclerView to use the LinearLayoutManager, with the setLayoutManager method. RecyclerView has three built in LayoutManagers:

  1. LinearLayoutManager shows items in a vertical or horizontal scrolling list.
  2. GridLayoutManager shows items in a grid.
  3. StaggeredGridLayoutManager shows items in a staggered grid.

Secondly, we created and set the Adapter. You must create your own Adapter, since your adapter has to be unique to your dataset.

Creating the Adapter

The Adapter extends RecyclerView.Adapter, and contains three methods
onCreateViewHolder() – Here you inflate the View used for each list item
onBindViewHolder() – Here you bind values from your object to Views
getItemCount() – Returns the number of items in the list

Notice that we define our ViewHolder (SimpleViewHolder) inside the Adapter class. Keeps everything together.

public class SimpleListAdapter extends RecyclerView.Adapter<SimpleListAdapter.SimpleViewHolder> {

    private List<Person> mPeople;

    public SimpleListAdapter(List<Person> people){
        mPeople = people;
    }

    @Override
    public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int type) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.simple_list_item, parent, false);
        SimpleViewHolder holder = new SimpleViewHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(SimpleViewHolder holder, int position) {
        final Person person = mPeople.get(position);
        holder.getBinding().setVariable(BR.person, person);
        holder.getBinding().executePendingBindings();
    }

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

    public static class SimpleViewHolder extends RecyclerView.ViewHolder {

        private SimpleListItemBinding listItemBinding;

        public SimpleViewHolder(View v) {
            super(v);
            listItemBinding = DataBindingUtil.bind(v);
        }

        public SimpleListItemBinding getBinding(){
            return listItemBinding;
        }
    }
}

You can see in the above class, in onCreateViewHolder, we simply inflate the required layout (R.layout.simple_list_item), and parse it to our SimpleViewHolder class. In onBindView, we set the binding variable to the current Person, and that’s all.
If you are not using DataBinding methods, your ViewHolder implementation would look like

    public static class SimpleViewHolder extends RecyclerView.ViewHolder {

        protected TextView nameTextView;
        protected TextView roleTextView;

        public SimpleViewHolder(View v) {
            super(v);
            nameTextView = ((TextView)findViewById(R.id.nameTextView));
            roleTextView = ((TextView)findViewById(R.id.roleTextView));
        }
    }

and your onBindViewHolder

    @Override
    public void onBindViewHolder(SimpleViewHolder holder, int position) {
        final Person person = mPeople.get(position);
        holder.nameTextView(person.getName());
        holder.roleTextView(person.getRole());
    }

RecyclerView and CardView

CardView extends the FrameLayout class and lets you show information inside cards that have a consistent look across the platform. CardView widgets can have shadows and rounded corners, and are very popular in Google apps (Google+, Google now, Youtube)

RecyclerView - Google apps

Before using CardView in your app, you must include the CardView library in your app build.gradle file, the same way you included the RecyclerView library above

dependencies {
    ...
    compile 'com.android.support:cardview-v7:24.2.0'
}

The CardView then becomes the base view for your list_item layout file. In our CardActivity sample, we have a card_list_item layout file.

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

    <data>

        <import type="com.sample.foo.usingrecyclerview.Person" />

        <variable
            name="person"
            type="Person" />
    </data>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        card_view:cardCornerRadius="@dimen/cardview_default_radius"
        card_view:cardElevation="@dimen/cardview_default_elevation">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="5dp">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_alignParentRight="true"
                android:scaleType="fitXY"
                android:src="@{person.image}" />

            <TextView
                android:id="@+id/nameTextView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_toLeftOf="@id/imageView"
                android:text="@{person.firstname + ' ' + person.lastname}"
                android:textSize="22sp" />

            <TextView
                android:id="@+id/roleTextView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/nameTextView"
                android:layout_toLeftOf="@id/imageView"
                android:text="@{person.role}"
                android:textSize="18sp" />

            <TextView
                android:id="@+id/descriptionTextView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/roleTextView"
                android:text="@{person.description}"
                android:textSize="18sp" />
        </RelativeLayout>
    </android.support.v7.widget.CardView>
</layout>

Amazingly, there is really no difference between the SimpleListActivity and SimpleListAdapter classes above and the CardActivity and CardAdapter classes for this sample. (Apart from the class names, and the layout files of course). Using databinding, we reference the relevant person attributes in the card_list_item layout file, and, voila, it just works.

RecyclerView and CardView

Recall that one of the advantages of RecyclerView we touted at the beginning of this tutorial was that it doesn’t care about scroll direction, and/or layout of the items.

To use a GridLayout, you simply use the GridLayoutManager class. So, if you want a two column grid view for your list, for example, simply declare your LayoutManager as a GridLayoutManager, as shown below.

    mLayoutManager = new GridLayoutManager(this, 2);

Similarly, to scroll your list horizontally, you set the LayoutManager orientation

    ((LinearLayoutManager) mLayoutManager).setOrientation(LinearLayoutManager.HORIZONTAL);
    // OR
    ((GridLayoutManager) mLayoutManager).setOrientation(GridLayoutManager.HORIZONTAL);

Add/Remove Items

Our final activity will handle capturing click events, and adding and removing items from the list.

The click_list_item layout is identical to the card_list_item layout, but we added a remove button below the “@id/descriptionTextView”.

            <Button
                android:id="@+id/exitButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/descriptionTextView"
                android:layout_centerInParent="true"
                android:text="Remove" />

and a FAB that adds a new Person when clicked.

        mClickBinding.insertFAB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mAdapter.addPerson(Util.getRandomPerson(ClickActivity.this));
                ((LinearLayoutManager)mLayoutManager).scrollToPositionWithOffset(0, 0);
            }
        });

We use the scrollToPositionWithOffset(0, 0) method to force the LayoutManager to scroll to the top of the List when an object is added to the list.

RecyclerView - Add and Remove CardViews

The Adapter is also pretty similar to the CardAdapter class declared above. However, we included the addPerson() method to enable adding a new Person, and we also included an onClickListener to handle the Remove Button click events.

    @Override
    public void onBindViewHolder(final ClickViewHolder holder, final int position) {
        final Person person = mPeople.get(holder.getAdapterPosition());
        holder.getBinding().setVariable(BR.person, person);
        holder.getBinding().executePendingBindings();

        holder.getBinding().exitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPeople.remove(holder.getAdapterPosition());
                notifyItemRemoved(holder.getAdapterPosition());
            }
        });
    }

    public void addPerson(Person person) {
        mPeople.add(0, person);
        notifyItemInserted(0);
    }

Note (Very Important)

Take a closer look at the onBindViewHolder method above. Note that we refer to the current Person object using holder.getAdapterPosition() rather than the (int) position variable. This is because, whenever we remove an item from the list, we must call notifyStateChanged() to sync the list count and the Adapter item count. However, notifyStateChanged() stops the item removal animation. holder.getAdapterPosition() is guaranteed to be correct always, whereas the parsed position could be erroneous.

Add and Remove CardView gif

Conclusion

While the ListView is still a very capable view, for new projects, I’ll strongly advise you use RecyclerView, and consider the ListView as deprecated. I can’t think of any situation where the ListView is better than the RecyclerView, even if you implement your ListView with the ViewHolder pattern. The RecyclerView is incredibly easy to use once you get the hang of it, and it’s really worth the few minutes it takes you to experiment with the features so that you understand it’s workings.

As always, the complete source for the sample app discussed in the tutorial above is available on github for use (and misuse) as you see fit.

Happy coding

By | 2016-09-14T09:00:11+00:00 September 14th, 2016|Android Related, Just the Tablets|0 Comments

About the Author:

Vancouver, Canada