time

About RecyclerView in Android

Contents

Introduction

The RecyclerView class, delivered as part of the v7 support library suite, is an improved version of the ListView and the GridView classes provided by the Android framework.

In other words, the RecyclerView is a new ViewGroup that is prepared to render any adapter-based view in a similar way.

The RecyclerView widget is a more advanced and flexible version of ListView. This widget is a container for displaying large data sets that can be scrolled very efficiently by maintaining a limited number of views. It requires you to use the ViewHolder pattern which improves performance as you avoid initializing views every time.

One advantage over ListView or GridView is the built in support for animations as items are added, removed, or repositioned. Moreover, respect to ListView, RecyclerView is much more customizable.

Unlike the ListView, the RecyclerView also provides a choice of three built-in layout managers to control the way in which the list items are presented to the user:

  • LinearLayoutManager - the list items are presented as either a horizontal or vertical scrolling list.
  • GridLayoutManager - the list items are presented in grid format. This manager is best used when the list items of are of uniform size.
  • StaggeredGridLayoutManager - the list items are presented in a staggered grid format. This manager is best used when the list items are not of uniform size.

For situations where none of the three built-in managers provide the necessary layout, custom layout managers may be implemented by subclassing the RecyclerView.LayoutManager class.

The implementation of RecyclerView requires a few classes to be implemented. The most important classes are listed in the following table.

Class Purpose Optional
Adapter Provides the data and responsible for creating the views for the individual entry. Required
ViewHolder Contains references for all views that are filled by the data of the entry Required
LayoutManager Contains references for all views that are filled by the data of the entry Required, but default implementations available
ItemDecoration Responsible for drawing decorations around or on top of the view container of an entry Default behavior, but can be overridden
ItemAnimator Responsible to define the animation if entries are added, removed or reordered Default behavior, but can be overridden
android_recyclerview_scheme.png

Comparison between RecyclerView and ListView

There are a lots of new features in RecyclerView that are not present in existing ListView. The RecyclerView is more flexible, powerful and a major enhancement over ListView. Here I will try to give you a detailed insight into it.

  1. Custom Item Layouts. ListView can only layout the items in Vertical Arrangement and that arrangement cannot be customized according to our requirements. Suppose we need to create a horizontal list then that thing is not feasible with default ListView. But with introduction of Recyclerview we can easily create a horizontal or vertical List. By using LayoutManager component of RecyclerView we can easily define the orientation of items.
  2. Use Of ViewHolder Pattern. ListView adapters do not require the use of ViewHolder but RecyclerView require the use of ViewHolder that is used to store the reference of view’s. In ListView it is recommended to use the ViewHolder but it is not compulsion but in RecyclerView it is mandatory to use ViewHolder which is the main difference between RecyclerView and ListView. ViewHolder is a static inner class in our Adapter which holds references to the relevant view’s. By using these references our code can avoid time consuming findViewById() method to update the widgets with new data.
  3. Adapters. In ListView we use many adapter‘s like ArrayAdapter for displaying simple array data, BaseAdapter and SimpleAdapters for custom lists. In RecyclerView we only use RecyclerView.Adapter to set the data in list.
  4. Item Animator. ListView are lacking in support of good animation. RecyclerView brings a new dimensions in it. By using RecyclerView.ItemAnimator class we can easily animate the view.
  5. Item Decoration. In ListView dynamically decorating items like adding divider or border was not easy but in RecyclerView by using RecyclerView.ItemDecorator class we have a huge control on it.

Example of RecyclerView

Our plan is

  1. Create a new project using your Android Studio.
  2. Add RecylerView and CardView dependencies to build.gradle (Module:app).
  3. Add RecylerView to our activity_main.xml and complete this layout.
  4. Create a new layout for single row of RecyclerView using CardView.
  5. Create a RecylerViewHolder java class for Recyclerview by extending RecyclerView.ViewHolder.
  6. Create an RecylerAdapter java class for Recyclerview by extending RecyclerView.Adapter.
  7. Set RecylerView in MainActivity.

1. Open your Android Studio and create a new project

2. Go to GradleScripts > build.gradle (Module:app) and add below libraries to this file under dependencies block. My dependencies block is

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.0.1'
    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.android.support:cardview-v7:25.0.1'
}

3. Open your activity_main.xml file and add Recyclerview to it

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rcView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="16dp"
        android:paddingTop="16dp"
        android:scrollbars="vertical"
         />

</RelativeLayout>

4. Create a new layout for single row of Recyclerview using CardView.

CardView widget can be used to create simple cards. CardView extends the FrameLayout class and lets you show information inside cards that have a consistent look across the platform. CardView widgets can have shadows and rounded corners.

For single row of RecyclerView I am going to make a simple layout with one ImageView and two text for title and description. The complete layout is wrapped inside CardView to give it some good look. Here is my item_list.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:layout_marginBottom="8dp"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    android:background="#C5CAE9"
    android:foreground="?attr/selectableItemBackground"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <RelativeLayout
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/avatar"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_centerVertical="true"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="10dp"
            android:scaleType="centerCrop"
            android:src="@android:drawable/star_big_on" />

        <TextView
            android:id="@+id/title"
            android:layout_centerVertical="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_toRightOf="@+id/avatar"
            android:text="Title"
            android:textColor="#000000"
            android:textAppearance="?attr/textAppearanceListItem"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/title"
            android:layout_marginLeft="16dp"
            android:layout_toRightOf="@+id/avatar"
            android:textColor="#000000"
            android:ellipsize="end"
            android:singleLine="true"
            android:text="Movie from IMDB 250"
            android:textAppearance="?attr/textAppearanceListItem"
            android:textSize="14sp" />
    </RelativeLayout>
</android.support.v7.widget.CardView>

5. Create a RecylerViewHolder java class for RecyclerView by extending RecyclerView.ViewHolder.

RecylerView uses a ViewHolder to store references to the views for one entry in the RecylerView. A ViewHolder class is typically a static inner class in your adapter which holds references to the relevant views. With these references your code can avoid the findViewById() method in an adapter to find the views which should be filled with your new data. This pattern avoids looking up the UI components all the time the system shows a row in the list and this is approximately 15% faster then using the findViewById() method.

Right click on your package and create a new java class name it RecyclerViewHolder and extends RecyclerView.ViewHolder. In this define two TextView and an ImageView of item_list.xml file. Copy and paste below code to your RecyclerViewHolder class. Here is my complete code of RecyclerViewHolder

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class RecyclerViewHolder extends RecyclerView.ViewHolder {
    TextView tv1, tv2;
    ImageView imageView;

    public RecyclerViewHolder(View itemView) {
        super(itemView);
        tv1 = (TextView) itemView.findViewById(R.id.title);
        tv2 = (TextView) itemView.findViewById(R.id.desc);
        imageView = (ImageView) itemView.findViewById(R.id.avatar);
    }
}

6. Create an RecylerAdapter java class for RecyclerView by extending RecyclerView.Adapter.

The adapter is a component that stands between the data model we want to show in our app UI and the UI component that renders this information. In other words, an adapter guides the way the information are shown in the UI.

To connect data set and view we need an adapter as you have already used adapter in Listview. But in case of RecyclerView we are not going to extend any base adapter or array adapter like ListView. For RecyclerView we are going to extend RecyclerView.Adapter in our Adapter class like below code. Create an new java class by right click on your package and name it RecyclerAdapter and extend RecyclerView.Adapter it will ask you to implement three method onCreateViewHolder(), onBindViewHolder(), getItemCount().

  • getItemCount() This method return the number of items present in the data.
  • onCreateViewHolder() Inside this method we specify the layout that each item of the RecyclerView should use. This is done by inflating the layout using LayoutInflater, passing the output to the constructor of the custom ViewHolder.
  • onBindViewHolder() This method is very similar to the getView method of a ListView's adapter. In our example, here's where you have to set the String values to TextView.

