RecyclerView and CardView Example

RecyclerView and CardView Example in Android (2026 Guide)

RecyclerView and CardView Example in Android

In this tutorial, you will build a complete RecyclerView with CardView example in Android. The app displays a grid of user cards with images and names, and opens a detail screen when a card is tapped.

You will learn how to create a custom adapter, use GridLayoutManager, load images with Glide, and attach click listeners to individual items.

Overview

This project demonstrates a complete Android RecyclerView implementation with:

  • GridLayoutManager — displays items in a 2-column grid
  • CardView — each item is wrapped in a Material Design card
  • Glide — efficient image loading from URLs with placeholder and error states
  • Custom click listener — opens a detail activity on item tap
  • Serializable — passes the selected user object to the detail screen

Project Structure

The project contains the following files:

Project Structure
─────────────────────────────────────────
app/
├── AndroidManifest.xml
├── java/example/deepsingh44/com/appfirst/
│   ├── MainActivity.java
│   ├── DetailActivity.java
│   ├── MyCustomAdapter.java
│   └── User.java
├── res/layout/
│   ├── activity_main.xml
│   ├── item.xml
│   └── activity_detail.xml
└── build.gradle

build.gradle

Add the RecyclerView, CardView, and Glide dependencies to your app-level build.gradle.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "example.deepsingh44.com.appfirst"
        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:recyclerview-v7:27.1.1'
    implementation 'com.android.support:cardview-v7:27.1.1'
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:0.5'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}

Note for modern Android (AndroidX): If you are starting a new project today, replace the support library dependencies with their AndroidX equivalents:

implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.github.bumptech.glide:glide:4.16.0'

AndroidManifest.xml

Declare both activities and add the INTERNET permission (required for Glide to load images from URLs).

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="example.deepsingh44.com.appfirst">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".DetailActivity" />

    </application>

</manifest>

User.java (Model)

A simple POJO that holds the user data. It implements Serializable so it can be passed between activities via Intent.putExtra().

package example.deepsingh44.com.appfirst;

import java.io.Serializable;

public class User implements Serializable {
    private String image;
    private String name;
    private String job;

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

MainActivity.java

The main activity sets up the RecyclerView with a GridLayoutManager (2 columns) and attaches the custom adapter. Clicking an item launches DetailActivity with the selected user passed as a Serializable extra.

package example.deepsingh44.com.appfirst;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<User> userList;

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

        RecyclerView rec = findViewById(R.id.rec);
        rec.setItemAnimator(new DefaultItemAnimator());
        rec.setLayoutManager(new GridLayoutManager(this, 2));

        userList();
        MyCustomAdapter m = new MyCustomAdapter(this, userList);

        m.setDeepListener(new MyCustomAdapter.MyListener() {
            @Override
            public void deepItemClick(View view, int position) {
                Intent in = new Intent(MainActivity.this, DetailActivity.class);
                in.putExtra("deep", userList.get(position));
                startActivity(in);
            }
        });

        rec.setAdapter(m);
    }

    private void userList() {
        userList = new ArrayList<>();

        User u1 = new User();
        u1.setName("Abhi");
        u1.setJob("Student");
        u1.setImage("https://img.pngio.com/user-icons-free-download-png-and-svg-user-png-200_200.png");

        User u2 = new User();
        u2.setName("Sonu");
        u2.setJob("Private");
        u2.setImage("https://img.pngio.com/user-icons-free-download-png-and-svg-user-png-200_200.png");

        User u3 = new User();
        u3.setName("Mukesh");
        u3.setJob("Government");
        u3.setImage("https://img.pngio.com/user-icons-free-download-png-and-svg-user-png-200_200.png");

        User u4 = new User();
        u4.setName("Deep");
        u4.setJob("Ducat");
        u4.setImage("https://img.pngio.com/user-icons-free-download-png-and-svg-user-png-200_200.png");

        userList.add(u1);
        userList.add(u2);
        userList.add(u3);
        userList.add(u4);
    }
}

MyCustomAdapter.java

The custom adapter inflates each item layout, binds user data to the views, and exposes a custom listener interface for click events. Glide loads the image with a placeholder and error fallback.

package example.deepsingh44.com.appfirst;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;

import java.util.List;

public class MyCustomAdapter extends RecyclerView.Adapter<MyCustomAdapter.MyView> {

    private Context context;
    private List<User> userList;
    private RequestOptions options;
    private MyListener listener;

    public interface MyListener {
        void deepItemClick(View view, int position);
    }

    public MyCustomAdapter(Context context, List<User> userList) {
        this.context = context;
        this.userList = userList;
    }

    @NonNull
    @Override
    public MyView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        options = RequestOptions.placeholderOf(R.drawable.doctor)
                .error(R.drawable.ic_launcher_background)
                .centerCrop();
        View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
        return new MyView(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyView holder, int position) {
        User user = userList.get(position);
        holder.tv.setText(user.getName());
        Glide.with(context).load(user.getImage()).apply(options).into(holder.im);
    }

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

