Over a million developers have joined DZone.

A First Glance at Android’s RecyclerView

This post is giving you an introduction to the RecyclerView, its many internal classes and interfaces, how those interact and how you can use them.

· Mobile Zone

At this year’s Google IO, Google released a preview to the upcoming Android version. The so called L Developer Preview. That’s a very big improvement over previous releases and I really love that Google is doing that. I think we all benefit by this decision. As developers and as consumers alike!

Part of this preview are two new views: RecyclerView and CardView. This post is giving you an introduction to the RecyclerView, its many internal classes and interfaces, how those interact and how you can use them.

Let me start with the good news: RecyclerView is part of the support library. So you can use it right now. Ok: You can use it as soon as the final support lib accompanying the L release gets released. So better to familiarize yourself with it right away :-)

What’s it with this odd name? Why RecyclerView?

That’s how Google describes RecyclerView in the API documentation of the L preview release:

A flexible view for providing a limited window into a large data set.

So RecyclerView is the appropriate view to use when you have multiple items of the same type and when it’s very likely that a screen of your user’s device cannot present all of those items at once. Possible examples are contacts, customers, audio files and so on. The user has to scroll around to see more items and that’s when the recycling and reuse comes into play. As soon as a user scrolls a currently visible item out of view, this item can be recycled and reused whenever a new item comes into view.

The following screenshots of the sample app illustrate this. On the left is the sample app after the initial start. When you scroll the view up, some views become eligible for recycling. The red area on the right screenshot, for example, highlights two invisible views, that the Recycler can take care of.

These two screens show what recycling means. On the right two views are eligible for reuse

These two screens show what recycling means. On the right two views are eligible for reuse

Recycling of views is a very useful approach. It saves CPU resources in that you do not have to inflate new views all the time and it saves memory in that it doesn’t keep plenty of invisible views around.

Now, you might say: That’s nothing new. And you’re right! We had that withListView for a very long time. The concept of recycling views itself it not new. But while you previously had a ListView where the appearance, recycling and everything was tightly coupled, Google now follows a much better, a much more flexible approach with the new RecyclerView. I really like the approach Google has taken here!

RecyclerView doesn’t care about visuals

Here’s the thing: While with Listview we had tight coupling, Google now uses an approach where the RecyclerView itself doesn’t care about visuals at all. It doesn’t care about placing the elements at the right place, it doesn’t care about separating any items and not about the look of each individual item either. To exaggerate a bit: All RecyclerView does, is to recycle stuff. Hence the name.

Anything that has to do with layouting, drawing and so on, that is anything that has to do with how your data set is presented, is delegated to pluggable classes. That makes the new RecyclerView API extremely flexible. You want another layout, plug in another LayoutManager. You want different animations, plugin an ItemAnimator, and so on.

Here’s the list of the most important classes that RecyclerView makes use of to present the data:

The most important classes of the RecyclerView API

ClassUsage
AdapterWraps the data set and creates views for individual items
ViewHolderHolds all sub views that depend on the current item’s data
LayoutManagerPlaces items within the available area
ItemDecorationDraws decorations around or on top of each item’s view
ItemAnimatorAnimates items when they are added, removed or reordered

In the next paragraphs I’m briefly describing what each class or interface is about and how RecyclerView uses it. In future posts I’m going to revisit some of these classes, write about them in detail and show you how to customize them for your project’s need.

ViewHolder

ViewHolders are basically caches of your View objects. The Android team recommended for a very long time to use the ViewHolder pattern, but they never actually enforced the use of it. Now with the new Adapter you finally have to use this pattern.

It’s a bit weird that Google waited so long to enforce the usage of the ViewHolder pattern, but better late than never. If you do not know about the ViewHolder pattern, have a look at this Android training session. It uses the old Adapter, but the pattern itself hasn’t changed.

Also searching for ViewHolder should yield plenty of hits to further blog posts. For example this post by Antoine Merle about ListView optimizations.

One thing specific to any RecyclerView.ViewHolder subclass is, that you can always access the root view of your ViewHolder by accessing the public memberitemView. So there’s no need to store that within your ViewHolder subclass.

And should you decide to override toString() have a look at the base class. ItstoString() implementation prints some useful information you should consider to use for your log messages as well.

Here’s the code for the ViewHolder of the sample project – which is an inner class of the Adapter. It’s pretty straightforward:

public final static class ListItemViewHolder extends RecyclerView.ViewHolder {
   TextView label;
   TextView dateTime;

   public ListItemViewHolder(View itemView, int viewType) {
      super(itemView);
      label = (TextView) itemView.findViewById(R.id.txt_label_item);
      dateTime = (TextView) itemView.findViewById(R.id.txt_date_time);
   }
}

Adapter

Adapters fulfill two roles: They provide access to the underlying data set. And they are responsible for creating the correct layout for individual items. Adapters always were part of Android and were used in many places. ListView,AutoCompleteTextViewSpinner and more all made use of adapters. All those classes up to now inherited from AdapterView.

For the new RecyclerView Google has decided to replace the old Adapter interface and replace it with a new RecyclerView.Adapter base class. So say good bye to something like SimpleCursorAdapterArrayAdapter and the like. At least in their current incarnation.

Currently there is no default implementation available. Google might add some later on, but I wouldn’t bet on this. For Animations to work properly, cursors and arrays aren’t the best fit, so porting the current Adapter implementations might not make too much sense.

Since Adapter is abstract you have to implement these three methods:

  • public VH onCreateViewHolder(ViewGroup parent, int viewType)
  • public void onBindViewHolder(VH holder, int position)
  • public int getItemCount()

Thus the most basic adapter for the sample layout looks like this:

public class RecyclerViewDemoAdapter extends 
        RecyclerView.Adapter
        <RecyclerViewDemoAdapter.ListItemViewHolder> {

    private List<DemoModel> items;

    RecyclerViewDemoAdapter(List<DemoModel> modelData) {
        if (modelData == null) {
            throw new IllegalArgumentException(
                  "modelData must not be null");
        }
        this.items = modelData;
    }

    @Override
    public ListItemViewHolder onCreateViewHolder(
            ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_demo_01, 
                        viewGroup,
                        false);
        return new ListItemViewHolder(itemView, viewType);
    }

    @Override
    public void onBindViewHolder(
            ListItemViewHolder viewHolder, int position) {
        DemoModel model = items.get(position);
        viewHolder.label.setText(model.label);
        String dateStr = DateUtils.formatDateTime(
                viewHolder.label.getContext(),
                model.dateTime.getTime(),
                DateUtils.FORMAT_ABBREV_ALL);
        viewHolder.dateTime.setText(dateStr);
    }

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

    public final static class ListItemViewHolder 
           extends RecyclerView.ViewHolder {
        // ... shown above in the ViewHolder section
    }
}

LayoutManager

The LayoutManager is probably the most interesting part of the RecyclerView. This class is responsible for layouting all child views. There is one default implementation available: LinearLayoutManager which you can use for vertical as well as horizontal lists.

You have to set a LayoutManager for your RecyclerView otherwise you will see an exception at Runtime:

08-01 05:00:00.000  2453  2453 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView$LayoutManager.onMeasure(android.support.v7.widget.RecyclerView$Recycler, android.support.v7.widget.RecyclerView$State, int, int)' on a null object reference
08-01 05:00:00.000  2453  2453 E AndroidRuntime: at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:1310)

Only one method of LayoutManager is currently abstract:

  • public LayoutParams generateDefaultLayoutParams()

But there is another one where the code states that you should overrride it since it’s soon going to be abstract:

public void scrollToPosition(int position) {

  if (DEBUG) {
  Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");

   }

}

That’s very weird! Why not make it abstract right away? Anyway: Better you override this one to be on the safe side when Google releases the final version of L.

But only overriding those two methods won’t get you very far. After all theLayoutManager is responsible for positioning the items you want to display. Thus you have to override onLayoutChildren() as well.

This method also contains a log statement stating “You must override onLayoutChildren(Recycler recycler, State state)”. Ok, than make it abstract :-)Luckily there’s still plenty (?) of time to change that into a proper abstract method for the final L release. We all make mistakes. After all, my “Stupid stuff devs make” series is all about blunders that I made. So don’t get me wrong. No hard feelings here!

LinearlayoutManager

The LinearLayoutManager is currently the only default implementation ofLayoutManager. You can use this class to create either vertical or horizontal lists.

The implementation of LinearLayoutManager is rather complex and I only had a look at some key aspects, yet. I will return to this implementation in my post about custom LayoutManagers.

To use the LinearLayoutManager you simply have to instantiate it, tell it which kind of orientation to use and you are done:

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(currPos);
recyclerView.setLayoutManager(layoutManager);

LinearLayoutManager also offers some methods to find out about the first and last items currently on screen:

  • findFirstVisibleItemPosition()
  • findFirstCompletelyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • findLastCompletelyVisibleItemPosition()

Surprisingly these methods are not part of the source code in the SDK folder, but you can use them as they are part of the binaries. As I cannot imagine those being removed again, I’m sure you’ll find these in the final L release as well.

Some more methods help you to get the orientation of the layout or the current scroll state. Others will compute the scroll offset. And finally you can reverse the ordering of the items.

Since I’m going to write an extra post about LayoutManagers this should suffice for now.

ItemDecoration

With an ItemDecoration you can add an offset to each item and modify the item so that items are separated from each other, highlighted or, well, decorated.

You do not need to use an ItemDecoration. If, for example, you use a CardViewfor each item, there’s no need for an ItemDecoration.

On the other hand you can add as many ItemDecorations as you like. TheRecyclerView simply iterates over all ItemDecorations and calls the respective drawing methods for each of them in the order of the decoration chain.

The abstract base class contains these three methods:

  • public void onDraw(Canvas c, RecyclerView parent)
  • public void onDrawOver(Canvas c, RecyclerView parent)
  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

Anything you paint in onDraw() might be hidden by the content of the item views but anything that you paint in onDrawOver() is drawn on top of the items. If you simply create a bigger offset and, for example, use this offset to paint dividers, this of course is of no importance. But if you really want to add decorations, you have to use onDrawOver().

The LayoutManager calls the getItemOffset() method during the measurement phase to calculate the correct size of the item views. The parameter outRectmight look a bit odd at first. Why not use a return value instead? But it really makes a lot of sense, since this allows RecyclerView to reuse one Rect object for all children and thus save resources. Not necessarily nice — but efficient.

One thing I didn’t expect at first from the name of the class is that theonDraw()/onDrawOver() methods are not called for each item, but just once for every draw operation of the RecyclerView. You have to iterate yourself over all child views of the RecyclerView.

I will explain this in more detail in a follow-up post about writing your ownItemDecorations.

ItemAnimator

ItemAnimator is the class to help the RecyclerView with animating individual items. ItemAnimators deal with three kinds of events:

  • An item gets added to the data set
  • An item gets removed from the data set
  • An item moves as a result of one or more of the previous two operations

Luckily there exists a default implementation aptly named DefaultItemAnimator. If you do not set a custom ItemAnimatorRecyclerViewuses an instance of DefaultItemAnimator.

Obviously for animations to work, Android needs to know about changes to the dataset. For this Android needs the support of your Adapter. While you previously called notifyDataSetChanged() whenever changes had occured, this is no longer appropriate. This method triggers a complete redraw of all (visible) children at once without any animation. To see animations you have to use more specific methods.

There are plenty of notifyXyz() methods available. The two most specific are:

  • public final void notifyItemInserted(int position)
  • public final void notifyItemRemoved(int position)

The following video shows the result of an addition as well as of a removal of an item in the sample app:

00:00
00:10

A short video showing the default animations for the removal and addition of elements

Listeners

RecyclerView offers also some rather generic listeners. Once again you can safely forget everything you used to use up to now. There is no OnItemClickListener orOnItemLongClickListener. But you can use an OnItemTouchListener in combination with gesture detection to identify those events. A bit more work and more code to achieve the same result. I still hope for Google to add those Listeners in the final release. But whether those Listeners will be added is as an open question.

Combining all classes

You combine the classes either in your fragment or your activity. For the sake of simplicity my sample app uses activities only.

First of all here’s the layout file containing the RecyclerView:

<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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".RecyclerViewDemoActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        tools:listitem="@layout/item_demo_01"
        />

    <ImageButton
        android:id="@+id/fab_add"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_width="@dimen/fab_size"
        android:layout_height="@dimen/fab_size"
        android:layout_gravity="bottom|right"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:background="@drawable/ripple"
        android:stateListAnimator="@anim/anim"
        android:src="@drawable/ic_action_add"
        android:elevation="1dp"
        />
</RelativeLayout>

As you can see, nothing special here. You do not define the orientation or stuff like that on the RecyclerView. Actually RecyclerView itself makes no use of the attributes, it passes them on to the parent (which is ViewGroup) and that’s it.

There is one place where an AttributeSet is used and that is in thegenerateLayoutParams() method:

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   if (mLayout == null) {
      throw new IllegalStateException("RecyclerView has no LayoutManager");
   }
   return mLayout.generateLayoutParams(getContext(), attrs);
}

In this snippet the RecyclerView passes the AttributeSet on to theLayoutManager.

The Java code is also pretty simple:

setContentView(R.layout.activity_recyclerview_demo);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(0);
recyclerView.setLayoutManager(layoutManager);

// allows for optimizations:
recyclerView.setHasFixedSize(true);

// For the sake of simplicity I misused the Application subclass as a DAO
List<DemoModel> items = RecyclerViewDemoApp.getDemoData();
adapter = new RecyclerViewDemoAdapter(items);
recyclerView.setAdapter(adapter);

RecyclerView.ItemDecoration itemDecoration =
        new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);

// that's the default, you can of course use a custom ItemAnimator, if you like
recyclerView.setItemAnimator(new DefaultItemAnimator());

// onClickDetection in done in this Activity's OnItemTouchListener
// with the help of a GestureDetector;
// Tip by Ian Lake on G+ in a comment to this post:
// https://plus.google.com/+LucasRocha/posts/37U8GWtYxDE
recyclerView.addOnItemTouchListener(this);
gesturedetector =
        new GestureDetectorCompat(this, new RecyclerViewDemoOnGestureListener());

Connecting all those elements together roughly consists of these steps:

  1. Get a reference to your RecyclerView
  2. Create a LayoutManager and add it
  3. Create an Adapter and add it
  4. Create ItemDecorations as needed and add them
  5. Create an ItemAnimator as needed and add it
  6. Create listeners as needed and add them

All in all about 30 lines of code.

Now of course this is misleading. That’s only the glue code. The really interesting stuff is in the many inner classes of RecyclerView which you can subclass and tweak to your needs. That’s where the real work is done.

But the separation of concerns Google created helps you stick to one task within one implementation and it should make reuse easier to achieve. That’s why I likeRecyclerView and its ecosystem. I’m not afraid to criticize big G, but that’s well done, Google!

Is that the final API?

Of course I do not know if the concrete implementations, that the preview contains, will be in the final L release. But I guess so. And I expect some additions as well as minor changes to the API, based on bug reports or developer feedback.

Google itself gives one hint in the current API documentation about more stuff to come. The documentation of LayoutManager contains this nugget:

Several stock layout managers are provided for general use.

So we can expect more LayoutManagers. Which, of course, is good. Furthermore I expect at least one default ItemDecoration as well. After all the sample project of the support library contains a DividerItemDecoration, which works well with theLinearLayoutManager.

I’m more skeptical about adapters. While an ArrayAdapter (or better yet,ListAdapter) is very well possible, I am more doubtful about a CursorAdaptersince cursors do not lend themself easily to the new addition and removal notifications within the Adapter.

Gradle integration

To use RecyclerView you have to add it to your gradle file. Adding the support library alone is not enough:

dependencies {
   //...
   compile 'com.android.support:recyclerview-v7:+'
   //...

}

Lucas Rocha’s TwoWayView to simplify your life

I strongly recommend to have a look at Lucas Rocha’s TwoWayView project. He has updated his project to work with RecyclerView and has done a great deal to make using RecyclerView a lot easier. For many use cases the default layouts he provides should suffice. And he also provides support for custom LayoutManagers. Which are simpler to write using his framework than with the base RecyclerView.

Take a look at his project and check out if it covers all you need. Using it helps you get rid of some of RecyclerView‘s complexity.

For more information about his project see his blog post about how TwoWayView extends and simplifies RecyclerView.

To learn about news about this project follow Lucas Rocha on Google plus orTwitter.

I will cover TwoWayView in this series as well – so stay tuned :-)

Report bugs!

We currently have a developer preview. The first time Google did that for Android. Really nice. But of course this preview is not free of bugs. To help us all get a more stable final release, give feedback or issue bug reports, if you encounter anything that bothers you or is a bug. There is a special L preview issue tracker.

And that’s it for today

I started this post as part of the preparation for my talk at the July meetup of theDutch Android User Group.

I had much fun presenting about this topic at the July meetup of the Dutch Android User Group. And I had fun digging into this topic – and still have. Reading the source of RecyclerView and its many inner classes is really interesting. Thanks to the organizers for giving me the opportunity to speak about this topic and for forcing me to dig into this stuff :-)

At Utrecht I had 15-20 minutes for my talk. How I managed to get it done in time is still a mystery to me. As you can see there’s a lot to talk about – and this post is only the beginning.

Until next time!

Topics:
java ,mobile ,android

Published at DZone with permission of Wolfram Rittmeyer, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}