Written by Adrian Kremski
Android Developer
Published June 22, 2015

RecyclerView tested!

RecyclerView is a new widget introduced with the Android Lollipop release. We tested it developing a simple app for managing your tasks.

The RecyclerView widget is meant to replace functionalities of ListView and GridView.

Let’s get down to business!

1. Gradle dependency

First we will add a few dependencies to our gradle.build.

dependencies {
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.android.support:recyclerview-v7:21.0.3'
    compile 'com.jakewharton:butterknife:6.0.0'
}

2. Basic setup

Now that we have access to RecyclerView, let’s add it to our main activity.

public class MainActivity extends ActionBarActivity {

    @InjectView(R.id.recycler_view_holder)
    public FrameLayout recyclerViewHolder;

    @InjectView(R.id.toolbar)
    public Toolbar toolbar;

    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        setSupportActionBar(toolbar);

        recyclerView = new RecyclerView(this);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        recyclerViewHolder.addView(recyclerView);
    }
}

`activity_main.xml`

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity">

   <android.support.v7.widget.Toolbar
       android:id="@+id/toolbar"
       style="@style/Toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:minHeight="?attr/actionBarSize"
       app:contentInsetLeft="8dp"
       app:contentInsetStart="8dp"
       app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
       app:theme="@style/ThemeOverlay.AppCompat.ActionBar"
       tools:ignore="UnusedAttribute">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:fontFamily="sans-serif-light"
           android:text="@string/app_name"
           android:textColor="#fff"
           android:textSize="20sp"
           android:textStyle="bold" />

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

       <FrameLayout
           android:id="@+id/recycler_view_holder"
           android:layout_width="match_parent"
           android:layout_height="match_parent">

       </FrameLayout>

</LinearLayout>

2. LayoutManager

RecyclerView.LayoutManager is a class responsible for laying out the RecyclerView children. ‘com.android.support:recyclerview-v7:21.0.3’ gives us access to 3 different layout policies:

If the presented LayoutManagers are not enough for you, consider using some third-party library like TwoWayView.

3. Adapter

RecyclerView.Adapter is responsible for chaining the model to its visual representation. It may sound similar to adapter class and its extensions used for ListView or GridView, however, there are few differences.

Let’s create our own adapter which will operate on a list of tasks.

public class MainActivity extends ActionBarActivity {

    private List<Task> tasks = new LinkedList<Task>();
    private TaskAdapter taskAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        tasks.add(new Task("Wake up"));
        tasks.add(new Task("Go to work"));
        tasks.add(new Task("Make a coffee"));
        tasks.add(new Task("Go to standup"));
        tasks.add(new Task("Make a coffee"));
        tasks.add(new Task("Spend some time in chillout room"));
        tasks.add(new Task("Make a coffee"));
        tasks.add(new Task("Go home"));
        tasks.add(new Task("Make a coffee"));
        tasks.add(new Task("Sleep"));

        recyclerView.setAdapter(taskAdapter = new TaskAdapter());

        ...
    }

    private class TaskAdapter extends RecyclerView.Adapter {

        @Override
        public TaskRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new TaskRowHolder(getLayoutInflater().inflate(R.layout.view_task_row, parent, false));
        }

        @Override
        public void onBindViewHolder(TaskRowHolder holder, int position) {
            holder.setTask(tasks.get(position), position);
        }

        @Override
        public int getItemCount() {
            return tasks.size();
        }
    }
 
    private class Task {
        String name;

        public Task(String name) {
            this.name = name;
        }
    }
}

As you can see, we had to implement 3 methods.

  • `getItemCount()` – returns amount of tasks in our application
  • `onCreateViewHolder()` – creates and returns row ViewHolder
  • `onBindViewHolder()` – updates ViewHolder with the item on given position

Here is how our TaskRowHolder and its layout will look like:

class TaskRowHolder extends RecyclerView.ViewHolder {

    private TextView taskNameLabel;

    private Task task;
    private int taskNumber;

    public TaskRowHolder(View itemView) {
        super(itemView);
        taskNameLabel = ButterKnife.findById(itemView, R.id.task_name);
    }
    
    public void setTask(Task task, int taskNumber) {
        this.task = task;
        this.taskNumber = taskNumber;
        taskNameLabel.setText(task.name);
    }
}

`view_task_row.xml`

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:card_view="http://schemas.android.com/apk/res-auto"
   android:id="@+id/card_view"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="center"
   android:layout_margin="4dp"
   card_view:cardCornerRadius="4dp">

   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_margin="4dp"
       android:orientation="horizontal"
       android:padding="8dp">

       <ImageView
           android:id="@+id/logo"
           android:layout_width="48dp"
           android:layout_height="48dp"
           android:layout_centerVertical="true"
           android:src="@drawable/schibsted" />

       <TextView
           android:id="@+id/task_name"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_centerVertical="true"
           android:layout_marginLeft="8dp"
           android:layout_toRightOf="@id/logo"
           android:fontFamily="sans-serif-light"
           android:textSize="18sp" />
   </RelativeLayout>
