Android RecyclerView Example

Recently for my app, I needed to provide a scrollable list of clickable items. After doing a little research I found that the RecyclerView is the recommended choice since it recycles view items that are not in the user's focus. Here is a sample app that I created to demonstrate how to implement such a list.

Here is what the app looks like.

Now to get into the details of how to implement this list. First, let's take a look at how the RecyclerView is setup in a layout XML file. Note: I referenced the Google example, Creating Lists and Cards, which is why the code looks very similar.

activity_main.xml

1|  <?xml version="1.0" encoding="utf-8"?>
2|  <LinearLayout>
3|      xmlns:android="http://schemas.android.com/apk/res/android"
4|      xmlns:tools="http://schemas.android.com/tools"
5|      android:id="@+id/activity_main"
6|      android:orientation="vertical"
7|      android:layout_width="match_parent"
8|      android:layout_height="match_parent"
9|      android:paddingBottom="@dimen/activity_vertical_margin"
10|     android:paddingLeft="@dimen/activity_horizontal_margin"
11|     android:paddingRight="@dimen/activity_horizontal_margin"
12|     android:paddingTop="@dimen/activity_vertical_margin"
13|     tools:context="com.example.johnqualls.recyclerviewdemo.MainActivity">
14|    <Button
15|         android:id="@+id/addItem"
16|         android:layout_width="match_parent"
17|         android:layout_height="wrap_content"
18|        android:text="Add"/>
19|    <Button
20|        android:id="@+id/removeItem"
21|        android:layout_width="match_parent"
22|        android:layout_height="wrap_content"
23|        android:text="Remove"/>
24|    <android.support.v7.widget.RecyclerView
25|        android:id="@+id/my_recycler_view"
26|        android:scrollbars="vertical"
27|        android:layout_width="match_parent"
28|        android:layout_height="wrap_content"/>
29| </LinearLayout>

The important thing to note here is the RecyclerView element at the bottom (Line 24). This element is placed where you want your list to appear in the UI. It is also needed by the Activity (or Fragment) to programmatically retrieve a reference. Next, we'll take a look at the Activity code to see how to configure the RecyclerView.

MainActivity.java

1|   package com.example.johnqualls.recyclerviewdemo;
2|   
3|   import android.app.Activity;
4|   import android.os.Bundle;
5|   import android.support.v7.widget.LinearLayoutManager;
6|   import android.support.v7.widget.RecyclerView;
7|   import android.view.View;
8|   import android.widget.Button;
9|  
10|  import java.util.ArrayList;
11|  import java.util.List;
12|  
13|  public class MainActivity extends Activity implements Button.OnClickListener{
14|      private int count;
15|      private RecyclerView mRecyclerView;
16|      private TestAdapter mAdapter;
17|      private RecyclerView.LayoutManager mLayoutManager;
18|      private List dataset;
19|      private Button addItem,
20|                     removeItem;
21|      private boolean clickable;
22|  
23|      @Override
24|      protected void onCreate(Bundle savedInstanceState) {
25|          super.onCreate(savedInstanceState);
26|          setContentView(R.layout.activity_main);
27|          mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
28|  
29|          // Register buttons
30|          clickable = true;
31|          addItem = (Button) findViewById(R.id.addItem);
32|          removeItem = (Button) findViewById(R.id.removeItem);
33|          addItem.setOnClickListener(this);
34|          removeItem.setOnClickListener(this);
35|  
36|          // use this setting to improve performance if you know that changes
37|          // in content do not change the layout size of the RecyclerView
38|          mRecyclerView.setHasFixedSize(true);
39|  
40|          // use a linear layout manager
41|          mLayoutManager = new LinearLayoutManager(this);
42|          mRecyclerView.setLayoutManager(mLayoutManager);
43|  
44|          // Initialize dataset
45|          dataset = new ArrayList<>();
46|          Item item = new Item("Key0", "Value0");
47|          dataset.add(item);
48|  
49|          // Counter for newly added Items
50|          count = 1;
51|  
52|          // specify an adapter
53|          mAdapter = new TestAdapter(dataset);
54|          mRecyclerView.setAdapter(mAdapter);
55|      }
56|  
57|  
58|      @Override
59|      public void onClick(View view) {
60|          if(view.getId() == R.id.addItem) {
61|              Item item = new Item("Key" + count, "Value" + count);
62|              dataset.add(item);
63|              mAdapter.notifyItemRangeInserted(dataset.size() - 1, 1);
64|              count++;
65|          }
66|          else if(dataset.size() > 0){
67|              int removePosition = dataset.size() - 1;
68|              dataset.remove(removePosition);
69|              mRecyclerView.removeViewAt(removePosition);
70|              count--;
71|          }
72|      }
73|  }

There are few steps involved when setting up the RecyclerView in the Activity.

  1. A RecyclerView reference must be retreieved.
  2. 27|          mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
    
  3. Initialize and set the RecyclerView's LinearLayoutManager. This is needed to manage the LinearLayouts used in the TestAdapter class.
  4. 40|          // use a linear layout manager
    41|          mLayoutManager = new LinearLayoutManager(this);
    42|          mRecyclerView.setLayoutManager(mLayoutManager);
    
  5. Initialize data set.
  6. 44|          // Initialize dataset
    45|          dataset = new ArrayList<>();
    46|          Item item = new Item("Key0", "Value0");
    47|          dataset.add(item);
    
  7. Pass the data set reference as a constructor parameter of a newly constructed RecyclerView.Adapter type (TestAdapter used as my sub class implementation), and set it as the RecyclerView's Adapter.
  8. 52|          // specify an adapter
    53|          mAdapter = new TestAdapter(dataset);
    54|          mRecyclerView.setAdapter(mAdapter);
    
  9. In order to notify the RecyclerView to dynamically update the View items when data set elements are changed, the following lines are needed.
  10. 63|              mAdapter.notifyItemRangeInserted(dataset.size() - 1, 1);
    ...
    69|              mRecyclerView.removeViewAt(removePosition);
    

