Android View Tutorial - Recyclerview
Quick Links

Jump to:

IMPORTANT: This Tutorial has been deprecated. For a better App and Tutorial, please see my new Android RecyclerView Tutorial at this link:

Android RecyclerView Tutorial 2017

Part 1 - Introduction

Check out the video here.
Welcome to my RecyclerView Tutorial series. Please be aware that I plan on remaking this series with better quality code and video. This was my first attempt at a Tutorial Series, and I’ve also improved as a Developer since creating it. That being said, I think it serves it’s purpose well for now.</p>

The RecyclerView widget is among the most commonly used components you will come across as an Android Developer. Displaying and manipulating a List of data happens more often than not in App development, and you can see plenty of examples of the RecyclerView in Google’s own apps such has Gmail, Note, and Messenger.

Introduced in API 21 it functions as a powerful improvement over its predecessor, the ListView.  I’ll go into some more detail about why the RecyclerView is generally a better option in later parts of the tutorial, but here’s some broad strokes:

  • It's easier to Animate and Manipulate.
  • It handles scrolling large datasets very efficiently. By default, it implements the ViewHolder pattern to avoid repeated calls to findViewById(), which can slow things down real fast.
  • Now that it's been released, the Android Developement Team will be focusing on RecyclerView instead of ListView. Need I say more?

In order to hopefully save some time and provide multiple forms of media for learning, I’ve created an accompanying youtube video for each part of this series (click the brown Camera Icon beside each section Header). The videos and corresponding sections are intended to be complimentary to each other, so I won’t just be repeating myself in each. I’ll provide source code, relevant links, and succinct instructions in the blog; the videos will go into finer detail in terms of information and examples if you want a more comprehensive experience.

For this and any other Android tutorial, I’ll be using Android Studio as my IDE. Although there shouldn’t be any reason why you can’t get through the entire tutorial using Eclipse ADT bundle, we’ll be importing a support library or two using Gradle (Android Studio’s default build automation tool). One of the main reasons I switched to Android Studio (aside from AS being a dedicated platform, supported by google itself), was the obnoxious hassle it was to import libraries into Eclipse. If you must use another IDE, you’ll have to figure out parallel instructions and sort out specific IDE problems on your own time. I’m not a help desk for IDE problems :).

 

Part 2a - Basic RecyclerView

Check out the video here.
Create a new Mobile/Tablet Project, called DerpList (or call it something else if you wish).

  • Choose an minimum SDK of 7 or greater, I'd suggest 16 (RecyclerView's support library covers 7 or higher).
  • While we're here, create an "Empty Activity" called ListActivity, and generate the Layout file as well.
  • Hit "Finish" to create the project.

Create the following files in your project:

  • list_item.xml (Layout Resource)
  • DerpAdapter.java, DerpData.java, ListItem.java (Java Classes)

I’ve separated my Java classes into relevant packages, but this part is optional. Once finished, your Directory Structure should look something like this:

directory
Open up your App level build.gradle file (Module: app), and add the following entry in the dependencies section [compile ‘com.android.support:recyclerview-v7:23.1.1’]. You may need to change the last few digits appropriately, depending on what you have installed (you can find a list of Revisions here). Your gradle file should like something like this:

gradle
Now we’ll set up our XML Layouts. Edit your list_item.xml and list_activity.xml files as so:

 
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:gravity="center_vertical|left"
    android:orientation="horizontal"
    android:paddingLeft="16dp"
    android:id="@+id/cont_item_root"
    >

     <ImageView
        android:id="@+id/im_item_icon"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@android:drawable/ic_menu_edit"
        android:tint="@color/colorPrimary" />

     <TextView
        android:id="@+id/lbl_item_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center"
        android:paddingLeft="8dp"
        android:text="Derp Derpsen" />

 </LinearLayout>
 
<!--activity_list.xml-->

 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wiseassblog.derplist.ListActivity">

     <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rec_list"
        >

     </android.support.v7.widget.RecyclerView>
 </RelativeLayout>
 

Let’s get our POJO (Plain Old Java Object) and dummy data source out of the way. ListItem.java is a Java object (surprisingly enough), which will be used as a container for the type of data represented in a single item of our RecyclerView.

 
public class ListItem {
    private String title;
    private int imageResId;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getImageResId() {
        return imageResId;
    }

    public void setImageResId(int imageResId) {
        this.imageResId = imageResId;
    }
}
 

DerpData.java simply creates a collection of ListItem.java objects, which we will use as mock data for our RecyclerView. I’ve used a couple default Android Icons, as well as some esoteric Bruce Lee quotes on the subject of Zen; feel free to use whatever you like.

 
//DerpData.java
import java.util.ArrayList;
import java.util.List;

/**
 * This class is a dummy Fake source, used to simulate the kind of input you might recieve from a
 * Database or Web source.
 * Created by Ryan on 01/03/2016.
 */
public class DerpData {
    private static final String[] titles  = {"Nothingness cannot be defined", "The softest thing cannot be snapped",
            "be like water, my friend."} ;
    private static final int[] icons = {android.R.drawable.ic_popup_reminder, android.R.drawable.ic_menu_add,
            android.R.drawable.ic_menu_delete};