    class MyView extends RecyclerView.ViewHolder implements View.OnClickListener {
        TextView tv;
        ImageView im;

        public MyView(View itemView) {
            super(itemView);
            tv = itemView.findViewById(R.id.mytext);
            im = itemView.findViewById(R.id.myimage);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            if (listener != null) {
                listener.deepItemClick(view, getAdapterPosition());
            }
        }
    }

    public void setDeepListener(MyListener myListener) {
        listener = myListener;
    }
}

DetailActivity.java

Reads the selected User object from the Intent extras and displays the image and job title.

package example.deepsingh44.com.appfirst;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

public class DetailActivity extends AppCompatActivity {

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

        User user = (User) getIntent().getExtras().getSerializable("deep");

        ImageView im = findViewById(R.id.myimage);
        Glide.with(this).load(user.getImage()).into(im);

        TextView tv = findViewById(R.id.myjob);
        tv.setText(user.getJob());
    }
}

res/layout/activity_main.xml

Simple layout with a full-screen RecyclerView.

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

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rec" />

</LinearLayout>

res/layout/item.xml

Each grid item is wrapped in a CardView with rounded corners, elevation, and padding. The image is centered above the name.

<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardUseCompatPadding="true"
    app:cardMaxElevation="5dp"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:id="@+id/myimage"
            android:layout_gravity="center"
            android:src="@drawable/doctor" />

        <TextView
            android:id="@+id/mytext"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:padding="20dp"
            android:text="Deep Singh"
            android:textColor="@color/red" />

    </LinearLayout>

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

res/layout/activity_detail.xml

Displays the selected user's image and job in a centered vertical layout.

<?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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context="example.deepsingh44.com.appfirst.DetailActivity">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:id="@+id/myimage"
        android:layout_gravity="center" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:id="@+id/myjob"
        android:textSize="40sp"
        android:gravity="center"
        android:text="Deep Singh" />

</LinearLayout>

How It Works (Step-by-Step)

  1. App launches MainActivity — the layout activity_main.xml is inflated.
  2. RecyclerView is configured — a GridLayoutManager with 2 columns is attached, plus a DefaultItemAnimator for smooth animations.
  3. Data is prepared — a list of four User objects is created in userList().
  4. Adapter is set — MyCustomAdapter receives the list and renders each item using onCreateViewHolder and onBindViewHolder.
  5. Image loading — Glide loads each user's image URL, showing a placeholder while loading and an error icon if the URL fails.
  6. Click listener — when a card is tapped, the custom listener passes the clicked position back to MainActivity, which starts DetailActivity with the selected User in the Intent.
  7. Detail screen — DetailActivity reads the extra and displays the image and job title.

Customization Ideas

  • Change GridLayoutManager to LinearLayoutManager for a list view.
  • Use StaggeredGridLayoutManager for Pinterest-style layouts.
  • Add swipe-to-refresh with SwipeRefreshLayout around the RecyclerView.
  • Fetch users from a REST API using Retrofit instead of hardcoded data.
  • Add item animations with ItemAnimator or use DiffUtil for efficient updates.

Frequently Asked Questions

What is RecyclerView in Android?

RecyclerView is a flexible and efficient view group for displaying large data sets. Unlike ListView, it recycles views as the user scrolls, which improves performance. It supports LinearLayoutManager, GridLayoutManager, and StaggeredGridLayoutManager.

How do I implement a click listener on a RecyclerView item?

Define a custom listener interface inside the adapter, call itemView.setOnClickListener(this) inside the ViewHolder constructor, then trigger the listener in the onClick method using getAdapterPosition() to get the clicked item position.

Why is Glide used with RecyclerView?

Glide is an image loading library that handles image caching, memory management, and background downloading automatically. When used with RecyclerView, it prevents memory issues and smooth scrolling by recycling image requests efficiently.

How do I add a RecyclerView to an Android project?

Add the recyclerview and cardview dependencies to build.gradle (or use AndroidX alternatives in modern projects), then declare a RecyclerView element in the layout XML with a unique ID, and set a LayoutManager and adapter in the Activity.

What is the difference between ListView and RecyclerView?

ListView is older and does not enforce the ViewHolder pattern. RecyclerView is more efficient, forces the ViewHolder pattern, supports multiple layout managers (linear, grid, staggered), and provides built-in animations and item decorations.

Conclusion

In this tutorial, you built a complete Android RecyclerView + CardView example with a grid layout, custom adapter, Glide image loading, and click listeners that open a detail activity. This pattern is the foundation for most list-based Android apps.

You can extend this project by loading data from a REST API, adding swipe-to-refresh, using DiffUtil for efficient list updates, or migrating to AndroidX and Jetpack Compose for modern Android development.

Related topics: RecyclerView with Retrofit, SwipeRefreshLayout with Live JSON Data, RecyclerView with SearchView, TabLayout with ViewPager2, Android Retrofit API Tutorial

No comments: