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)
-
App launches MainActivity — the layout
activity_main.xmlis inflated. -
RecyclerView is configured — a
GridLayoutManagerwith 2 columns is attached, plus aDefaultItemAnimatorfor smooth animations. -
Data is prepared — a list of four
Userobjects is created inuserList(). -
Adapter is set —
MyCustomAdapterreceives the list and renders each item usingonCreateViewHolderandonBindViewHolder. - Image loading — Glide loads each user's image URL, showing a placeholder while loading and an error icon if the URL fails.
-
Click listener — when a card is tapped, the custom listener passes
the clicked position back to
MainActivity, which startsDetailActivitywith the selectedUserin the Intent. -
Detail screen —
DetailActivityreads the extra and displays the image and job title.
Customization Ideas
- Change
GridLayoutManagertoLinearLayoutManagerfor a list view. - Use
StaggeredGridLayoutManagerfor Pinterest-style layouts. - Add swipe-to-refresh with
SwipeRefreshLayoutaround the RecyclerView. - Fetch users from a REST API using Retrofit instead of hardcoded data.
- Add item animations with
ItemAnimatoror useDiffUtilfor 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:
Post a Comment