    public static List <ListItem> getListData() {
        List <ListItem> data = new ArrayList <>();

        //Repeat process 4 times, so that we have enough data to demonstrate a scrollable
        //RecyclerView
        for (int x = 0; x  < 4; x++) {
            //create ListItem with dummy data, then add them to our List
            for (int i = 0; i  < titles.length && i  < icons.length; i++) {
                ListItem item = new ListItem();
                item.setImageResId(icons[i]);
                item.setTitle(titles[i]);
                data.add(item);
            }
        }
        return data;
    }
}
 

 

Part 2b - Basic RecyclerView

Check out the video here.
We’ll start by adding some code to our ListActivty.java.

  • Create instance variables for our RecyclerView and DerpAdapter
  • Get a reference to the RecyclerView in activity_list.xml
  • Set a LinearLayoutManager to the RecyclerView (Don't be an idiot like me a miss this step on two multiple occasions!)
  • Set mAdapter equal to a new instance of DerpAdapter.java, passing in a call to our static data from DerpData.java, and the Activity's context
  • Call setAdapter() on the RecyclerView and pass in mAdapter

At this point, Android Studio/Eclipse will be lit up like a Christmas tree, but don’t worry about that; We’ll sort things out momentarily.

 
	public class ListActivity extends AppCompatActivity {

    private RecyclerView recView;
    private DerpAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);
        
        recView = (RecyclerView)findViewById(R.id.rec_list);
        //Check out GridLayoutManager and StaggeredGridLayoutManager for more options
        recView.setLayoutManager(new LinearLayoutManager(this));

        adapter = new DerpAdapter(DerpData.getListData(), this);
        recView.setAdapter(adapter);
    }
}
 

The last piece of the puzzle is finishing off the RecyclerView’s Adapter. There’s a fair number of things we need to do here, and If you aren’t already doing so, you may wish to watch the Video tutorial at this point.

In the interest of not having you jump all over the place, I’ll follow a specific process for coding up the rest of DerpAdapter.java. That being said, I normally just copy/paste it from a ready made template, and would suggest you do so once you have played around with the class. The following code snippets are to be placed inside the body of DerpAdapter.java.

 
public class DerpAdapter extends RecyclerView.Adapter<DerpAdapter.DerpHolder> {
  
}

Add a nested class called DerpHolder to the adapter, and make it a subclass of RecyclerView.ViewHolder. Click somewhere on the Class declaration, hit Alt + Enter, and select “create constructor matching super” (or just copy and paste the class from below). We’ll also add some instance variables to get a handle to the Views inside list_item.xml. Important: be sure to call [Class-Cast]itemView.findViewById(). Omitting itemView will cause problems. That’s our ViewHolder class done for now.

 
	class DerpHolder extends RecyclerView.ViewHolder {

        private TextView title;
        private ImageView icon;
        private View container;

        public DerpHolder(View itemView) {
            super(itemView);
            
            title = (TextView)itemView.findViewById(R.id.lbl_item_text);
            icon = (ImageView)itemView.findViewById(R.id.im_item_icon);
            //We'll need the container later on, when we add an View.OnClickListener
            container = itemView.findViewById(R.id.cont_item_root);
        }
    }
 

Now that we’ve created our ViewHolder, we can set up our Adapter properly.

  • Make it a subclass of "RecyclerView.Adapter<DerpAdapter.DerpHolder>"
  • Click on the class declaration and Hit Alt + Enter to implement the methods we'll need (onCreateViewHolder etc...)
  • Create instance variables for the List<ListItem> we passed in from the Activity, and a LayoutInflater which we'll create from the Context we passed in
  • Create a constructor which receives our list and context, and initializes the variables we've just created
 
	private List<ListItem> listData;
  	private LayoutInflater inflater;

    public DerpAdapter(List<ListItem> listData, Context c){
        inflater = LayoutInflater.from(c);
        this.listData = listData;
    }
 

Good job if you’ve made it this far, I’m sure this is some exciting stuff for you ;). Let’s finish off the rest of our methods. They handle inflating our RecyclerView’s ListItems, binding data relative to the index (position) of listData, and keeping track of the size of our dataset.

 
	@Override
    public DerpAdapter.DerpHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.list_item, parent, false);
        return new DerpHolder(view);
    }

	@Override
    public void onBindViewHolder(DerpHolder holder, int position) {
        ListItem item = listData.get(position);
        holder.title.setText(item.getTitle());
        holder.icon.setImageResource(item.getImageResId());
    }

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

That’s the end of Part 2! Fire up your phone or emulator and give the App a shot. It doesn’t do much, but you should have a simple RecyclerView, which we’ll be adding to in the coming tutorials. Thanks for watching/reading/coding.

 

Part 3a - OnClick and Design


Check out the video here.

Although I’m doing my best to save time by using default Android resources for drawables, I recommend that you head on over to this page (don’t worry, it’s a Google site!), and download the following icons:

  • ic_star_black_24dp.png
  • ic_star_border_black_24dp.png
  • ic_tonality_black_24dp.png

In the previous section, I used some default Android icons which were ugly as hell. Since we’re working on aesthetics, I thought it would be a good idea to use some better drawables.

We’re going to make some alterations to the model classes. Open up ListItem.java and add the following:

  • A String called subTitle
  • A Boolean called favourite (or favorite for those of you who live south of me ;P), initialized to false.
  • Some getters and setters for both
 
	public class ListItem {
    private int imageResId;
    private String subTitle;
    private String title;
    private boolean favourite = false;

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public boolean isFavourite() {
        return favourite;
    }

    public void setFavourite(boolean favourite) {
        this.favourite = favourite;
    }

    public int getImageResId() {
        return imageResId;
    }

    public void setImageResId(int imageResId) {
        this.imageResId = imageResId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
 

In order to more accurately demonstrate what a RecyclerView might look like and contain, I’ve made some changes to DerpData.java. Later on, we’ll be retrieving this data from our RecyclerView and passing it to a new Activity.

 
	public class DerpData {
    private static final String[] titles = {"Nothingness cannot be defined",
            "Time is like a river made up of the events which happen, and a violent stream; " +
                    "for as soon as a thing has been seen, it is carried away, and another comes" +
                    " in its place, and this will be carried away too,",
            "But when I know that the glass is already broken, every minute with it is precious.",
            "For me, it is far better to grasp the Universe as it really is than to persist in" +
                    " delusion, however satisfying and reassuring.",
            "The seeker after the truth is not one who studies the writings of the ancients and," +
                    " following his natural disposition, puts his trust in them, but rather the" +
                    " one who suspects his faith in them and questions what he gathers from them," +
                    " the one who submits to argument and demonstration, and not to the " +
                    "sayings of a human being whose nature is fraught with all kinds " +
                    "of imperfection and deficiency.",
            "You must take personal responsibility. You cannot change the circumstances, the" +
                    " seasons, or the wind, but you can change yourself. That is something you" +
                    " have charge of."
    };
    private static final String[] subTitles = {"Bruce Lee",
            "Marcus Aurelius",
            "Meng Tzu",
            "Ajahn Chah",
            "Carl Sagan",
            "Alhazen",
            "Jim Rohn"

    };
    private static final int icon = R.drawable.ic_tonality_black_36dp;

    public static List <ListItem> getListData() {
        List <ListItem> data = new ArrayList <>();

        //Repeat process 4 times, so that we have enough data to demonstrate a scrollable
        //RecyclerView
        for (int x = 0; x  < 4; x++) {
            //create ListItem with dummy data, then add them to our List
            for (int i = 0; i  < titles.length; i++) {
                ListItem item = new ListItem();
                item.setTitle(titles[i]);
                item.setSubTitle(subTitles[i]);
                data.add(item);
            }
        }
        return data;
    }
}
 

At the end of the previous section, we made list_item.xml contain a primary icon, and a single line of text. Although I could’ve stuck with that, I thought it would be more prudent if I demonstrated a more complicated RecyclerView; with a second TextView and a secondary icon which will handle its own clicks (Damn, Thinking big huh?).

You’ll probably be well aware of this by now, but there’s many ways to achieve the same layout, with varying pros and cons. I’ve chosen to utilize a RelativeLayout, as it allows me to refrain from creating a large number of nested ViewGroups. It probably doesn’t make much difference in the long run, but avoiding nested layouts takes up less resources. Feel free to play around with the layout if you aren’t happy with it; that’s pretty much how I learned how to make them.

 
	<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cont_item_root"
    android:layout_width="match_parent"
    android:layout_height="72dp"
    android:background="@drawable/background_state_drawable"
    android:clickable="true"
   >

     <ImageView
        android:id="@+id/im_item_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:paddingLeft="16dp"
        android:src="@drawable/ic_tonality_black_36dp" />

     <TextView
        android:id="@+id/lbl_item_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/im_item_icon"
        android:layout_marginLeft="72dp"
        android:layout_marginRight="48dp"
        android:ellipsize="end"
        android:fontFamily="sans-serif"
        android:singleLine="true"
        android:text="Sois comme l'eau mon ami"
        android:textColor="@android:color/black"
        android:textSize="16sp" />

     <TextView
        android:id="@+id/lbl_item_sub_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lbl_item_text"
        android:layout_marginLeft="72dp"
        android:layout_marginRight="48dp"
        android:ellipsize="end"
        android:fontFamily="sans-serif"
        android:singleLine="true"
        android:text="Mononc' J"
        android:textSize="14sp" />

     <ImageView
        android:id="@+id/im_item_icon_secondary"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:clickable="true"
        android:padding="16dp"
        android:src="@drawable/ic_star_border_black_24dp"
        android:background="@drawable/background_state_drawable"
        />

 </RelativeLayout>
 

You’ll be missing background_state_drawable.xml, so we’ll add that now. I’ve included both files in one gist, but you’ll need to include them in separate files obviously.

 
	<!--This one goes in drawable-->
 <?xml version="1.0" encoding="utf-8"?>
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true">
         <shape>
             <solid android:color="#B6B6B6"/>
         </shape>
     </item>
     <item>
         <shape>
             <solid android:color="@android:color/transparent"/>
         </shape>
     </item>
 </selector>

 <!--This one goes in drawable-v21-->
 <?xml version="1.0" encoding="utf-8"?>
 <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#B6B6B6">
     <item android:id="@android:id/mask" android:drawable="@android:color/white"/>
 </ripple>	
 
  1. Create a new empty Activity called DetailActivity (generate the layout automatically as well). We’ll write the code for this Activity in the next section, but we can take care of the layout. Add two TextViews as such:
 
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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.wiseassblog.derpdemo.ui.DetailActivity">

     <TextView
        android:id="@+id/lbl_quote_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fontFamily="sans-serif-medium" />

     <TextView
        android:id="@+id/lbl_quote_attribution"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbl_quote_text"
        android:fontFamily="sans-serif-light"
        android:textStyle="italic" />

 </RelativeLayout>
 

Part 3b - OnClick and Design

Check out the video here.

1. Open up DerpAdapter.java. This next portion can be a bit confusing, since we're using both a View.OnClickListener attached to our ViewHolder, and an Interface to pass messages to our Activity. If you haven't worked with Interfaces much, just think of it as a means for our Adapter to communicate with its Activity, without having to keep a reference to it in memory. Here's the steps:

  • Create an interface inside DerpAdapter.java, called itemClickCallbacks, with two methods: The first, onItemClick(int p), will fire when the user clicks on anything but the secondary action icon; the second, onSecondaryIconClick(int p) doesn't warrant further explanation.
  • Make Derpholder implement View.OnClickListener, and override the requisite methods.
  • Call .setOnClickListener(this) on container and secondaryIcon.
  • Update onBindViewHolder() to handle our new kinds of data.
  • Depending on whether container or secondaryIcon was clicked, call the appropriate interface method, passing getAdapterPosition() as our parameter.
After the dust settles, you're DerpAdapter.java should look like this:
 
	public class DerpAdapter extends RecyclerView.Adapter <DerpAdapter.DerpHolder>{

    private List <ListItem> listData;
    private LayoutInflater inflater;

    private ItemClickCallback itemClickCallback;

    public interface ItemClickCallback {
        void onItemClick(int p);
        void onSecondaryIconClick(int p);
    }

    public void setItemClickCallback(final ItemClickCallback itemClickCallback) {
        this.itemClickCallback = itemClickCallback;
    }

    public DerpAdapter(List <ListItem> listData, Context c){
        inflater = LayoutInflater.from(c);
        this.listData = listData;
    }

    @Override
    public DerpAdapter.DerpHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.list_item, parent, false);
        return new DerpHolder(view);
    }

    @Override
    public void onBindViewHolder(DerpHolder holder, int position) {
        ListItem item = listData.get(position);
        holder.title.setText(item.getTitle());
        holder.subTitle.setText(item.getSubTitle());
        if (item.isFavourite()){
            holder.secondaryIcon.setImageResource(R.drawable.ic_star_black_24dp);
        } else {
            holder.secondaryIcon.setImageResource(R.drawable.ic_star_border_black_24dp);
        }
    }

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

    class DerpHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        ImageView thumbnail;
        ImageView secondaryIcon;
        TextView title;
        TextView subTitle;
        View container;

        public DerpHolder(View itemView) {
            super(itemView);
            thumbnail = (ImageView)itemView.findViewById(R.id.im_item_icon);
            secondaryIcon = (ImageView)itemView.findViewById(R.id.im_item_icon_secondary);
            secondaryIcon.setOnClickListener(this);
            subTitle = (TextView)itemView.findViewById(R.id.lbl_item_sub_title);
            title = (TextView)itemView.findViewById(R.id.lbl_item_text);
            container = (View)itemView.findViewById(R.id.cont_item_root);
            container.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.cont_item_root){
                itemClickCallback.onItemClick(getAdapterPosition());
            } else {
                itemClickCallback.onSecondaryIconClick(getAdapterPosition());
            }
        }
    }
}
 
2. Open up ListActivity.java, and make the following changes:
  • Make the Activity implement our DerpAdapter.ItemClickCallback interface, and implement the requisite methods.
  • Add three String keys globally, which we will use for assigning our extras.
  • In onItemClick(int p), grab the appropriate item from listData() based on our Adapter's position (p).
  • Make a new intent with the appropriate data as a bundle, and call startActivity().
Once again, make it so:
 
	public class ListActivity extends AppCompatActivity implements DerpAdapter.ItemClickCallback {
    private static final String BUNDLE_EXTRAS = "BUNDLE_EXTRAS";
    private static final String EXTRA_QUOTE = "EXTRA_QUOTE";
    private static final String EXTRA_ATTR = "EXTRA_ATTR";

    private RecyclerView recyclerView;
    private DerpAdapter adapter;
    private ArrayList listData;

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

        listData = (ArrayList) DerpData.getListData();

        recyclerView = (RecyclerView) findViewById(R.id.rec_list);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        adapter = new DerpAdapter(DerpData.getListData(), this);
        recyclerView.setAdapter(adapter);
        adapter.setItemClickCallback(this);
    }

    @Override
    public void onItemClick(int p) {
        ListItem item = (ListItem) listData.get(p);

        Intent i = new Intent(this, DetailActivity.class);
        
        Bundle extras = new Bundle();
        extras.putString(EXTRA_QUOTE, item.getTitle());
        extras.putString(EXTRA_ATTR, item.getSubTitle());
        i.putExtra(BUNDLE_EXTRAS, extras);
        
        startActivity(i);
    }

    @Override
    public void onSecondaryIconClick(int p) {

    }
}
 
3. Open up DetailActivity.java, and add these things:
  • Copy/Paste the String keys from ListActivity.java
  • Grab the bundle extras
  • Call setText() with our data from the bundle, on our TextViews
  • Done!
That's the last important step in this tutorial. I consider Step 4 to be bonus, since not everyone cares about being able to handle a secondary action in the RecyclerView. Once finished, DetailActivity.java should look like this:
 
	public class DetailActivity extends AppCompatActivity {
    private static final String BUNDLE_EXTRAS = "BUNDLE_EXTRAS";
    private static final String EXTRA_QUOTE = "EXTRA_QUOTE";
    private static final String EXTRA_ATTR = "EXTRA_ATTR";


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

        Bundle extras = getIntent().getBundleExtra(BUNDLE_EXTRAS);

        ((TextView)findViewById(R.id.lbl_quote_text)).setText(extras.getString(EXTRA_QUOTE));
        ((TextView)findViewById(R.id.lbl_quote_attribution)).setText(extras.getString(EXTRA_ATTR));
    }
}
 
4. ***Note:*** Updating a RecyclerView's data has been the subject of much debate when consulting the Oracle (StackOverflow, not the company). For the purposes of this tutorial, I'm going to show you a simple way to do so, which doesn't require threading, since our data is infinitesimal. If you are going to be parsing data from the web, or reading from a database, you'll most likely need to use some threading wizardry. You have been warned! Add this to onSecondaryIconClick(int p) in ListActivity.java:
 
	@Override
    public void onSecondaryIconClick(int p) {
        ListItem item = (ListItem) listData.get(p);
        //update our data
        if (item.isFavourite()){
            item.setFavourite(false);
        } else {
            item.setFavourite(true);
        }
        //pass new data to adapter and update
        adapter.setListData(listData);
        adapter.notifyDataSetChanged();
    }
 
Add this method to DerpAdapter.java:
 
	public void setListData(ArrayList <ListItem> exerciseList) {
        this.listData.clear();
        this.listData.addAll(exerciseList);
    }
 
That's it! Try clicking the secondary Icon, and it should update appropriately. Thanks for learning.

Part 4 - CardViews

Check out the video here.

This one should be pretty quick. As I mentioned in the first video, I wanted to take a moment to show you how to make a RecyclerView which uses CardViews. As the actual process of implementing cards in Java isn't particularly difficult/interesting, I'll talk about. Before we begin, just a quick note: My original plan was to have the App able to tab between a more standard RecyclerView, and a RecylcerView which uses CardViews. While this isn't difficult to do, it would've added some extra steps to the tutorial which don't really have anything to do with the content I'm trying to cover. As such, we're going to treat Part 4 as a bonus section. All that this means is that the next tutorial will continue on from the code we used in Part 3. To save yourself some headaches, I would suggest either making a copy of the project, or checking it into Revision Control. 1. We'll start by importing the appropriate Support Library. Add the following line to your App level build.gradle:

'com.android.support:cardview-v7:23.4.0'
Obviously, you may need to change update to the appropriate version. 2. Create a layout file called card_item.xml. This will be the layout resource for each item in the RecyclerView. As the CardView class itself extends FrameLayout, we can think of it as a ViewGroup which we will add our desired child views to.
 
	<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cont_item_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

     <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/card_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        card_view:cardCornerRadius="4dp">

         <RelativeLayout
            android:id="@+id/cont_card_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#FFFFFF">

             <ImageView
                android:id="@+id/im_item_icon"
                android:layout_width="match_parent"
                android:layout_height="156dp"
                android:background="@color/colorPrimary"
                android:padding="36dp"
                android:src="@drawable/ic_tonality_black_36dp"
                android:tint="#FFFFFF"

                />

             <TextView
                android:id="@+id/lbl_item_sub_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_above="@+id/lbl_item_text"
                android:paddingLeft="16dp"
                android:textColor="@android:color/white"
                android:textSize="22sp"
                android:textStyle="bold" />

             <TextView
                android:id="@+id/lbl_item_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/im_item_icon"
                android:paddingLeft="16dp" />


             <Button
                android:id="@+id/btn_card_load"
                android:layout_width="wrap_content"
                android:layout_height="56dp"
                android:layout_below="@+id/lbl_item_text"
                android:background="@android:color/transparent"
                android:textColor="@color/colorAccent"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:paddingTop="8dp"
                android:paddingBottom="8dp"
                android:text="LOAD" />

         </RelativeLayout>

     </android.support.v7.widget.CardView>

 </LinearLayout>
 
3. Open up DerpAdapter.java. The most important change we need to make, is changing the resource in onCreateViewHolder() to use our Card layout instead of the ListView style layout. Other than that, we'll make a few patchwork fixes to our ViewHolder. As I mentioned before, we'll throw out those changes later since they will just get in the way of the next section.
 
	public class DerpAdapter extends RecyclerView.Adapter <DerpAdapter.DerpHolder>{

    private List <ListItem> listData;
    private LayoutInflater inflater;

    private ItemClickCallback itemClickCallback;

    public interface ItemClickCallback {
        void onItemClick(int p);
        void onSecondaryIconClick(int p);
    }

    public void setItemClickCallback(final ItemClickCallback itemClickCallback) {
        this.itemClickCallback = itemClickCallback;
    }

    public DerpAdapter(List <ListItem> listData, Context c){
        inflater = LayoutInflater.from(c);
        this.listData = listData;
    }

    @Override
    public DerpAdapter.DerpHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.card_item, parent, false);
        return new DerpHolder(view);
    }

    @Override
    public void onBindViewHolder(DerpHolder holder, int position) {
        ListItem item = listData.get(position);
        holder.title.setText(item.getTitle());
        holder.subTitle.setText(item.getSubTitle());
        /*if (item.isFavourite()){
            holder.secondaryIcon.setImageResource(R.drawable.ic_star_black_24dp);
        } else {
            holder.secondaryIcon.setImageResource(R.drawable.ic_star_border_black_24dp);
        }*/
    }

    public void setListData(ArrayList <ListItem> exerciseList) {
        this.listData.clear();
        this.listData.addAll(exerciseList);
    }

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

    class DerpHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        ImageView thumbnail;
        //ImageView secondaryIcon;
        TextView title;
        TextView subTitle;
        View container;
        Button load;

        public DerpHolder(View itemView) {
            super(itemView);
            thumbnail = (ImageView)itemView.findViewById(R.id.im_item_icon);
           // secondaryIcon = (ImageView)itemView.findViewById(R.id.im_item_icon_secondary);
           // secondaryIcon.setOnClickListener(this);
            subTitle = (TextView)itemView.findViewById(R.id.lbl_item_sub_title);
            title = (TextView)itemView.findViewById(R.id.lbl_item_text);
            container = (View)itemView.findViewById(R.id.cont_item_root);
            load = (Button)itemView.findViewById(R.id.btn_card_load);
            load.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.btn_card_load){
                itemClickCallback.onItemClick(getAdapterPosition());
            } else {
               // itemClickCallback.onSecondaryIconClick(getAdapterPosition());
            }
        }
    }
}
 
That about sums it up.

Part 5a - RecyclerView Animations: Add, Swipe, Delete, Move, Transition

Check out the video here.

For the last part of this tutorial, we'll be going through some basic methods and patterns for animating a RecyclerView. In particular, we'll be worrying about two types of animations:

  • Animating changes to the RecyclerView (i.e. moving, adding, deleting items)
  • Transition animations between Activities via RecyclerView
In 5(a), we'll be using a class called ItemTouchHelper, which will work in conjunction with RecyclerView.Adapter methods such as notifyItemMoved/Removed/Inserted(), to do the work of animating such operations. I'm happy to say that this part is quite simple, and requires surprisingly little configuration on our end. NOTE This tutorial begins where 3(b) leaves off, as far as the code is concerned. Also, please make the following correction in the onCreate() method of ListActivity.java: "adapter =  new DerpAdapter(listData, this);" instead of  "adapter =  new DerpAdapter(DerpData.getListData, this);" See the video if this isn't clear. 1. Open up DerpData.java. We're going to make a new Static method which simply creates a new ListItem (Quote and Attribution pair) randomly. Later on, we'll add a feature to the App which allows the user to add an item to the RecyclerView. Copy and paste the following snippet into the body of the DerpData.java class:
 
	public static ListItem getRandomListItem(){
        int rand = new Random().nextInt(6);

        ListItem item = new ListItem();

        item.setTitle(titles[rand]);
        item.setSubTitle(subTitles[rand]);

        return item;
    }
 
2. We're now going to add a Button to the bottom of our activity_list.xml layout. When the user presses this button, we'll have our DerpData.java class pass in a random new ListItem, in order to demonstrate Addition Animations.
 
	<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    android:orientation="vertical"
    tools:context=".ui.ListActivity">

     <android.support.v7.widget.RecyclerView
        android:id="@+id/rec_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#e8eaf6"
        />

     <Button
        android:id="@+id/btn_add_item"
        android:layout_width="match_parent"
        android:layout_height="72dp"
        android:background="@drawable/background_state_drawable"
        android:padding="16dp"
        android:text="Add Item"
        android:textColor="@android:color/white"
        android:textSize="18sp" />
 </LinearLayout>
 
3.  As I mentioned in the intro, most of the heavy lifting for animating RecyclerView changes will come via the ItemTouchHelper class. Open ListActivity.java and add the following snippet to the bottom of onCreate():
 
	ItemTouchHelper itemTouchHelper = new ItemTouchHelper(createHelperCallback());
        itemTouchHelper.attachToRecyclerView(recyclerView);
        
        Button addItem = (Button) findViewById(R.id.btn_add_item);
        addItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addItemToList();
            }
        });
 
At this point, you'll be seeing some red lines. You'll also notice that we've set up an OnClickListener() for the button we added in step 2. 4. In order to handle changes to the RecylcerView properly, we'll need to slap the following method in to our ListActivity.java. I've placed it below onCreate():
 
	private ItemTouchHelper.Callback createHelperCallback() {
        ItemTouchHelper.SimpleCallback simpleItemTouchCallback =
                new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                        ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {

                    @Override
                    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                                          RecyclerView.ViewHolder target) {
                        moveItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
                        return true;
                    }

                    @Override
                    public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {
                        deleteItem(viewHolder.getAdapterPosition());
                    }
                };
        return simpleItemTouchCallback;
    }
 
5. Almost finished; we just need to code those missing methods. I've put them below createHelperCallback():
 
	private void addItemToList() {
        ListItem item = DerpData.getRandomListItem();
        listData.add(item);
        adapter.notifyItemInserted(listData.indexOf(item));
    }

    private void moveItem(int oldPos, int newPos) {

        ListItem item = (ListItem) listData.get(oldPos);
        listData.remove(oldPos);
        listData.add(newPos, item);
        adapter.notifyItemMoved(oldPos, newPos);
    }

    private void deleteItem(final int position) {
        listData.remove(position);
        adapter.notifyItemRemoved(position);
    }
 
That's it. You should be able to Swipe, Move (try long clicking an Item), and Add items to the RecyclerView at this point.

Part 5b - RecyclerView Animations: Add, Swipe, Delete, Move, Transition

Check out the video here.

6. In order to perform the Animation between ListActivity.java and DetailActivity.java, we'll be using Shared Element Transitions. A Shared Element Transition is used when we wish Animate views which are common to more than one Activity/Fragment. Before we can use Shared Element Transitions, we'll need to change a few things. Shared Element Transitions are currently (as of summer 2016) only supported in Android API 21 and up. While this means that our Transitions will be ignored on API < 21, we can still set up our App to use the Transitions when the target device is API 21 or higher. To do this in AS, right click on the res/values directory, then New>Values resource file. Make the File name styles and in the Available qualifiers pane, select Version (at the bottom of the list in mine), ">>", then set the Platform API level to 21. Hit OK. If that doesn't work for some reason, simply use a File Explorer to create res/values-v21/styles.xml within your project. We'll also need to make a slight adjustment to our default styles.xml. styles.xml/styles.xml(v21):

 
	<!--v21 styles.xml:-->
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <style name="AppTheme" parent="AppTheme.Base">
         <item name="android:windowContentTransitions">true </item>
     </style>
 </resources>

 <!--Default styles.xml:-->
 <resources>

     <style name="AppTheme" parent="AppTheme.Base"> </style>

     <style name="AppTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="colorPrimary">@color/colorPrimary </item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark </item>
         <item name="colorAccent">@color/colorAccent </item>
     </style>

 </resources>
 
We'll also add a few new strings to strings.xml. We'll talk about what they do later:
<string name="transition_image">image</string>
<string name="transition_quote">quote</string>
<string name="transition_attribution">attribution</string>
7. We'll need to make some changes to our layout files. Our goal will be to animate the Icon from an individual list_item which was clicked on, to a large scale image in DetailActivity. The key here is to notice that each element which is shared between the two Activities/Layouts, will adopt a common "transitionName" attribute. This will be what tells our App which Views we intend to animate. I've also added an ImageView to activity_detail.xml, since we intend to animate an image to that Activity. activity_detail.xml:
 
	<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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.wiseassblog.derplist.ui.DetailActivity">

     <ImageView
        android:id="@+id/im_detail_image"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:src="@drawable/ic_tonality_black_36dp"
        android:transitionName="@string/transition_image" />

     <TextView
        android:id="@+id/lbl_quote_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/im_detail_image"
        android:fontFamily="sans-serif-medium"
        android:transitionName="@string/transition_quote" />

     <TextView
        android:id="@+id/lbl_quote_attribution"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbl_quote_text"
        android:fontFamily="sans-serif-light"
        android:textStyle="italic"
        android:transitionName="@string/transition_attribution" />

 </RelativeLayout>
 
