RecyclerView and Adapters in Android Java

Introduction to RecyclerView

RecyclerView is a more advanced and flexible version of ListView for displaying large data sets efficiently. It recycles item views as they scroll off-screen, providing better performance and memory management.

Basic RecyclerView Setup

1. Add Dependencies

dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.cardview:cardview:1.0.0'
}

2. Layout Files

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

item_layout.xml (Individual item layout)

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/textTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description"
android:textSize="14sp"
android:layout_marginTop="4dp" />
</LinearLayout>
<ImageButton
android:id="@+id/btnDelete"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@android:drawable/ic_delete"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
</androidx.cardview.widget.CardView>

Data Model Class

public class Item {
private String title;
private String description;
private int imageResource;
private boolean isSelected;
public Item(String title, String description, int imageResource) {
this.title = title;
this.description = description;
this.imageResource = imageResource;
this.isSelected = false;
}
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getImageResource() { return imageResource; }
public void setImageResource(int imageResource) { this.imageResource = imageResource; }
public boolean isSelected() { return isSelected; }
public void setSelected(boolean selected) { isSelected = selected; }
}

ViewHolder Pattern

Basic ViewHolder

import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class ItemViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView textTitle;
public TextView textDescription;
public ImageButton btnDelete;
public ItemViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.imageView);
textTitle = itemView.findViewById(R.id.textTitle);
textDescription = itemView.findViewById(R.id.textDescription);
btnDelete = itemView.findViewById(R.id.btnDelete);
}
}

RecyclerView Adapter

Basic Adapter Implementation

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class ItemAdapter extends RecyclerView.Adapter<ItemViewHolder> {
private List<Item> itemList;
private OnItemClickListener listener;
// Interface for click events
public interface OnItemClickListener {
void onItemClick(int position);
void onDeleteClick(int position);
}
public ItemAdapter(List<Item> itemList, OnItemClickListener listener) {
this.itemList = itemList;
this.listener = listener;
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false);
return new ItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
Item currentItem = itemList.get(position);
holder.imageView.setImageResource(currentItem.getImageResource());
holder.textTitle.setText(currentItem.getTitle());
holder.textDescription.setText(currentItem.getDescription());
// Handle item click
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(position);
}
});
// Handle delete button click
holder.btnDelete.setOnClickListener(v -> {
if (listener != null) {
listener.onDeleteClick(position);
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
}

Advanced Adapter with Multiple View Types

Multiple View Types Adapter

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MultiTypeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Object> items;
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
public MultiTypeAdapter(List<Object> items) {
this.items = items;
}
@Override
public int getItemViewType(int position) {
Object item = items.get(position);
if (item instanceof Header) {
return TYPE_HEADER;
} else if (item instanceof Footer) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case TYPE_HEADER:
View headerView = inflater.inflate(R.layout.header_layout, parent, false);
return new HeaderViewHolder(headerView);
case TYPE_FOOTER:
View footerView = inflater.inflate(R.layout.footer_layout, parent, false);
return new FooterViewHolder(footerView);
default:
View itemView = inflater.inflate(R.layout.item_layout, parent, false);
return new ItemViewHolder(itemView);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case TYPE_HEADER:
Header header = (Header) items.get(position);
HeaderViewHolder headerHolder = (HeaderViewHolder) holder;
headerHolder.textHeader.setText(header.getTitle());
break;
case TYPE_ITEM:
Item item = (Item) items.get(position);
ItemViewHolder itemHolder = (ItemViewHolder) holder;
itemHolder.textTitle.setText(item.getTitle());
itemHolder.textDescription.setText(item.getDescription());
break;
case TYPE_FOOTER:
Footer footer = (Footer) items.get(position);
FooterViewHolder footerHolder = (FooterViewHolder) holder;
footerHolder.textFooter.setText(footer.getMessage());
break;
}
}
@Override
public int getItemCount() {
return items.size();
}
// Different ViewHolder classes
static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView textHeader;
HeaderViewHolder(View view) {
super(view);
textHeader = view.findViewById(R.id.textHeader);
}
}
static class FooterViewHolder extends RecyclerView.ViewHolder {
TextView textFooter;
FooterViewHolder(View view) {
super(view);
textFooter = view.findViewById(R.id.textFooter);
}
}
}

DiffUtil for Efficient Updates

Using DiffUtil for Better Performance

import androidx.recyclerview.widget.DiffUtil;
import java.util.List;
public class ItemDiffCallback extends DiffUtil.Callback {
private final List<Item> oldList;
private final List<Item> newList;
public ItemDiffCallback(List<Item> oldList, List<Item> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = oldList.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
return oldItem.getTitle().equals(newItem.getTitle());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = oldList.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
return oldItem.getTitle().equals(newItem.getTitle()) &&
oldItem.getDescription().equals(newItem.getDescription()) &&
oldItem.getImageResource() == newItem.getImageResource();
}
}
// Enhanced Adapter with DiffUtil
public class EfficientItemAdapter extends RecyclerView.Adapter<ItemViewHolder> {
private List<Item> items;
private OnItemClickListener listener;
public EfficientItemAdapter(List<Item> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}
public void updateItems(List<Item> newItems) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new ItemDiffCallback(this.items, newItems)
);
this.items.clear();
this.items.addAll(newItems);
diffResult.dispatchUpdatesTo(this);
}
// ... rest of the adapter methods
}

Complete MainActivity Implementation