</android.support.v7.widget.CardView>

`Remember to add CardView dependency to build.gradle.`

dependencies {
    'com.android.support:cardview-v7:21.0.3''
}

So let’s check the result.

result_1_resized

4. ItemDecoration

As you can see, the rows on the list are separated by CardView look. The other option would be to use RecyclerView.ItemDecoration.

First we will prepare our TaskRowItemDecoration (code used below is available in Android demos).

public class MainActivity extends ActionBarActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        recyclerView.addItemDecoration(new TaskRowItemDecoration(getDrawable(R.drawable.task_divider)));
    }

    public class TaskRowItemDecoration extends RecyclerView.ItemDecoration {

        private Drawable dividerDrawable;

        public TaskRowItemDecoration(Drawable drawable) {
            this.dividerDrawable = drawable.mutate();
        }

        @Override
        public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i &lt; parent.getChildCount(); i++) {
                View child = parent.getChildAt(i);

                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

                int top = child.getBottom() + params.bottomMargin;
                int bottom = top + dividerDrawable.getIntrinsicHeight();

                dividerDrawable.setBounds(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom);
                dividerDrawable.draw(canvas);
            }
        }
    }
}

`CardView also needs to be removed temporarily to show the effect of using ItemDecoration.`

`task_divider.xml`

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
   android:shape="rectangle">
   <solid android:color="#444" />
   <size android:width="1dp" android:height="1dp" />
</shape>

To add dividers, we had to implement public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state). method

  • `canvas` – destination canvas
  • `parent` – our recyclerView instance
  • `state` – state of RecyclerView

Result

result_2_resized

Once again, you can find some libraries which will help you with the item decoration (e.g FlexibleDivider)

5. Checkable items

So far, our list doesn’t have any behaviour. We will change that by making the rows checkable.
First, we will add a CheckBox widget to our view_task_row.xml
`view_task_row.xml`

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:card_view="http://schemas.android.com/apk/res-auto"
   android:id="@+id/card_view"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="center"
   android:layout_margin="4dp"
   card_view:cardCornerRadius="4dp">

   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_margin="4dp"
       android:orientation="horizontal"
       android:padding="8dp">

       <ImageView
           android:id="@+id/logo"
           android:layout_width="48dp"
           android:layout_height="48dp"
           android:layout_centerVertical="true"
           android:src="@drawable/schibsted" />

       <TextView
           android:id="@+id/task_name"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_centerVertical="true"
           android:layout_marginLeft="8dp"
           android:layout_toLeftOf="@id/checkbox"
           android:layout_toRightOf="@id/logo"
           android:fontFamily="sans-serif-light"
           android:textSize="18sp" />

       <CheckBox
           android:id="@+id/checkbox"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_alignParentRight="true"
           android:layout_centerVertical="true"
           android:layout_marginLeft="8dp" />

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

Now we need to prepare our TaskRowHolder to send events upon checkbox state change. For this purpose we will use Otto event bus.

Add gradle dependecy for Otto.

dependencies {
    ....
    compile 'com.squareup:otto:1.3.+'
    ...
}

Define the event class which will hold information about the changed item.

public class TaskCheckStateChangedEvent {
    public boolean isChecked;
    public Task task;

    public TaskCheckStateChangedEvent(Task task, boolean isChecked) {
        this.task = task;
        this.isChecked = isChecked;
    }
}

Define BUS instance in our MainActivity.

public class MainActivity extends ActionBarActivity {
    private static final Bus BUS = new Bus();
    ...

    @Override
    protected void onResume() {
        super.onResume();
        BUS.register(taskAdapter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        BUS.unregister(taskAdapter);
    }
    ...
}

Post event upon TaskViewHolder checkbox state change.

class TaskRowHolder extends RecyclerView.ViewHolder {

    private TextView taskNameLabel;
    private CheckBox checkBox;

    private Task task;

    public TaskRowHolder(View itemView) {
       super(itemView);
       taskNameLabel = ButterKnife.findById(itemView, R.id.task_name);
       checkBox = ButterKnife.findById(itemView, R.id.checkbox);
       checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
           @Override
           public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
               BUS.post(new TaskCheckStateChangedEvent(task, isChecked));
           }
       });
    }

    public void setTask(Task task) {
        this.task = task;
        taskNameLabel.setText(task.name);
    }

    public void setChecked(boolean checked) {
        checkBox.setChecked(checked);
    }
}

Respond to Otto events with `taskAdapter`.

private class TaskAdapter extends RecyclerView.Adapter {

    private Set selectedTasks = new HashSet();
        
    ...

    @Override
    public void onBindViewHolder(TaskRowHolder holder, int position) {
        ...
        holder.setChecked(selectedTasks.contains(task));
    }

