Sunday 6 March 2016

Android RecyclerView and CardView

The new support library in Android L introduced two new UI widgets: RecyclerView and CardView. The RecyclerView is a more advanced and more flexible version of the ListView. This new component is a big step because the ListView is one of the most used UI widgets. The CardView widget, on the other hand, is a new component that does not "upgrade" an existing component. In this tutorial, I'll explain how to use these two widgets and show how we can mix them. Let's start by diving into the RecyclerView.
RecyclerView: Introduction
As I mentioned, RecyclerView is more flexible that ListView even if it introduces some complexities. We all know how to use ListView in our app and we know if we want to increase the ListView performances we can use a pattern called ViewHolder. This pattern consists of a simple class that holds the references to the UI components for each row in the ListView. This pattern avoids looking up the UI components all the time the system shows a row in the list. Even if this pattern introduces some benefits, we can implement the ListView without using it at all. RecyclerView forces us to use the ViewHolder pattern. To show how we can use the RecyclerView, we can suppose we want to create a simple app that shows a list of contact cards. The first thing we should do is create the main layout. RecyclerView is very similar to the ListView and we can use them in the same way:
1.  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2.        xmlns:tools="http://schemas.android.com/tools"
3.        android:layout_width="match_parent"
4.        android:layout_height="match_parent"
5.        android:paddingLeft="@dimen/activity_horizontal_margin"
6.        android:paddingRight="@dimen/activity_horizontal_margin"
7.        android:paddingTop="@dimen/activity_vertical_margin"
8.        android:paddingBottom="@dimen/activity_vertical_margin"
9.        tools:context=".MyActivity">
10.       <android.support.v7.widget.RecyclerView
11.              android:id="@+id/cardList"
12.              android:layout_width="match_parent"
13.              android:layout_height="match_parent"
14.        />    
15. </RelativeLayout>
As you'll notice from the layout shown above, the RecyclerView is available in the Android support library, so we have to modify build.gradle to include this dependency:
1.  dependencies {
2.         ...    
3.         compile 'com.android.support:recyclerview-v7:21.0.0-rc1'
4.   }
Now, in the onCreate method we can get the reference to our RecyclerView and configure it:
1.  @Override
2.  protected void onCreate(Bundle savedInstanceState) {
3.        super.onCreate(savedInstanceState);
4.        setContentView(R.layout.activity_my);
5.        RecyclerView recList = (RecyclerView) findViewById(R.id.cardList);
6.        recList.setHasFixedSize(true);
7.        LinearLayoutManager llm = new LinearLayoutManager(this);
8.        llm.setOrientation(LinearLayoutManager.VERTICAL);
9.        recList.setLayoutManager(llm);
10. }
If you look at the code above, you'll notice some differences between the RecyclerView and ListView. RecyclerView requires a layout manager. This component positions item views inside the row and determines when it is time to recycle the views. The library provides a default layout manager calledLinearLayoutManager.
CardView
The CardView UI component shows information inside cards. We can customise its corners, the elevation and so on. We want to use this component to show contact information. These cards will be the rows of RecyclerView and we will see later how to integrate these two components. By now, we can define our card layout:
1.  <android.support.v7.widget.CardView
2.        xmlns:card_view="http://schemas.android.com/apk/res-auto"
3.        xmlns:android="http://schemas.android.com/apk/res/android"
4.        android:id="@+id/card_view"
5.        android:layout_width="match_parent"
6.        android:layout_height="match_parent"
7.        card_view:cardCornerRadius="4dp"
8.        android:layout_margin="5dp">
9.   
10.   <RelativeLayout
11.       android:layout_width="match_parent"
12.       android:layout_height="match_parent">
13.  
14.      <TextView
15.          android:id="@+id/title"
16.          android:layout_width="match_parent"
17.          android:layout_height="20dp"
18.          android:background="@color/bkg_card"
19.          android:text="contact det"
20.          android:gravity="center_vertical"
21.          android:textColor="@android:color/white"
22.          android:textSize="14dp"/>
23.  
24.     <TextView
25.         android:id="@+id/txtName"
26.         android:layout_width="wrap_content"
27.         android:layout_height="wrap_content"
28.         android:text="Name"
29.         android:gravity="center_vertical"
30.         android:textSize="10dp"
31.         android:layout_below="@id/title"
32.         android:layout_marginTop="10dp"
33.         android:layout_marginLeft="5dp"/>
34.  
35.     <TextView
36.         android:id="@+id/txtSurname"
37.         android:layout_width="wrap_content"
38.         android:layout_height="wrap_content"
39.         android:text="Surname"
40.         android:gravity="center_vertical"
41.         android:textSize="10dp"
42.         android:layout_below="@id/txtName"
43.         android:layout_marginTop="10dp"
44.         android:layout_marginLeft="5dp"/>
45.  
46.     <TextView
47.         android:id="@+id/txtEmail"
48.         android:layout_width="wrap_content"
49.         android:layout_height="wrap_content"
50.         android:text="Email"
51.         android:textSize="10dp"
52.         android:layout_marginTop="10dp"
53.         android:layout_alignParentRight="true"
54.         android:layout_marginRight="150dp"
55.         android:layout_alignBaseline="@id/txtName"/>
56.  
57. </RelativeLayout>
As you can see, the CardView is very simple to use. This component is available in another android support library so we have to add this dependency too:
1.  dependencies {
2.          compile 'com.android.support:cardview-v7:21.0.0-rc1'
3.          compile 'com.android.support:recyclerview-v7:21.0.0-rc1'
4.   }
RecyclerView: Adapter
The adapter is a component that stands between the data model we want to show in our app UI and the UI component that renders this information. In other words, an adapter guides the way the information are shown in the UI. So if we want to display our contacts, we need an adapter for the RecyclerView. This adapter must extend a class called RecyclerView.Adapter passing our class that implements the ViewHolder pattern:
public class MyAdapter extends RecyclerView.Adapter<MyHolder> { ..... }
We now have to override two methods so that we can implement our logic: onCreateViewHolderis called whenever a new instance of our ViewHolder class is created, and onBindViewHolder is called when the SO binds the view with the data -- or, in other words, the data is shown in the UI.
In this case, the adapter helps us combine the RecyclerView and CardView. The layout we defined before for the cards will be the row layout of our contact list in the RecyclerView. Before doing it, we have to define our data model that stands at the base of our UI (i.e. what information we want to show). For this purpose, we can define a simple class:
1.  public class ContactInfo {
2.        protected String name;
3.        protected String surname;
4.        protected String email;
5.        protected static final String NAME_PREFIX = "Name_";
6.        protected static final String SURNAME_PREFIX = "Surname_";
7.        protected static final String EMAIL_PREFIX = "email_";
8.  }
And finally, we are ready to create our adapter. If you remember what we said before about Viewholder pattern, we have to code our class that implements it:
1.  public static class ContactViewHolder extends RecyclerView.ViewHolder {
2.       protected TextView vName;
3.       protected TextView vSurname;
4.       protected TextView vEmail;
5.       protected TextView vTitle;
6.   
7.       public ContactViewHolder(View v) {
8.            super(v);
9.            vName =  (TextView) v.findViewById(R.id.txtName);
10.           vSurname = (TextView)  v.findViewById(R.id.txtSurname);
11.           vEmail = (TextView)  v.findViewById(R.id.txtEmail);
12.           vTitle = (TextView) v.findViewById(R.id.title);
13.       }
14.  }
Look at the code, in the class constructor we get the reference to the views we defined in our card layout. Now it is time to code our adapter:
1.  public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
2.   
3.      private List<ContactInfo> contactList;
4.   
5.      public ContactAdapter(List<ContactInfo> contactList) {
6.              this.contactList = contactList;
7.      }
8.   
9.      @Override
10.     public int getItemCount() {
11.           return contactList.size();
12.     }
13.  
14.     @Override
15.     public void onBindViewHolder(ContactViewHolder contactViewHolder, int i) {
16.         ContactInfo ci = contactList.get(i);
17.         contactViewHolder.vName.setText(ci.name);
18.         contactViewHolder.vSurname.setText(ci.surname);
19.         contactViewHolder.vEmail.setText(ci.email);
20.         contactViewHolder.vTitle.setText(ci.name + " " + ci.surname);
21.    }
22.  
23.    @Override
24.    public ContactViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
25.         View itemView = LayoutInflater.
26.                     from(viewGroup.getContext()).
27.                     inflate(R.layout.card_layout, viewGroup, false);
28.  
29.         return new ContactViewHolder(itemView);
30.    }
31.  
32.   public static class ContactViewHolder extends RecyclerView.ViewHolder {
33.       ...
34.   }
35. }
In our implementation, we override onBindViewHolder where we bind the data (our contact info) to the Views. Notice that we don't look up UI components but simply use the references stored in our ContactViewHolder. In onCreateViewHolder we return our ContactViewHolder inflating the row layout (the CardView in our case).