import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements ItemAdapter.OnItemClickListener {
private RecyclerView recyclerView;
private ItemAdapter adapter;
private List<Item> itemList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeData();
setupRecyclerView();
}
private void initializeData() {
itemList = new ArrayList<>();
itemList.add(new Item("Java", "Object-oriented programming language", R.drawable.java_icon));
itemList.add(new Item("Kotlin", "Modern programming language for Android", R.drawable.kotlin_icon));
itemList.add(new Item("Python", "High-level programming language", R.drawable.python_icon));
itemList.add(new Item("JavaScript", "Scripting language for web development", R.drawable.js_icon));
itemList.add(new Item("C++", "General-purpose programming language", R.drawable.cpp_icon));
// Add more items as needed
}
private void setupRecyclerView() {
recyclerView = findViewById(R.id.recyclerView);
// Set layout manager
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// Set adapter
adapter = new ItemAdapter(itemList, this);
recyclerView.setAdapter(adapter);
// Add item decoration for spacing
recyclerView.addItemDecoration(new ItemDecoration(16));
}
@Override
public void onItemClick(int position) {
Item item = itemList.get(position);
Toast.makeText(this, "Clicked: " + item.getTitle(), Toast.LENGTH_SHORT).show();
// Toggle selection
item.setSelected(!item.isSelected());
adapter.notifyItemChanged(position);
}
@Override
public void onDeleteClick(int position) {
Item item = itemList.get(position);
itemList.remove(position);
adapter.notifyItemRemoved(position);
Toast.makeText(this, "Deleted: " + item.getTitle(), Toast.LENGTH_SHORT).show();
}
// Method to add new item
public void addItem(Item newItem) {
itemList.add(0, newItem); // Add to beginning
adapter.notifyItemInserted(0);
recyclerView.scrollToPosition(0);
}
// Method to update item
public void updateItem(int position, Item updatedItem) {
itemList.set(position, updatedItem);
adapter.notifyItemChanged(position);
}
}

Custom Item Decoration

import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class ItemDecoration extends RecyclerView.ItemDecoration {
private final int spacing;
public ItemDecoration(int spacing) {
this.spacing = spacing;
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
outRect.left = spacing;
outRect.right = spacing;
outRect.bottom = spacing;
// Add top margin only for the first item
if (parent.getChildAdapterPosition(view) == 0) {
outRect.top = spacing;
}
}
}

Advanced Features

1. Swipe to Delete

import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private ItemAdapter adapter;
public SwipeToDeleteCallback(ItemAdapter adapter) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.adapter = adapter;
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, 
@NonNull RecyclerView.ViewHolder viewHolder, 
@NonNull RecyclerView.ViewHolder target) {
return false; // We don't support drag & drop
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
adapter.deleteItem(position);
}
}
// Usage in MainActivity
private void enableSwipeToDelete() {
SwipeToDeleteCallback swipeToDeleteCallback = new SwipeToDeleteCallback(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(swipeToDeleteCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}

2. Drag and Drop

public class DragDropCallback extends ItemTouchHelper.Callback {
private final ItemAdapter adapter;
public DragDropCallback(ItemAdapter adapter) {
this.adapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, 
@NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder source,
@NonNull RecyclerView.ViewHolder target) {
adapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// Not used
}
}
// Add this method to your adapter
public void onItemMove(int fromPosition, int toPosition) {
Collections.swap(itemList, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
}

3. Endless Scrolling

public abstract class EndlessScrollListener extends RecyclerView.OnScrollListener {
private LinearLayoutManager layoutManager;
private int visibleThreshold = 5;
private int currentPage = 0;
private int previousTotalItemCount = 0;
private boolean loading = true;
private int startingPageIndex = 0;
public EndlessScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
@Override
public void onScrolled(@NonNull RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
int totalItemCount = layoutManager.getItemCount();
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore(currentPage, totalItemCount, view);
loading = true;
}
}
public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
}
// Usage
recyclerView.addOnScrollListener(new EndlessScrollListener(layoutManager) {
@Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
// Load more data
loadMoreData(page);
}
});

Best Practices and Performance Tips

1. ViewHolder Pattern

  • Always use ViewHolder pattern
  • Store view references in ViewHolder
  • Avoid findViewById() in onBindViewHolder()

2. Efficient Data Updates

  • Use DiffUtil for large datasets
  • Prefer notifyItemChanged() over notifyDataSetChanged()
  • Use stable IDs when possible

3. Memory Management

  • Clear references in onViewRecycled()
  • Use weak references for click listeners
  • Implement proper lifecycle management

4. Layout Optimization

  • Use ConstraintLayout for complex item layouts
  • Avoid nested weights
  • Use appropriate view types

Common Issues and Solutions

1. Item Click Not Working

// Make sure items are clickable
holder.itemView.setClickable(true);
// Or set focusable if needed
holder.itemView.setFocusable(true);

2. RecyclerView Not Showing

// Check layout manager is set
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// Check adapter is set
recyclerView.setAdapter(adapter);
// Check data is not empty
if (itemList.isEmpty()) {
// Show empty state
}

3. Performance Issues

// Use setHasFixedSize(true) if item size doesn't change
recyclerView.setHasFixedSize(true);
// Use setItemViewCacheSize() for better scrolling
recyclerView.setItemViewCacheSize(20);
// Use setRecycledViewPool() for multiple RecyclerViews
RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();
recyclerView.setRecycledViewPool(sharedPool);

RecyclerView with custom adapters provides a powerful and efficient way to display lists and grids in Android applications. By following these patterns and best practices, you can create smooth, responsive list interfaces that handle large datasets efficiently.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper