How It Began
I do programming for many years, but mainly C and Python. Recently I had the chance to create an Android prototype for which I reused parts of an existing app using RecyclerView. With almost no Java and Android experience this turned out to be quite a challenge. I had some struggle understanding how it work in order to turn the very simple examples to a more complex use-case. Therefore I wrote this tutorial that starts with the simple part and shows an example how to extend and highlight some worth to mention things of RecyclerView. For some deep dive into the RecyclerView I suggest the article by Naveen Goyal I linked at the bottom.
RecyclerView - What's It About
When you read about RecyclerView
you find that it is supposed to be more efficient, is flexible and has some build-in animations that help your app to look more professional.
As the name suggests a View is something that can be displayed on the screen. That does not mean that it actually is displayed. As the name RecyclerView
suggests it is a View
itself. So it is a container View
that handles displaying other Views
. What's special about it, that it is optimized to display views that share a common layout, but have different data to be filled with. E.g. this can be an image with a frame around or card-like boxes that display address data. In the following I will talk of data-set when I mean the subset of data that is used for a specific view. (e.g. the view could be a colored box with some image inside and the data-set the color of the frame and the actual image to be displayed) The combination of view and it's variable content is called item.
Note: Though it is one of the strengths of RecyclerView
, to scroll-display things that have a similar layout but different content, It's not mandatory to use the exact same layout for the views. It is possible to use completely different ones as well to adapt and extend similar layouts individually. In other words "data" is not limited to numbers and strings but can also be layout changes or even new views within the view.
Set It Up
ViewAdapter
To get started we need our own extension of RecyclerView.ViewAdapter
. This is something like the root manager of your views and data-sets.
Here's an empty template to start with:
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class myViewAdapterExample extends RecyclerView.Adapter {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
RecyclerView
has some basic requirements to your data. The first and very important one is that you'r scroll-able list of items will be enumerated consecutively starting with 0. This is the position parameter you will be given from RecyclerView
when it calls your onBindViewHolder
. So it's your part to tell RecyclerView
how many items there are overall and in case you want to remove some or add additional ones. (e.g. you have a database of 100 addresses, then return 100 in getItemCount()
.)
@Override
public int getItemCount() {
return 100;
}
ViewHolder
When your RecyclerView
starts to display data it will start picking up items from you until the screen and somewhat beyond is filled. It will do so starting with position 0. But before the onBindViewHolder
is called it will ask you to supply a ViewHolder
. There is not much you need to add to the ViewHolder
supplied by RecyclerView.ViewHolder
class but supply your View to it.
If you have the same layout for all items to display it's fairly simple, then there is only one type of ViewHolder
you need to supply. Even with different types of ViewHolder
You can do all implementation in your main adapter. Since this can get quite extensive if you deal with different layouts, I prefer to create separate ones:
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.octopus.recyclerviewexample.R;
public class MyBasicViewHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public MyBasicViewHolder(@NonNull ViewGroup parent) {
//super will set the result from inflater to itemView of the super class
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_card_layout, parent, false));
mTitle = itemView.findViewById(R.id.card_title);
}
}
I seems good practice that those sub-views who's data we want to change are assigned to a variable of that view, like done here with mTitle
.
To get the example running we will create two simple layout files and add it to the projects /res/layout
folder. The first containing the RecyclerView
and second the layout that will be used repeated times for each item.
activity_cart.xml
, that only contains the RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_card_activity"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
my_card_layout.xml
, having only a single text line.
<?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:orientation="vertical">
<TextView
android:id="@+id/card_title"
tools:text="Title"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
</LinearLayout>
Before we can use our Adapter we need to extend the onCreateViewHolder
public MyBasicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyBasicViewHolder(parent);
}
By default viewType is always 0 and which is fine at this point, since we only have a single layout we want to display. Later on I will show how to extend this to support different ones.
To get the machine running and actually display something some preparation calls are needed from your Activity
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.octopus.recyclerviewexample.R;
...
//in the onCreate of the activity
//set the layout that contains the RecyclerView
setContentView(R.layout.activity_card);
// set up the RecyclerView, rv_card_activity is the name we gave it in the activity_card.xml
RecyclerView recyclerView = findViewById(R.id.rv_card_activity);
//The Layout Manager determines how the items will be displayed
//There are predefined ones available, e.g. the LinearLayoutManager
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyViewAdapterExample());
If you run this now (and you didn't forget to let getItemCount()
return a value x > 0) this will create a scroll-able list of items, that all state 'Title'. This quite a big effort to display a simple list but once it is set up properly it performs well also for huge data sets.
But before we continue let's see what RecyclerView
does. When the activity is started RecyclerView asks our adpater how many items there are. So we tell him e.g. 100. Then it will start requesting the ViewHolders for the first item by calling onCreateViewHolder
and right afterwards onBindViewHolder(RecyclerView.ViewHolder holder, int position)
to get it's data updated (which we don't since it's still empty in our example). With this call it supplies us the position
so we can determine which of the 100 items we told him that we have, it is talking about. The ViewHolder
we get is just the one we returned on onCreateViewHolder
. Then it will do the same for the next View that is on position 2. So what's this fuss about, since so far we could have done this without a fancy RecyclerView
thing.
The Recycler Magic starts to happen once the visible screen of the phone is filled with our item. Then it will stop requesting further ViewHolders until we start scrolling down the list. Once we do this it might request some more, but it might as well reuse some we supplied previously. (Remember that by default RecyclerView
assumes that all ViewHolders are of the same type, so the ViewHolders are exchangable and only differ in the data that was given to them in the onBindViewHolder
). Reuse means that a ViewHolder
Object we created earlier and that is not visible on the screen anymore is given to us as holder
in a onBindViewHolder
call, but for another position, aka another data-set.
Bind to Adapter
Before extending this with different views let's fill in some data:
public class myViewAdapterExample extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyBasicViewHolder(parent);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MyBasicViewHolder mbvHolder = (MyBasicViewHolder) holder;
mbvHolder.mTitle.setText(Integer.toString(position));
mbvHolder.itemView.setVisibility(View.VISIBLE);
}
@Override
public int getItemCount() {
return 100;
}
}
What we got so far: A list of 100 items that is dynamically filled with data and only uses only about as many ViewHolders as there is needed to display the data on the screen.
Different ViewTypes
Not exiting enough yet? Ok here it is: different ViewTypes!
Before I mentioned that RecyclerView
by default assumes a single ViewType. Therefore if we have different ones, we need to tell it. This is done by overriding int getItemViewType(int position)
. RecyclerView
will call this before it requests or binds a ViewHolder
from us and what it does is, ask us "Hey what viewType will we use for position x?". You can choose any int values you like to identify your different view-types. RecyclerView
will then ask you to supply a new ViewHolder
of this type or, in case it has one already that is available for Recycling supplies this directly to the onBindViewHolder
. You can even create different types for each position, though it will force RecylerView
to only reuse ViewHolders for a particular position and therefore might be a performance issue at some point.
To show how another layout can be used, I created this my_other_card_layout.xml
and MyOtherViewHolder
.
<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:orientation="vertical">
<TextView
android:id="@+id/card_title"
tools:text="Title"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
<TextView
android:id="@+id/card_content"
tools:text="Additional Content"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
</LinearLayout>
public class MyOtherViewHolder extends RecyclerView.ViewHolder {
TextView mContent;
TextView mTitle;
public MyOtherViewHolder(@NonNull ViewGroup parent) {
//super will set the result from inflater to itemView of the super class
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_other_card_layout, parent, false));
mTitle = itemView.findViewById(R.id.card_title);
mContent = itemView.findViewById(R.id.card_content);
}
}
MultiView Adapter Accomplished
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class myViewAdapterExample extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//to avoid juggling with magic int nor upgly @Intdef constructions I
//use java enum as a set of consecutively numbered integers
enum ViewTypeEnum {BASIC_VIEW, OTHER_VIEW}
static final ViewTypeEnum[] ordinalToViewTypeEnum = ViewTypeEnum.values();
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewTypeEnum viewTypeEnum = ordinalToViewTypeEnum[viewType];
RecyclerView.ViewHolder viewHolder;
switch(viewTypeEnum){
case BASIC_VIEW:
viewHolder = new MyBasicViewHolder(parent);
break;
case OTHER_VIEW:
viewHolder = new MyOtherViewHolder(parent);
break;
default:
throw new IllegalStateException("Unexpected value: " + viewTypeEnum);
}
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ViewTypeEnum viewTypeEnum = ordinalToViewTypeEnum[getItemViewType(position)];
switch(viewTypeEnum){
case BASIC_VIEW:
//fill basic view with data of it's position
MyBasicViewHolder mbvHolder = (MyBasicViewHolder) holder;
mbvHolder.mTitle.setText(Integer.toString(position));
break;
case OTHER_VIEW:
//fill other view with data of it's position
MyOtherViewHolder movHolder = (MyOtherViewHolder) holder;
movHolder.mTitle.setText(Integer.toString(position));
movHolder.mContent.setText(Integer.toString(position));
break;
default:
throw new IllegalStateException("Unexpected value: " + viewTypeEnum);
}
holder.itemView.setVisibility(View.VISIBLE);
}
@Override
public int getItemCount() {
return 100;
}
@Override
public int getItemViewType(int position){
if(position%2 == 0) {
return ViewTypeEnum.BASIC_VIEW.ordinal();
}
return ViewTypeEnum.OTHER_VIEW.ordinal();
}
}
My Ahas
RecyclerView
might request a new ViewHolders
for a position any time, even when the item is still visible on the screen. Consequently there might actually be two (or more?) ViewHolder
instances around within RecyclerView
for the exact same item at the exact same position. In the Deep Dive Link below there is some background why this occurs. Don't keep instances of them by yourself. Don't do anything in the bind that assumes there is only a single instance of ViewHolder around for this position.
Anything can be done onBindViewHolder
except creating the ViewHolder
instance itself. There is no hard restriction that only changing data is handled in onBindViewHolder
call. Anything can as well be done on onBindViewHolder
, but it typically will be called much more often than create and therefore might impact your apps performance.
Links
If you are looking for some more tutorials on RecyclerView I suggest you check out these:
RecyclerView Tutorial With Example In Android Studio Deep Dive Into the RecyclerView. Introduction | by Naveen Goyal