Next, we'll make sure that list_item.xml implements our transitionName attribute. list_item.xml:
 
	<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cont_item_root"
    android:layout_width="match_parent"
    android:layout_height="72dp"
    android:background="@drawable/background_state_drawable"
    android:clickable="true"
    >

     <ImageView
        android:id="@+id/im_item_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:paddingLeft="16dp"
        android:transitionName="@string/transition_image"
        android:src="@drawable/ic_tonality_black_36dp" />

     <TextView
        android:transitionName="@string/transition_quote"
        android:id="@+id/lbl_item_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/im_item_icon"
        android:layout_marginLeft="72dp"
        android:layout_marginRight="48dp"
        android:ellipsize="end"
        android:fontFamily="sans-serif"
        android:singleLine="true"

        android:text="Sois comme l'eau mon ami"
        android:textColor="@android:color/black"
        android:textSize="16sp" />

     <TextView
        android:id="@+id/lbl_item_sub_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lbl_item_text"
        android:layout_marginLeft="72dp"
        android:layout_marginRight="48dp"
        android:ellipsize="end"
        android:fontFamily="sans-serif"
        android:singleLine="true"
        android:transitionName="@string/transition_attribution"
        android:text="Mononc' J"
        android:textSize="14sp" />

     <ImageView
        android:id="@+id/im_item_icon_secondary"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:clickable="true"
        android:padding="16dp"
        android:src="@drawable/ic_star_border_black_24dp"
        android:background="@drawable/background_state_drawable"
        />

 </RelativeLayout>
 
8.  Since our Animation will begin from a given list item in our RecyclerView, we'll need to change our RecyclerView's Adapter (DerpAdapter) to pass in an extra parameter, which will be the clicked View itself. Open up DerpAdapter  and make the following changes to the specified method and interface:
 
	//within DerpAdapter
public interface ItemClickCallback {
        void onItemClick(View v, int p);
        void onSecondaryIconClick(int p);
    }
    
//within DerpHolder
@Override
        public void onClick(View v) {
            if (v.getId() == R.id.cont_item_root){
                itemClickCallback.onItemClick(v, getAdapterPosition());
            } else {
                itemClickCallback.onSecondaryIconClick(getAdapterPosition());
            }
        }
 
We'll also fix up our DerpData.java class as I forgot to set an iconResId for each ListItem:
 
	public static List <ListItem> getListData() {
        List <ListItem> data = new ArrayList <>();

        //Repeat process 4 times, so that we have enough data to demonstrate a scrollable
        //RecyclerView
        for (int x = 0; x  < 4; x++) {
            //create ListItem with dummy data, then add them to our List
            for (int i = 0; i  < titles.length; i++) {
                ListItem item = new ListItem();
                item.setTitle(titles[i]);
                item.setSubTitle(subTitles[i]);
                item.setImageResId(icon);
                data.add(item);
            }
        }
        return data;
    }

    public static ListItem getRandomListItem(){
        int rand = new Random().nextInt(6);
        ListItem item = new ListItem();

        item.setTitle(titles[rand]);
        item.setTitle(subTitles[rand]);
        item.setImageResId(icon);

        return item;
    }
 
9. We'll finish up by fixing our Activities. In ListAcitivity.java, we'll need to modify our existing onItemClick() method to fire the transition if the device's API >=21.
 
	//Add this variable to the top of ListActivity.java and DetailActivity.java:
    private static final String EXTRA_IMAGE_RES_ID = "EXTRA_IMAGE_RES_ID";

//make the following changes to onItemClick() in ListActivity.java:
 @Override
    public void onItemClick(View v, int p) {
        ListItem item = (ListItem) listData.get(p);

        Intent i = new Intent(this, DetailActivity.class);

        Bundle extras = new Bundle();
        extras.putString(EXTRA_QUOTE, item.getTitle());
        extras.putString(EXTRA_ATTR, item.getSubTitle());
        extras.putInt(EXTRA_IMAGE_RES_ID, item.getImageResId());
        i.putExtra(BUNDLE_EXTRAS, extras);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setEnterTransition(new Fade(Fade.IN));
            getWindow().setExitTransition(new Fade(Fade.OUT));
            ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                    this,
                    new Pair <View, String>(v.findViewById(R.id.im_item_icon),
                            getString(R.string.transition_image)),
                    new Pair <View, String>(v.findViewById(R.id.lbl_item_text),
                            getString(R.string.transition_quote)),
                    new Pair <View, String>(v.findViewById(R.id.lbl_item_sub_title),
                            getString(R.string.transition_attribution))
            );

            ActivityCompat.startActivity(this, i, options.toBundle());
        } else {
            startActivity(i);
        }
    }
 
We'll also need to pass the clicked images' imageResId to the DetailAcitivity.java, so we'll add a new static key to both Activities. Check the Video if anything is confusing:
 
	 //make the following changes to onCreate() in DetailActivity.java:
	private static final String EXTRA_IMAGE_RES_ID = "EXTRA_IMAGE_RES_ID";

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

        Bundle extras = getIntent().getBundleExtra(BUNDLE_EXTRAS);

        ((TextView)findViewById(R.id.lbl_quote_text)).setText(extras.getString(EXTRA_QUOTE));
        ((TextView)findViewById(R.id.lbl_quote_attribution)).setText(extras.getString(EXTRA_ATTR));
        ((ImageView)findViewById(R.id.im_detail_image)).setImageDrawable(
                ContextCompat.getDrawable(this, extras.getInt(EXTRA_IMAGE_RES_ID)));
    }
 
That should be it; fire it up and see how it works.