In this tutorial, we will build a modern, list-based Android application. We will use a Single Activity Architecture, meaning our main screen will be hosted inside a Fragment. We will implement both a horizontal and a vertical RecyclerView on the home screen, and make them clickable to open a separate Detail Screen.
App Preview: What We Are Building
Interact with the phone below! You can scroll the top section horizontally, and the bottom section vertically, demonstrating the standard Android RecyclerView behavior.
Brief description for item 1…
Brief description for item 2…
Brief description for item 3…
Brief description for item 4…
Step 1: Update the Main Activity Setup
Replace the default TextView with a FragmentContainerView to host our Fragments. Then update your MainActivity.java to load the MainFragment initially.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.example.recyclerview;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
// Load MainFragment initially
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new MainFragment())
.commit();
}
}
}
Step 2: Create a Serializable Data Model
Create a simple MyItem.java class to represent a single list item. We add implements Serializable so this object can be passed between Fragments later.
package com.example.recyclerview;
import java.io.Serializable;
public class MyItem implements Serializable {
private String title;
private String description;
public MyItem(String title, String description) {
this.title = title;
this.description = description;
}
public String getTitle() { return title; }
public String getDescription() { return description; }
}
Step 3: Create the Item Card Layout
Create a layout file that defines how a single list item will look visually in our lists.
<?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="200dp"
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="vertical"
android:padding="16dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#e0e0e0"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/text_item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Item Title"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_item_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Item description..."
android:textSize="14sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
Step 4: Build the Adapter with Click Listeners
Next, build the adapter to bind your data to your layout. We create an OnItemClickListener interface so the Fragment knows exactly when an item is tapped.
package com.example.recyclerview;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<MyItem> itemList;
private OnItemClickListener listener;
// 1. Create the click listener interface
public interface OnItemClickListener {
void onItemClick(MyItem item);
}
// 2. Require the listener in the constructor
public MyAdapter(List<MyItem> itemList, OnItemClickListener listener) {
this.itemList = itemList;
this.listener = listener;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_card, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
MyItem item = itemList.get(position);
holder.title.setText(item.getTitle());
holder.description.setText(item.getDescription());
// 3. Attach the click listener to the whole item view
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(item);
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView title, description;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.text_item_title);
description = itemView.findViewById(R.id.text_item_desc);
}
}
}
Step 5: Detail Screen Layout & Class
Create the layout for the full-screen view, and the Fragment class to pull the data from the Bundle and display it.
<?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="24dp"
android:background="#FFFFFF">
<ImageView
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#e0e0e0"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/detail_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Detail Title"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/detail_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Full description goes here..."
android:textSize="18sp" />
</LinearLayout>
package com.example.recyclerview;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class DetailFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_detail, container, false);
TextView titleView = view.findViewById(R.id.detail_title);
TextView descView = view.findViewById(R.id.detail_desc);
// Retrieve the data passed from the MainFragment
if (getArguments() != null) {
MyItem selectedItem = (MyItem) getArguments().getSerializable("item_data");
if (selectedItem != null) {
titleView.setText(selectedItem.getTitle());
descView.setText(selectedItem.getDescription());
}
}
return view;
}
}
Step 6: Main Screen Layout & Class
Finally, we tie it all together in the MainFragment. Here we setup our horizontal and vertical lists, generate dummy data, and launch a FragmentTransaction when an item is clicked.
<?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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Horizontal Category List"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Vertical Details List"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
package com.example.recyclerview;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
RecyclerView recyclerHorizontal = view.findViewById(R.id.recycler_horizontal);
RecyclerView recyclerVertical = view.findViewById(R.id.recycler_vertical);
// Generate Dummy Data
List<MyItem> dummyData = new ArrayList<>();
dummyData.add(new MyItem("Item 1", "This is the first description..."));
dummyData.add(new MyItem("Item 2", "This is the second description..."));
dummyData.add(new MyItem("Item 3", "This is the third description..."));
dummyData.add(new MyItem("Item 4", "This is the fourth description..."));
// Initialize Adapter AND listen for clicks
MyAdapter adapter = new MyAdapter(dummyData, new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(MyItem item) {
// Prepare the Detail Fragment
DetailFragment detailFragment = new DetailFragment();
// Put the clicked item into a Bundle
Bundle bundle = new Bundle();
bundle.putSerializable("item_data", item);
detailFragment.setArguments(bundle);
// Swap the fragments
if (getActivity() != null) {
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, detailFragment)
.addToBackStack(null) // Allows the user to press the back button
.commit();
}
}
});
// Setup horizontal layout
recyclerHorizontal.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerHorizontal.setAdapter(adapter);
// Setup vertical layout
recyclerVertical.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
recyclerVertical.setAdapter(adapter);
return view;
}
}