Now we will complete some small things like we will make an array for title text. We will also make a context variable LayoutInflater and a constructor of Adapter class just copy and paste below code in your adapter class above all the methods.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerViewHolder>{

    String[] name = {"The Shawshank Redemption", "The Godfather", 
         "The Godfather: Part II", "The Dark Knight", "Schindler's List", "12 Angry Men", "Pulp Fiction"};
    Context context;
    LayoutInflater inflater;

    public RecyclerAdapter(Context context) {
        this.context = context;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = inflater.inflate(R.layout.item_list, parent, false);

        RecyclerViewHolder viewHolder = new RecyclerViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        holder.tv1.setText(name[position]);
        holder.imageView.setOnClickListener(clickListener);
        holder.imageView.setTag(holder);
    }

    View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            RecyclerViewHolder vholder = (RecyclerViewHolder) v.getTag();
            int position = vholder.getPosition();
            Toast.makeText(context, "Position is " + position, Toast.LENGTH_LONG).show();
        }
    };

    @Override
    public int getItemCount() {
        return name.length;
    }
}

As you can see we inflate item_list.xml file inside onCreateViewHolder(). Also have made object of RecyclerViewHolder inside onCreateViewHolder and return this object like below code.

Inside onBindViewHolder() method we have set text to TextView from name array and also have made onClickListener for click on ImageView of row of RecyclerView.

And last step is to set number of items of RecyclerView so inside getItemCount() method we have returned number of items of name array.

7. Set RecylerView in MainActivity and complete everything.

Open your MainActivity.java and we will complete following steps

  • Define RecyclerView and register.
  • Make an object of RecyclerAdapter class and set Adapter to RecyclerView.
  • Set LayoutManager to RecyclerView in my case I am using LinearLayoutManager.

Here is complete code of MainActivity.java.

public class MainActivity extends AppCompatActivity {
    RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.rcView);

        RecyclerAdapter adapter = new RecyclerAdapter(this);
        recyclerView.setAdapter(adapter);
        recyclerView.setHasFixedSize(true);

        // layout manager for RecyclerView

        //int columns = 2;
        //GridLayoutManager lm = new GridLayoutManager(this, columns, 
        //       GridLayoutManager.VERTICAL, false);
        //StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(columns, 
        //       StaggeredGridLayoutManager.VERTICAL);
        //lm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);

        LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(lm);
    }
}

Result

android_recyclerview_demo.png

Code on github

Item click listener for RecyclerView

In this section we'll handle user clicks on RecyclerView items. In other words, we want to know if the an user clicks on a item and what type of click he is making: simple click or long click.

When we use RecyclerView the things are a little more complex than ListView. In this case, we have to create a class that implements RecyclerView.OnItemTouchListener.

Right click on your package and create a new java class with name RecyclerItemListener and extends RecyclerView.OnItemTouchListener.