    @Subscribe
    public void onTaskCheckStateChangedEvent(TaskCheckStateChangedEvent event){
        if (event.isChecked) {
            selectedTasks.add(event.task);
        } else {
            selectedTasks.remove(event.task);
        }
    }
}

Result

6. Removing items

As the possibility to check/uncheck tasks is done, we will work on the removal feature.

Let’s start by defining xml file with our menu.

`main_menu.xml`

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item
       android:id="@+id/action_delete"
       android:orderInCategory="100"
       app:showAsAction="always"
       android:title="Delete"/>
</menu>
public class MainActivity extends ActionBarActivity {

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_delete:
                taskAdapter.deleteCheckedItems();
                break;
            default:
                return super.onOptionsItemSelected(item);
        }
        return true;
    }
    ...
}

Now the code for items removal.

private class TaskAdapter extends RecyclerView.Adapter {

    ...

    public void deleteCheckedItems() {
            for (Task task : selectedTasks) {
                taskAdapter.notifyItemRemoved(tasks.indexOf(task));
                tasks.remove(task);
            }
            selectedTasks.clear();
        }
}

7. Animating RecycleView item addition

We have the possibility to remove items but their addition to the ReyclerView is still hardcoded. Let’s change that.

We will use an additional library for FloatingButoon.

dependencies {
    compile 'com.getbase:floatingactionbutton:1.9.0'
}

Add floating button to main activity.

`activity_main.xml`

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity">

   <android.support.v7.widget.Toolbar
       android:id="@+id/toolbar"
       style="@style/Toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:minHeight="?attr/actionBarSize"
       app:contentInsetLeft="8dp"
       app:contentInsetStart="8dp"
       app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
       app:theme="@style/ThemeOverlay.AppCompat.ActionBar"
       tools:ignore="UnusedAttribute">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:fontFamily="sans-serif-light"
           android:text="@string/app_name"
           android:textColor="#fff"
           android:textSize="20sp"
           android:textStyle="bold" />

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

   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">

       <FrameLayout
           android:id="@+id/recycler_view_holder"
           android:layout_width="match_parent"
           android:layout_height="match_parent">

       </FrameLayout>

       <com.getbase.floatingactionbutton.FloatingActionButton
           android:id="@+id/add_task"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:fab_colorNormal="#ff4081"
           app:fab_colorPressed="#FF6E9F"
app:fab_icon="@drawable/ic_fab_star"

           android:layout_alignParentBottom="true"
           android:layout_alignParentRight="true"
           android:layout_marginBottom="16dp"/>

   </RelativeLayout>


</LinearLayout>

`MainActivity`

public class MainActivity extends ActionBarActivity {

    ...

    @OnClick(R.id.add_task)
    public void addTask() {
        final EditText input = new EditText(this);

        new AlertDialog.Builder(this).setTitle("New task").setMessage("Please supply task name")
                .setView(input).setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                tasks.add(new Task(input.getText().toString()));
                taskAdapter.notifyItemInserted(tasks.size()-1);
            }
        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                // Canceled.
            }
        }).show();
    }
}

Now that we have the possibility to add items manually, we can remove this code.

tasks.add(new Task("Wake up"));
tasks.add(new Task("Go to work"));
tasks.add(new Task("Make a coffee"));
tasks.add(new Task("Go to standup"));
tasks.add(new Task("Make a coffee"));
tasks.add(new Task("Spend some time in chillout room"));
tasks.add(new Task("Make a coffee"));
tasks.add(new Task("Go home"));
tasks.add(new Task("Make a coffee"));
tasks.add(new Task("Sleep"));

Finally we are going to add an animation for ‘add task’ action.

Gradle dependency

dependencies {
    ...

    compile 'jp.wasabeef:recyclerview-animators:1.2.0@aar'
}

Now all we need to do is to set our ItemAnimator

public class MainActivity extends ActionBarActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        recyclerView.setItemAnimator(new SlideInLeftAnimator());
    }
}

Result

8. Further extensions

As a final step we will add one more extension: the possibility to remove items with swipe gesture.

Get swipe to dismiss class SwipeToDismissTouchListener and add it to project with implementation shown below.

public class MainActivity extends ActionBarActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        swipeToDismissTouchListener = new SwipeToDismissTouchListener(recyclerView, new SwipeToDismissTouchListener.DismissCallbacks() {
            @Override
            public SwipeToDismissTouchListener.SwipeDirection canDismiss(int position) {
                return SwipeToDismissTouchListener.SwipeDirection.RIGHT;
            }
            @Override
            public void onDismiss(RecyclerView view, List dismissData) {
                for (SwipeToDismissTouchListener.PendingDismissData data : dismissData) {
                    tasks.remove(data.position);
                    taskAdapter.notifyItemRemoved(data.position);
                }
            }
        });
        recyclerView.addOnItemTouchListener(swipeToDismissTouchListener);
    }
}

Result

Check my Github repo for full code of the Task Manager.

`Thanks for reading!`

Written by Adrian Kremski
Android Developer
Published June 22, 2015