TestAdapter.java

The adapter is needed to bind data set elements to View instances.

Source: Creating Lists and Cards

1|   package com.example.johnqualls.recyclerviewdemo;
2|   
3|   import android.support.v7.widget.RecyclerView;
4|   import android.util.Log;
5|   import android.view.LayoutInflater;
6|   import android.view.ViewGroup;
7|   import android.widget.LinearLayout;
8|   import android.widget.TextView;
9|  
10|  import java.util.List;
11|  
12|  public class TestAdapter extends RecyclerView.Adapter {
13|      private List mDataset;
14|  
15|      // Provide a reference to the views for each data item
16|      // Complex data items may need more than one view per item, and
17|      // you provide access to all the views for a data item in a view holder
18|      public static class ViewHolder extends RecyclerView.ViewHolder {
19|          public LinearLayout mLinearLayout;
20|  
21|          public ViewHolder(LinearLayout v) {
22|              super(v);
23|              mLinearLayout = v;
24|          }
25|      }
26|  
27|      // Provide a suitable constructor (depends on the kind of dataset)
28|      public TestAdapter(List myDataset) {
29|          mDataset = myDataset;
31|      }
32|  
33|      // Create new views (invoked by the layout manager)
34|      @Override
35|      public TestAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
36|          // create a new view
37|          LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
38|                  .inflate(R.layout.row_view, parent, false);
39|          // set the view's size, margins, paddings and layout parameters
40|          //...
41|          ViewHolder vh = new ViewHolder(v);
42|          return vh;
43|      }
44|  
45|      // Replace the contents of a view (invoked by the layout manager)
46|      @Override
47|      public void onBindViewHolder(ViewHolder holder, int position) {
48|          // - get element from your dataset at this position
49|          // - replace the contents of the view with that element
50|          Item item = mDataset.get(position);
51|  
52|          Log.i(TestAdapter.class.getName(), "onBindViewHolder() -> PopulateView");
53|          ((TextView) holder.mLinearLayout.getChildAt(0)).setText(item.getKey());
54|          ((TextView) holder.mLinearLayout.getChildAt(1)).setText(item.getValue());
55|      }
56|  
57|      // Return the size of your dataset (invoked by the layout manager)
58|      @Override
59|      public int getItemCount() {
60|          return mDataset.size();
61|      }
62|  
63|  }

Let's look into the details of this class.

  1. The dataset reference is passed via a constructor argument.
  2. 28|      public TestAdapter(List myDataset) {
    29|          mDataset = myDataset;
    31|      } 
    
  3. ViewHolders are needed to store Views for each data item in the dataset. The row_view.xml layout is inflated and a reference is passed to a new ViewHolder. In this example, I'm using one LinearLayout per data item.
  4. 33|      // Create new views (invoked by the layout manager)
    34|      @Override
    35|      public TestAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    36|          // create a new view
    37|          LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
    38|                  .inflate(R.layout.row_view, parent, false);
    39|          // set the view's size, margins, paddings and layout parameters
    40|          //...
    41|          ViewHolder vh = new ViewHolder(v);
    42|          return vh;
    43|      }
    
  5. onBindViewHolder() is invoked for each newly initialized ViewHolder. This is where the view is updated with the corresponding data item. In this example, I am applying two values to TextViews within a LinearLayout.
  6. 46|      @Override
    47|      public void onBindViewHolder(ViewHolder holder, int position) {
    48|          // - get element from your dataset at this position
    49|          // - replace the contents of the view with that element
    50|          Item item = mDataset.get(position);
    51|  
    52|          Log.i(TestAdapter.class.getName(), "onBindViewHolder() -> PopulateView");
    53|          ((TextView) holder.mLinearLayout.getChildAt(0)).setText(item.getKey());
    54|          ((TextView) holder.mLinearLayout.getChildAt(1)).setText(item.getValue());
    55|      }
    
  7. Finally, the dataset size must be returned from getItemCount(). This is required in order for the LayoutManager to function correctly.
  8. 58|      @Override
    59|      public int getItemCount() {
    60|          return mDataset.size();
    61|      } 
    

row_view.xml

This layout represents the UI for each row in the RecyclerView list.

1|  <?xml version="1.0" encoding="utf-8"?>
2|  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3|              android:orientation="horizontal"
4|              android:layout_width="match_parent"
5|              android:layout_height="match_parent">
6|    <TextView
7|         android:id="@+id/name1"
8|         android:layout_width="match_parent"
9|         android:layout_height="wrap_content"
10|        android:layout_weight=".5"
12|        android:textSize="16sp"/>
13|
14|    <TextView
15|        android:id="@+id/name2"
16|        android:layout_width="match_parent"
17|        android:layout_height="wrap_content"
18|        android:layout_weight=".5"
19|        android:textSize="16sp"/>
20| </LinearLayout>

At this point, you should now have a fully functional RecyclerView that can remove and add items at runtime!
The source code can be downloaded from my GitHub Repository.

Comments

Popular posts from this blog

Mocking Super Class Method Invocations with PowerMock

Verifying Static Method Calls

Unit Testing a Singleton Using PowerMock