public class RecyclerItemListener 
    implements RecyclerView.OnItemTouchListener  {

    private RecyclerTouchListener listener;
    private GestureDetector gd;

    public interface RecyclerTouchListener {
        public void onClickItem(View v, int pos);
        public void onLongClickItem(View v, int pos);
    }

    public RecyclerItemListener(Context ctx, final RecyclerView rv, 
                                final RecyclerTouchListener listener) {
        this.listener = listener;
        gd = new GestureDetector(ctx, 
             new GestureDetector.SimpleOnGestureListener() {
                 @Override
                 public void onLongPress(MotionEvent e) {
                   View v = rv.findChildViewUnder(e.getX(), e.getY());
                   listener.onLongClickItem(v, rv.getChildAdapterPosition(v));
                }

               @Override
               public boolean onSingleTapUp(MotionEvent e) {
                  View v = rv.findChildViewUnder(e.getX(), e.getY());
                  listener.onClickItem(v, rv.getChildAdapterPosition(v));
                  return true;
              }
         });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        View child = rv.findChildViewUnder(e.getX(), e.getY());
        return ( child != null && gd.onTouchEvent(e));
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
}

We define a callback interface to notify the listener when the user clicks on an item. The first thing is creating an instance of GestureDetector so that we can know when the user clicks and what click type he is doing.

Next, our class overrides onInterceptTouchEvent to know if the user selects and item and if the gesture detected is handled by our instance.

To receive notification, in the listener class we simply implements the interface declared above. Following snippet goes to onCreate of MainActivity

recyclerView.addOnItemTouchListener(new RecyclerItemListener(getApplicationContext(), 
    recyclerView, new RecyclerItemListener.RecyclerTouchListener() {
        public void onClickItem(View v, int pos) {
            Toast.makeText(getApplicationContext(), "Simple click", Toast.LENGTH_LONG).show();
        }

        public void onLongClickItem(View v, int position) {
            Toast.makeText(getApplicationContext(), "Long click", Toast.LENGTH_LONG).show();
        }
}));

Decorate item for RecyclerView

In this section we'll customize the RecyclerView and add a row divider.. To do it we have to implement a custom class that extends RecyclerView.ItemDecoration.

Right click on your package and create a new java class with name DividerItemDecoration and extends RecyclerView.ItemDecoration.

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable div;

    public DividerItemDecoration(Drawable div) {
       this.div = div;
    }

    @Override
    public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + div.getIntrinsicHeight();
            div.setBounds(left, top, right, bottom);
            div.draw(canvas);
        }
    }
}

In this class, we override onDrawOver and implements our UI customizazion.

To set this decorator add following snippet to onCreate of MainActivity

recyclerView.addItemDecoration(
    new DividerItemDecoration(ContextCompat.getDrawable(getApplicationContext(), 
        R.drawable.item_decorator)));

Where item_decorator is defined under res/drawable directroy in this way

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
      <size android:height="1dp" />
      <solid android:color="#FFB1BEC4" />
</shape>

Also we can add some margin to first item like so

public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
    private int offset;

    public ItemOffsetDecoration(int offset) {
        this.offset = offset;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view,
                               RecyclerView parent, RecyclerView.State state) {

        if (parent.getChildAdapterPosition(view) == 0) {
                outRect.right = offset;
                outRect.left = offset;
                outRect.top = offset;
                outRect.bottom = offset;
            }
        }
    }
}

And use with following statement

recyclerView.addItemDecoration(new ItemOffsetDecoration(20));

Infinite scroll

There are several ways to create infinite scroll in RecyclerView:

  1. Use UltimateRecyclerView as substitution for RecyclerView. Tutotial here.
  2. Use Paginate to extend RecyclerView functionality. Read about it below.
  3. Use PlaceHolderView as substitution for RecyclerView. Tutotial here.
  4. Implement setOnLoadMoreListener. Details here.

My list of dependencies are

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:recyclerview-v7:23.3.0'
    compile 'com.android.support:cardview-v7:23.3.0'
    compile 'com.github.castorflex.smoothprogressbar:library-circular:1.2.0'
    compile 'com.github.markomilos:paginate:0.5.1'
}

Main layout activity_main.xml is the same.

MainActivity have updated to new version

public class MainActivity extends AppCompatActivity implements Paginate.Callbacks {
    RecyclerView recyclerView;

    private boolean loading = false;
    private int currentPage = 0;
    protected int threshold = 4;
    protected int totalPages = 10;
    private Handler handler = new Handler();
    private Paginate paginate;
    protected boolean addLoadingRow = true;
    protected boolean customLoadingListItem = false;
    Random rnd = new Random();

