+++ template = "article.html" title = "RecyclerView basics" date = 2015-01-18T19:37:00+01:00 description = "An introduction to RecyclerView in Android, covering adapters, view holders, and layout managers for efficient list displays." [taxonomies] tags = ["android"] +++ ## Introduction to RecyclerView `RecyclerView` has been introduced with Android 5, in the support-v7 package. It allows to display a collection of items in an arbitrary disposition (think of a `ListView`, but much more flexible). As the name of the support package indicates, it's available from the API level 7 (Android 2.1). Its name comes from the way it works: when an item is hidden, instead of being destroyed and a new one being created for each newly displayed item, hidden ones are _recycled_: they are reused, with new data bound on them. A `RecyclerView` is split into 6 main components: - an `Adapter`, providing data (similar to `ListView`'s) - an `ItemAnimator`, responsible on animations to play when items are modified, added, removed or moved - an `ItemDecoration`, which can add drawings or change the layout of an item (e.g. adding dividers) - a `LayoutManager`, which specifies how items are laid out (grid, list…) - a `ViewHolder`, the base class for each item's view - the `RecyclerView` itself, binding everything together Default implementations are bundled in the support-v7 package for some of these components. You have an `ItemAnimator` and three `LayoutManager`s (a linear one, similar to a `ListView`, a static grid, and a staggered grid). The `RecyclerView` doesn't need to be modified, and the `ItemDecoration` is optional. This leaves us the `Adapter` and the `ViewHolder`. ## Display a RecyclerView ### 1. Prepare your project To use a `RecyclerView`, you need a specific module of the support-v7 package. If you use Gradle, add to your dependencies: `compile 'com.android.support:recyclerview-v7:21.0.3'` This post will also use `CardView`s, so we reference them too: `compile 'com.android.support:cardview-v7:21.0.3'` That's it! ### 2. The base item We'll write a very simple list, containing items with a title and a subtitle. {{ filename(body="Item.java") }} ```java public class Item { private String title; private String subtitle; Item(String title, String subtitle) { this.title = "title;" this.subtitle = "subtitle;" } public String getTitle() { return title; } public String getSubtitle() { return subtitle; } } ``` ### 3. Item layout As mentionned earlier, our items will be displayed on a `CardView`. A `CardView` is just a `FrameLayout` with some decorations, hence having two `TextView`s to display is pretty simple: {{ filename(body="layout/item.xml") }} ```xml ``` ### 4. The adapter The first step is to define our `ViewHolder` class. It must extend `RecyclerView.ViewHolder`, and should store references to the views you'll need when binding your data on the holder. Here, we have our two `TextView`s: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { @SuppressWarnings("unused") private static final String TAG = Adapter.class.getSimpleName(); public static class ViewHolder extends RecyclerView.ViewHolder { TextView title; TextView subtitle; public ViewHolder(View itemView) { super(itemView); title = "(TextView) itemView.findViewById(R.id.title);" subtitle = "(TextView) itemView.findViewById(R.id.subtitle);" } } } ``` Now, what's the simplest way to store a collection of objects? Well, a `Collection`. Sometimes, even Java gets it right. For the simplicity of this example, we'll store our items in an `ArrayList` in our `Adapter`: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { @SuppressWarnings("unused") private static final String TAG = Adapter.class.getSimpleName(); private static final int ITEM_COUNT = 50; private List items; public Adapter() { super(); // Create some items items = new ArrayList<>(); for (int i = 0; i < ITEM_COUNT; ++i) { items.add(new Item("Item " + i, "This is the item number " + i)); } } // ViewHolder definition omitted } ``` Then we should implement the actual `RecyclerView.Adapter` methods: - `onCreateViewHolder(ViewGroup parent, int viewType)` should create the view, and return a matching `ViewHolder`, - `onBindViewHolder(ViewHolder holder, int position)` should fill the `ViewHolder` with data from item at position `position`, - `getItemCount()` should give the number of elements in the `Adapter` underlying data collection. The implementation of these methods is pretty straightforward in our case: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // Attributes and constructor omitted @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(ViewHolder holder, int position) { final Item item = items.get(position); holder.title.setText(item.getTitle()); holder.subtitle.setText(item.getSubtitle()); } @Override public int getItemCount() { return items.size(); } // ViewHolder definition omitted } ``` ### 5. Bind everything together We defined everything we needed. Now, it's time to give everything to a `RecyclerView`, and watch the magic happen! First step, add a `RecyclerView` to an `Activity`: {{ filename(body="layout/activity_main.xml") }} ```xml ``` We will use the simplest layout manager for now: `LinearLayoutManager`. We will also use the `DefaultItemAnimator`. {{ filename(body="MainActivity.java") }} ```java public class MainActivity extends ActionBarActivity { @SuppressWarnings("unused") private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setAdapter(new Adapter()); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new LinearLayoutManager(this)); } } ``` Compile, run, and… {{ img(src="/images/articles/recyclerview-basics/recyclerview-1.png", caption="Our first RecyclerView") }} Now that we have a basic `RecyclerView` displayed, let's see what we can do with it. ## Different kind of views on the same RecyclerView Let's say you have two types of items you want to display. For example, you display a remote music collection, and only some albums are available offline. You can do specific actions on them, and display some specific information too. For our example, we will add an `active` property to our items. {{ filename(body="Item.java") }} ```java public class Item { private String title; private String subtitle; private boolean active; Item(String title, String subtitle, boolean active) { this.title = "title;" this.subtitle = "subtitle;" this.active = active; } public String getTitle() { return title; } public String getSubtitle() { return subtitle; } public boolean isActive() { return active; } } ``` We change our items creation to have some active ones, and change the subtitle to add an active/inactive indication: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // Attributes omitted public Adapter() { super(); // Create some items Random random = new Random(); items = new ArrayList<>(); for (int i = 0; i < ITEM_COUNT; ++i) { items.add(new Item("Item " + i, "This is the item number " + i, random.nextBoolean())); } } // onCreateViewHolder omitted @Override public void onBindViewHolder(ViewHolder holder, int position) { final Item item = items.get(position); holder.title.setText(item.getTitle()); holder.subtitle.setText(item.getSubtitle() + ", which is " + (item.isActive() ? "active" : "inactive")); } // … } ``` Displaying a different string is a good start, but we need more. When we were writing the adapter, you may have noticed an argument that we didn't use in `onCreateViewHolder(ViewGroup parent, int viewType)`. This `viewType` is here to achieve exactly what we need: alter the `ViewHolder` creation. We must tell the `Adapter` how to determine the type of an item. We do this by overriding a new method, `getItemViewType(int position)`: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // … private static final int TYPE_INACTIVE = 0; private static final int TYPE_ACTIVE = 1; // … @Override public int getItemViewType(int position) { final Item item = items.get(position); return item.isActive() ? TYPE_ACTIVE : TYPE_INACTIVE; } // … } ``` Now, you have multiple possibilities depending on your needs: create a different `ViewHolder` for each view type, inflate a different layout but use the same `ViewHolder`… To keep things simple here, we will use the same `ViewHolder`, but a different layout. We will keep using the present layout for inactive items, and create a new for active ones: {{ filename(body="layout/item_active.xml") }} ```xml ``` Last but not least: we have to inflate a different layout depending on `viewType` in `onCreateViewHolder`: {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // … @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final int layout = viewType == TYPE_INACTIVE ? R.layout.item : R.layout.item_active; View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); return new ViewHolder(v); } // … } ``` Now, we can distinguish active items from inactive ones: {{ img(src="/images/articles/recyclerview-basics/recyclerview-active.png", caption="Displaying different layouts in the same RecyclerView") }} ## Layout managers ### LinearLayoutManager This is the one we used. This manager replicates the `ListView` behaviour. It takes up to three parameters: a `Context` (mandatory), an orientation (vertical, which is the default, or horizontal), and a `boolean` allowing to reverse the layout. This is what happens with a reversed, horizontal linear layout manager: {{ img(src="/images/articles/recyclerview-basics/recyclerview-linear-horizontal-reversed.png", caption="LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)") }} Note that when `reverseLayout` is set to `true`, the view is automaticaly displayed at the end without the need for scrolling. ### GridLayoutManager This one is similar to the `GridView`. It takes up to four parameters: a `Context` (mandatory), a span count (mandatory), an orientation (vertical, which is the default too, or horizontal), and a `reverseLayout` option. Here's a `GridLayoutManager` with a span count set to 3, vertical orientation, not reversed: {{ img(src="/images/articles/recyclerview-basics/recyclerview-grid-3-vertical.png", caption="GridLayoutManager(this, 3)") }} Note that the `reverseLayout` can be surprising when working with a grid. It reverses the layout in the direction you gave it, but not on the other one. With a vertical orientation, the items are reversed in vertical order, but not in horizontal: {{ img(src="/images/articles/recyclerview-basics/recyclerview-grid-3-vertical-reversed.png", caption="GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, true)") }} ### StaggeredGridLayoutManager The `StaggeredGridLayoutManager` is a `GridLayoutManager` on steroids. And as steroids may have bad effects on your body, they have bad effects on the `StaggeredGridLayoutManager`, which at the time of writing of this post (support-v7 21.0.0.3), has some pretty [annoying](https://code.google.com/p/android/issues/detail?id=93156) [bugs](https://code.google.com/p/android/issues/detail?id=93711). It seems that this layout manager was finished in a hurry, and isn't in par with the other ones. We can note this in its parameters: it doesn't need a `Context`, but the orientation is mandatory. It also needs a span count, like the `GridLayoutManager`. The code allows to reverse it, but there's no parameter in the constructor to do this. This layout is a grid, with a fixed span. However, we can have items spanning on the whole line or column. Let's see how it works. Using our active/inactive items from earlier, we'll make active items fully spanning. This is done in the `Adapter`, when binding an item. {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // … @Override public void onBindViewHolder(ViewHolder holder, int position) { final Item item = items.get(position); holder.title.setText(item.getTitle()); holder.subtitle.setText(item.getSubtitle() + ", which is " + (item.isActive() ? "active" : "inactive")); // Span the item if active final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams) lp; sglp.setFullSpan(item.isActive()); holder.itemView.setLayoutParams(sglp); } } // … } ``` Here, we must check if the layout manager is a `StaggeredGridLayoutManager` (line 13). If it's the case, we can modify the layout params accordingly. The mandatory screenshot: {{ img(src="/images/articles/recyclerview-basics/recyclerview-staggered-grid.png", caption="StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)") }} ## Respond to clicks on items The `RecyclerView` doesn't provide any high-level API to handle item clicks as the `ListView` does. However, it's still pretty simple to achieve. Let's think about it: we want to listen to click and long-click events on each item. Each item is represented by a `ViewHolder`. Each `ViewHolder` is initialized from its root `View`. Well, that's perfect: `View` as callbacks for click and long-click events. The last thing we need is mapping each `ViewHolder` to its position. `RecyclerView.ViewHolder` does all the work for us: the method `getPosition()` returns the position of the currently bound item. {{ filename(body="Adapter.java") }} ```java public class Adapter extends RecyclerView.Adapter { // … public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { @SuppressWarnings("unused") private static final String TAG = ViewHolder.class.getSimpleName(); TextView title; TextView subtitle; public ViewHolder(View itemView) { super(itemView); title = "(TextView) itemView.findViewById(R.id.title);" subtitle = "(TextView) itemView.findViewById(R.id.subtitle);" itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } @Override public void onClick(View v) { Log.d(TAG, "Item clicked at position " + getPosition()); } @Override public boolean onLongClick(View v) { Log.d(TAG, "Item long-clicked at position " + getPosition()); return true; } } } ``` ## Selection handling A frequent pattern when long-clicking on a list item is to trigger a selection mode. Once again, the `RecyclerView` doesn't help us with this, but it's pretty simple to do. It can be split in three steps: - maintain a selection state, - update the view of the selected items, - start the selection mode. To illustrate this part, we will add a way to select items, then remove this selection. ### 1. Selection state We need to modify our `Adapter` to keep a list of selected elements. Here's a list of what the `Adapter` has to provide: - list of selected elements, - change selection state of a given element. We can add bonus methods: - check if a specific element is selected, - clear the whole selection, - give the number of selected elements. I didn't chose these methods randomly, we will need them for the next parts. We can notice one thing with these five methods: none of them is `Item`-specific. We can write them in a generic way, and reuse our `Adapter` behaviour. Once we got all of this prepared, the code is pretty simple: {{ filename(body="SelectableAdapter.java") }} ```java public abstract class SelectableAdapter extends RecyclerView.Adapter { @SuppressWarnings("unused") private static final String TAG = SelectableAdapter.class.getSimpleName(); private SparseBooleanArray selectedItems; public SelectableAdapter() { selectedItems = new SparseBooleanArray(); } /** * Indicates if the item at position position is selected * @param position Position of the item to check * @return true if the item is selected, false otherwise */ public boolean isSelected(int position) { return getSelectedItems().contains(position); } /** * Toggle the selection status of the item at a given position * @param position Position of the item to toggle the selection status for */ public void toggleSelection(int position) { if (selectedItems.get(position, false)) { selectedItems.delete(position); } else { selectedItems.put(position, true); } notifyItemChanged(position); } /** * Clear the selection status for all items */ public void clearSelection() { List selection = getSelectedItems(); selectedItems.clear(); for (Integer i : selection) { notifyItemChanged(i); } } /** * Count the selected items * @return Selected items count */ public int getSelectedItemCount() { return selectedItems.size(); } /** * Indicates the list of selected items * @return List of selected items ids */ public List getSelectedItems() { List items = new ArrayList<>(selectedItems.size()); for (int i = 0; i < selectedItems.size(); ++i) { items.add(selectedItems.keyAt(i)); } return items; } } ``` Last change needed: our `Adapter` must extend `SelectableAdapter`. Its code doesn't change: {{ filename(body="Adapter.java") }} ```java public class Adapter extends SelectableAdapter { // … } ``` ### 2. Update the item views To notify the user that an item is selected, we often see a colored overlay on the selected views. That's what we'll do. On both `item.xml` and `item_active.xml`, we add an invisible, colored `View`. As this `View` should fill the whole `CardView` space, we need to make some change in the layout (move the padding to the inner `LinearLayout` instead of the `CardView`). The color should be transparent. We can also add a nice touch feedback using the framework's `selectableItemBackground` as a foreground on the `CardView`. On Android 5, this background displays a ripple, and a simple grey overlay on previous Android versions. {{ filename(body="item.xml (same changes go for item_active.xml)") }} ```xml ``` The next step is to decide when to display this overlay. The right place to do it seems pretty obvious: `Adapter`'s `onBindViewHolder()`. We also need to add a reference to the overlay in the`ViewHolder`. {{ filename(body="Adapter.java") }} ```java public class Adapter extends SelectableAdapter { // … @Override public void onBindViewHolder(ViewHolder holder, int position) { final Item item = items.get(position); holder.title.setText(item.getTitle()); holder.subtitle.setText(item.getSubtitle() + ", which is " + (item.isActive() ? "active" : "inactive")); // Span the item if active final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams) lp; sglp.setFullSpan(item.isActive()); holder.itemView.setLayoutParams(sglp); } // Highlight the item if it's selected holder.selectedOverlay.setVisibility(isSelected(position) ? View.VISIBLE : View.INVISIBLE); } // … public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { @SuppressWarnings("unused") private static final String TAG = ViewHolder.class.getSimpleName(); TextView title; TextView subtitle; View selectedOverlay; public ViewHolder(View itemView) { super(itemView); title = "(TextView) itemView.findViewById(R.id.title);" subtitle = "(TextView) itemView.findViewById(R.id.subtitle);" selectedOverlay = itemView.findViewById(R.id.selected_overlay); itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } // … } } ``` ### 3. Start the selection mode This last step will be a little more complex, but nothing really hard. First, we need to route click and long-click events back to our `Activity`. To achieve this, our `ViewHolder`s will expose a listener. We will pass it through the `Adapter`: {{ filename(body="Adapter.java") }} ```java public class Adapter extends SelectableAdapter { // … private ViewHolder.ClickListener clickListener; public Adapter(ViewHolder.ClickListener clickListener) { super(); this.clickListener = clickListener; // Create some items Random random = new Random(); items = new ArrayList<>(); for (int i = 0; i < ITEM_COUNT; ++i) { items.add(new Item("Item " + i, "This is the item number " + i, random.nextBoolean())); } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final int layout = viewType == TYPE_INACTIVE ? R.layout.item : R.layout.item_active; View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); return new ViewHolder(v, clickListener); } // … public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { @SuppressWarnings("unused") private static final String TAG = ViewHolder.class.getSimpleName(); TextView title; TextView subtitle; View selectedOverlay; private ClickListener listener; public ViewHolder(View itemView, ClickListener listener) { super(itemView); title = "(TextView) itemView.findViewById(R.id.title);" subtitle = "(TextView) itemView.findViewById(R.id.subtitle);" selectedOverlay = itemView.findViewById(R.id.selected_overlay); this.listener = listener; itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } @Override public void onClick(View v) { if (listener != null) { listener.onItemClicked(getPosition()); } } @Override public boolean onLongClick(View v) { if (listener != null) { return listener.onItemLongClicked(getPosition()); } return false; } public interface ClickListener { public void onItemClicked(int position); public boolean onItemLongClicked(int position); } } } ``` To distinguish the selection mode from the normal mode, we will use an `ActionMode`, allowing us to display a different `ActionBar` while the selection is active. To achieve this, we have to implement a basic `ActionMode.Callback`. For simplicity, our `Activity` will implement this interface in an inner class. It will also implement our new click listener interface, `Adapter.ViewHolder.ClickListener`. We will need access to our `Adapter` from the callback class, so we move it as an attribute in the `Activity`. Let's summarize the click handlers logic. On a click, if there's no current selection, we do nothing. If there is something selected, we toggle the selection state of the clicked item. On a long click, if there is no current selection, we start the selection and toggle the selection state of the clicked item. If there is already something selected, we toggle the selection state too. Our `MainActivity` becomes a little more complex: {{ filename(body="MainActivity.java") }} ```java public class MainActivity extends ActionBarActivity implements Adapter.ViewHolder.ClickListener { @SuppressWarnings("unused") private static final String TAG = MainActivity.class.getSimpleName(); private Adapter adapter; private ActionModeCallback actionModeCallback = new ActionModeCallback(); private ActionMode actionMode; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); adapter = new Adapter(this); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setAdapter(adapter); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)); } @Override public void onItemClicked(int position) { if (actionMode != null) { toggleSelection(position); } } @Override public boolean onItemLongClicked(int position) { if (actionMode == null) { actionMode = startSupportActionMode(actionModeCallback); } toggleSelection(position); return true; } /** * Toggle the selection state of an item. * * If the item was the last one in the selection and is unselected, the * selection is stopped. * Note that the selection must already be started (actionMode must not be * null). * * @param position Position of the item to toggle the selection state */ private void toggleSelection(int position) { adapter.toggleSelection(position); int count = adapter.getSelectedItemCount(); if (count == 0) { actionMode.finish(); } else { actionMode.setTitle(String.valueOf(count)); actionMode.invalidate(); } } private class ActionModeCallback implements ActionMode.Callback { @SuppressWarnings("unused") private final String TAG = ActionModeCallback.class.getSimpleName(); @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mode.getMenuInflater().inflate (R.menu.selected_menu, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_remove: // TODO: actually remove items Log.d(TAG, "menu_remove"); mode.finish(); return true; default: return false; } } @Override public void onDestroyActionMode(ActionMode mode) { adapter.clearSelection(); actionMode = null; } } } ``` And here is the result. The screenshot was taken while selecting the item "Item 5": {{ img(src="/images/articles/recyclerview-basics/recyclerview-selection.png", caption="Selection mode") }} We need to handle our "remove" event, which does nothing for now. Let's see how to modify our dataset in the last part. ## Changing the dataset In order to update the view, the `Adapter` must notify when a change occurs on its data. On basic `ListView`s, adapters had a single method to achieve that: `notifyDataSetChanged()`. However, this method is far from optimal: every view must be refreshed, because we don't know exactly what changed. With the `RecyclerView.Adapter`, we've got multiple methods: - `notifyItemChanged(int position)` - `notifyItemInserted(int position)` - `notifyItemRemoved(int position)` - `notifyItemMoved(int fromPosition, int toPosition)` - `notifyItemRangeChanged(int positionStart, int itemCount)` - `notifyItemRangeInserted(int positionStart, int itemCount)` - `notifyItemRangeRemoved(int positionStart, int itemCount)` - `notifyDataSetChanged()` We can notify an item insertion, removal and change, same for a range of items, an item move, or a full dataset change. Let's take the removal as an example. We will write two public methods in our `Adapter` to allow external classes to remove either a single item, or a list of items. While the removal of a single item is straightforward, we need to think a little more for the list. If the user provides us with a list of `[5, 8, 9]` to remove, if we start by removing the item 5, our list is an item shorter, _before_ 8 and 9. We should remove `[5, 7, 7]` one after the other. We can handle that. But what happens if the user provides `[8, 9, 5]`? There's a pretty simple solution to this: sort our input list in the reverse-order. This allows easy ranges detection too, which is something we will need to make our calls to `notifyItemRangeRemoved()`. {{ filename(body="Adapter.java") }} ```java public class Adapter extends SelectableAdapter { // … public void removeItem(int position) { items.remove(position); notifyItemRemoved(position); } public void removeItems(List positions) { // Reverse-sort the list Collections.sort(positions, new Comparator() { @Override public int compare(Integer lhs, Integer rhs) { return rhs - lhs; } }); // Split the list in ranges while (!positions.isEmpty()) { if (positions.size() == 1) { removeItem(positions.get(0)); positions.remove(0); } else { int count = 1; while (positions.size() > count && positions.get(count).equals(positions.get(count - 1) - 1)) { ++count; } if (count == 1) { removeItem(positions.get(0)); } else { removeRange(positions.get(count - 1), count); } for (int i = 0; i < count; ++i) { positions.remove(0); } } } } private void removeRange(int positionStart, int itemCount) { for (int i = 0; i < itemCount; ++i) { items.remove(positionStart); } notifyItemRangeRemoved(positionStart, itemCount); } // … } ``` And the final code chunk of this article: actually call these two methods. We already set-up a "Remove" action in our contextual menu, we just need to call `removeItems()` from it. To test the other one, let's say that a click on a view will remove it: {{ filename(body="MainActivity.java") }} ```java public class MainActivity extends ActionBarActivity implements Adapter.ViewHolder.ClickListener { // … @Override public void onItemClicked(int position) { if (actionMode != null) { toggleSelection(position); } else { adapter.removeItem(position); } } // … private class ActionModeCallback implements ActionMode.Callback { // … @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_remove: adapter.removeItems(adapter.getSelectedItems()); mode.finish(); return true; default: return false; } } // … } } ``` And that's it. We can remove items. As a bonus from using the right notification method instead of the generic `notifyDataSetChanged()`, we have a nice animation! {{ video(src="/images/articles/recyclerview-basics/recyclerview-remove.webm", caption="Removing items from the RecyclerView") }} ## Conclusion While the `RecyclerView` is a little more difficult to set up than a `ListView` or a `GridView`, it allows for an easier responsibility separation between the view holders, the data source, the list itself… and a more polished user-experience, thanks to the animations. Regarding the performance, I think that in comparison to a well-used `ListView`, implementing the `ViewHolder` pattern correctly, it should be pretty similar. However, with the `RecyclerView`, you **have** to use the `ViewHolder` pattern. It's not an option. And that's a good thing for both the user, who will have better performance than without the `ViewHolder`, and for the developer, who will have to write better code. While the `RecyclerView` obviously still lacks some polish (yes, I'm looking at you, `StaggeredGridLayoutManager`), it's perfectly usable right now, and is a welcome addition in the SDK. The full example code is available on [GitHub](https://github.com/Kernald/recyclerview-sample). Note that the code has been updated by [Shinil M S](https://github.com/shinilms12) (thanks again) since the article was released, to follow the small API changes. The code written for this article can be found at the tag [article](https://github.com/Kernald/recyclerview-sample/tree/article).