Android RecyclerView – Simple List

RecyclerView

The replacement for ListView, GridView and a couple of other components. To gain good performance you need to implement the ViewHolder pattern unfortunately there is a lot of ways to mess that up. Now with the RecyclerView they forces you to implement the ViewHolder pattern and have made is more difficult to mess up.

You need a support lib to have backwards compatibility.

dependencies {

    ...

    compile "com.android.support:recyclerview-v7:21.0.0"
}

There is nothing special about adding a RecyclerView in your xml layout:

LayoutManager

In the ListView you only had to set the adapter now you have to set the adapter and a LayoutManager. There are three LayoutManager provided at the moment;
GridLayoutManager, StaggeredGridLayoutManager and LinearLayoutManager the last one is the one I’m going to use for this tutorial.

To set the LayoutManager just use:

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

The complete setup would look something like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(com.jayway.recyclerview.R.layout.activity_recycler_view_example);

    mRecyclerView = (RecyclerView) findViewById(com.jayway.recyclerview.R.id.recyclerview);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    mRecyclerView.setAdapter(new BasicListAdapter(this));

    ...

}

By default the LinearLayoutManager is vertical if you want it to be horizontal set

linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

RecyclerView.Adapter

Now to the interesting part, the adapter, this is where it differ the most from the ListView setup. First of all you need to extend the RecyclerView.Adapter in the simplest way there is three methods you need to override; onCreateViewHolder, onBindViewHolder and getItemCount.

public abstract class AbstractListAdapter extends RecyclerView.Adapter {

    protected List mData = new ArrayList();

    @Override
    public abstract K onCreateViewHolder(ViewGroup viewGroup, int viewType);

    @Override
    public abstract void onBindViewHolder(K k, int position);

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

    ...

}

In the onCreateViewHolder method you inflate the view based on the viewType. The onBindViewHolder method is where you set the data on the view. getItemCount is kind of self explained.

Notify

There is a lot of new ‘notify…’ methods for notify different changes to a single item or a range of items. Because of this new notify methods I think it is cleaner to split up my adapter in an abstract superclass and a subclass. In the superclass I add the data logic and the view inflation in the subclass.

The superclass I call AbstractListAdapter here I put all my data methods like; add, move, delete and swap. By using the new notify methods you get a nice animation out of the box. This will be used when changing the data set.

public abstract class AbstractListAdapter extends RecyclerView.Adapter {

    ...

    public void addEntity(int i, V entity) {
        mData.add(i, entity);
        notifyItemInserted(i);
    }

    public void deleteEntity(int i) {
        mData.remove(i);
        notifyItemRemoved(i);
    }

    public void moveEntity(int i, int loc) {
        move(mData, i, loc);
        notifyItemMoved(i, loc);
    }

    private void move(List data, int a, int b) {
        V temp = data.remove(a);
        data.add(b, temp);
    }
}

If we like to change the whole data set we start by removing all deleted items and then add all new and swapping the ones that have moved. This will give us a really nice animation.

public abstract class AbstractListAdapter extends RecyclerView.Adapter {

    ...

    public void setData(final List data) {
        // Remove all deleted items.
        for (int i = mData.size() - 1; i >= 0; --i) {
            if (getLocation(data, mData.get(i)) < 0) {
                deleteEntity(i);
            }
        }

        // Add and move items.
        for (int i = 0; i < data.size(); ++i) {
            V entity = data.get(i);
            int loc = getLocation(mData, entity);
            if (loc < 0) {
                addEntity(i, entity);
            } else if (loc != i) {
                moveEntity(i, loc);

            }
        }
    }

    private int getLocation(List data, V entity) {
        for (int j = 0; j < data.size(); ++j) {
            V newEntity = data.get(j);
            if (entity.equals(newEntity)) {
                return j;
            }
        }

        return -1;
    }

    ...

}

ViewHolder

The in the subclass we do all out view creations I prefer to use inflation. There really isn't that complicated. Instead of working with a view containing a ViewHolder we now have a ViewHolder containing a view. In onCreateViewHolder you create the ViewHolder based on the viewType.

public class BasicListAdapter extends AbstractListAdapter {

    ...

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        return new ViewHolder(
                mInflater.inflate(R.layout.section_item, viewGroup, false)
        );
    }

    ...

}

Make sure that you in the ViewHolder's constructor setup the different views otherwise you will get bad performance and add a bind method for setting the data.

public static class ViewHolder extends RecyclerView.ViewHolder {

    private final TextView mTextView;

    public ViewHolder(View v) {
        super(v);
        mTextView = (TextView) v.findViewById(R.id.label);
    }

    public void bind(Entity entity){
        mTextView.setText(entity.getTitle());
    }

    public TextView getTextView() {
        return mTextView;
    }

    @Override
    public String toString() {
        return "ViewHolder{" + mTextView.getText() + "}";
    }
}

... and in the onBindViewHolder you bind the data to the view through the ViewHolder.

public class BasicListAdapter extends AbstractListAdapter {

    ...

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        viewHolder.bind(mData.get(position));
    }

    ...

}

In this example I have called my data object for Entity and it's really important that you implement the equals and hashCode methods correctly to get the data methods in the AbstractListAdapter class to work correctly.

public class Entity {
    private final String mTitle;

    public Entity(String title) {
        mTitle = title;
    }

    public String getTitle() {
        return mTitle;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Entity entity = (Entity) o;

        if (mTitle != null ? !mTitle.equals(entity.mTitle) : entity.mTitle != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return mTitle != null ? mTitle.hashCode() : 0;
    }
}

OnItemClickListener

You can't put a listener on the RecyclerView instead you have to implement it your self and to make it easy I usually add it on the adapter. Start by creating an interface and adding it to the adapter:

public class BasicListAdapter extends AbstractListAdapter {

    ...

    private OnItemClickListener mOnItemClickListener;

    ... 

    public void setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }

    ...

    public static interface OnItemClickListener {
        public void onItemClick(Entity entity);
    }
}

Update the ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {

    ...

    private Entity mEntity;

    public ViewHolder(View v) {
        super(v);
        mTextView = (TextView) v.findViewById(R.id.label);
        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClick(mEntity);
                }
            }
        });
    }

    public void bind(Entity entity) {
        mEntity = entity;
        mTextView.setText(entity.getTitle());
    }

    ...

}

The nice thing about this is that if you have different views you can add different OnClickListener on the different views and for example have several methods in your interface like:

public static interface OnItemClickListener {
    public void onItemClick(Entity entity);
    public void onItemDelete(Entity entity);
    public void onItemEdit(Entity entity);
}

DividerItemDecoration

As you probably have noticed there is no item divider in the RecyclerView, there is however several ways of hocking in stuff to the RecyclerView one of this ways are an ItemDecoration.

"An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more."

In the SDK example code there is an example of how to do this: com.example.android.supportv7.widget.decorator.DividerItemDecoration just add it to the RecyclerView.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(com.jayway.recyclerview.R.layout.activity_recycler_view_example);

    ...

    mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));

    ...

}

So this is all you need to know to get started with the RecyclerView. There is an working example at: github

This Post Has 8 Comments

  1. Little error in the adapter. Your moveEntity method calls swap which is not the same thing (unless they are adjacent).
    This will eventually cause a crash since RV received wrong notification.

    1. Hi, thanks for the input, error due to coding to late :)

  2. s/LiveView/ListView/g

  3. Hi, thank you for a great article. Has the moveEntity method that yigit mentioned been corrected on this webpage?

    And has it been corrected at github source?

  4. Thank’s for good example!

  5. Hi
    Thanks for your great tutorial.
    I have two questions:
    1. What is mInflater,mAdapter,mRecycler etc. And How should I define each one?

    2. How can I use it in a fragment. For Example what should I use instead of “this”?

  6. Thanks for the good tutorial, but in the setData method you call
    moveEntity(i, loc);

    i think it should be called

    moveEntity(loc, i);

    loc is the current position of the array element, and i the new position.

Leave a Reply

Close Menu