    RecyclerAdapter moviesAdapter;
    ArrayList movies;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.rcView);
        LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(lm);


        recyclerView.addOnItemTouchListener(new RecyclerItemListener(getApplicationContext(), recyclerView,
            new RecyclerItemListener.RecyclerTouchListener() {
                public void onClickItem(View v, int position) {
                    Toast.makeText(getApplicationContext(), "On Click Item interface",
                            Toast.LENGTH_LONG).show();
                }

                public void onLongClickItem(View v, int position) {
                    Toast.makeText(getApplicationContext(), "On Long Click Item interface",
                            Toast.LENGTH_LONG).show();
                }
        }));

        setupPagination();
    }

    protected void setupPagination() {
        if (paginate != null) {
            paginate.unbind();
        }

        handler.removeCallbacks(fakeCallback);

        movies = new ArrayList<String>(Arrays.asList("The Shawshank Redemption", "The Godfather",
                "The Godfather: Part II", "The Dark Knight", "Schindler's List", "12 Angry Men",
                "Pulp Fiction"));

        moviesAdapter = new RecyclerAdapter(this, movies);
        recyclerView.setAdapter(moviesAdapter);
        recyclerView.setHasFixedSize(true);

        loading = false;
        currentPage = 0;

        paginate = Paginate.with(recyclerView, this)
            .setLoadingTriggerThreshold(threshold)
            .addLoadingListItem(addLoadingRow)
            .setLoadingListItemCreator(customLoadingListItem ? new CustomLoadingListItemCreator() : null)
            .build();
    }

    @Override
    public synchronized void onLoadMore() {
        loading = true;
        // fake asynchronous loading that will generate page of random data after some delay
        handler.postDelayed(fakeCallback, 1000);
    }

    @Override
    public synchronized boolean isLoading() {
        return loading; // return boolean weather data is already loading or not
    }

    @Override
    public boolean hasLoadedAllItems() {
        return currentPage == totalPages; // if all pages are loaded return true
    }

    private Runnable fakeCallback = new Runnable() {
        @Override
        public void run() {
            currentPage++;
            moviesAdapter.add("Movie " + String.valueOf(rnd.nextInt(100)));
            loading = false;
        }
    };

    private class CustomLoadingListItemCreator implements LoadingListItemCreator {
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(R.layout.custom_loading_list_item, parent, false);
            return new LoadingHolder(view);
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            LoadingHolder lh = (LoadingHolder) holder;
            lh.tvLoading.setText(String.format("Total items loaded: %d.\nLoading more...", moviesAdapter.getItemCount()));
        }
    }

    static class LoadingHolder extends RecyclerView.ViewHolder {
        TextView tvLoading;

        public LoadingHolder(View itemView) {
            super(itemView);
            tvLoading = (TextView) itemView.findViewById(R.id.tv_loading_text);
        }
    }
}

Note some key things:

  • We implemented Paginate.Callbacks interface for MainActivity.
  • Paginate.Callbacks interface requires three methods onLoadMore, isLoading, hasLoadedAllItems.
  • We realize fakeCallback method for generating new items.
  • Class CustomLoadingListItemCreator is responsible for loader view.

Layout for custom loader is

<?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="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <fr.castorflex.android.circularprogressbar.CircularProgressBar
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:indeterminate="true"
        app:cpb_color="#F00"
        app:cpb_max_sweep_angle="300"
        app:cpb_min_sweep_angle="10"
        app:cpb_rotation_speed="1.0"
        app:cpb_stroke_width="4dp"
        app:cpb_sweep_speed="1.0"/>

    <TextView
        android:id="@+id/tv_loading_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        tools:text="Loading..."/>
</LinearLayout>

Following is new RecyclerAdapter

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerViewHolder> {
    List<String> names;
    Context context;
    LayoutInflater inflater;

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = inflater.inflate(R.layout.item_list, parent, false);

        RecyclerViewHolder viewHolder = new RecyclerViewHolder(v);
        return viewHolder;
    }

    public RecyclerAdapter(Context context, List<String> name) {
        this.context = context;
        this.names = name;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        holder.tv1.setText(names.get(position));
        holder.imageView.setTag(holder);
    }

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

    public void add(String name) {
        int previousDataSize = names.size();
        names.add(name);
        //notifyDataSetChanged();
        notifyItemInserted(names.size());
    }
}

Layout for RecyclerView items and RecyclerViewHolder are the same.

Result

android_recyclerview_infinite_scroll.png

Grid via GridLayoutManager

Similar to displaying items as a list, displaying items as a grid is simple using RecyclerView. In this section we are going to display images and text as grid using RecyclerView.

Our main layout has only one RecyclerView widget in LinearLayout.

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

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rvItems"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="1dp"
        />

</LinearLayout>

The next layout is for RecylerView grid item. It has a Textview child widget to display some text.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_margin="2dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"/>

</LinearLayout>

Following is MainActivity with RecyclerView.Adapter and RecyclerViewHolder.

public class MainActivity extends AppCompatActivity {
    public class GridAdapter extends RecyclerView.Adapter<GridAdapter.RecyclerViewHolder>{
        Context context;
        LayoutInflater inflater;
        String[] items;
        Random rnd = new Random();

        public class RecyclerViewHolder extends RecyclerView.ViewHolder {
            TextView tvTitle;

            public RecyclerViewHolder(View view) {
                super(view);
                tvTitle = (TextView) view.findViewById(R.id.tvTitle);
            }
        }

        public GridAdapter (Context context, String[] items) {
            this.context = context;
            this.items = items;
            inflater = LayoutInflater.from(context);
        }

        @Override
        public GridAdapter.RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View v = inflater.inflate(R.layout.grid_item, parent, false);
            GridAdapter.RecyclerViewHolder viewHolder = new GridAdapter.RecyclerViewHolder(v);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(GridAdapter.RecyclerViewHolder holder, final int position) {
            final String title = items[position];

            int color = getRandomHSVColor();
            holder.tvTitle.setBackgroundColor(getLighterColor(color));
            holder.tvTitle.setTextColor(getReverseColor(color));

            holder.tvTitle.setText(title);
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(context, title, Toast.LENGTH_SHORT).show();
                }
            });
        }

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

        protected int getRandomHSVColor(){
            // Generate a random hue value between 0 to 360
            int hue = rnd.nextInt(361);
            // We make the color depth full
            float saturation = 1.0f;
            // We make a full bright color
            float value = 1.0f;
            // We avoid color transparency
            int alpha = 255;
            // Finally, generate the color
            int color = Color.HSVToColor(alpha, new float[]{hue, saturation, value});
            // Return the color
            return color;
        }

        protected int getReverseColor(int color){
            float[] hsv = new float[3];
            Color.RGBToHSV(
                    Color.red(color), // Red value
                    Color.green(color), // Green value
                    Color.blue(color), // Blue value
                    hsv
            );
            hsv[0] = (hsv[0] + 180) % 360;
            return Color.HSVToColor(hsv);
        }

        protected int getLighterColor(int color){
            float[] hsv = new float[3];
            Color.colorToHSV(color,hsv);
            hsv[2] = 0.2f + 0.8f * hsv[2];
            return Color.HSVToColor(hsv);
        }
    }

    int NUM_COLUMNS = 3;
    RecyclerView rvItems;
    String[] items = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        GridAdapter adapter = new GridAdapter(this, items);

        rvItems = (RecyclerView) findViewById(R.id.rvItems);
        rvItems.setLayoutManager(new GridLayoutManager(this, NUM_COLUMNS));
        rvItems.setAdapter(adapter);
        rvItems.setHasFixedSize(true);
    }
}

Result

android_recyclerview_grid.png

Useful links

comments powered by Disqus