Skip to content

Latest commit

 

History

History
608 lines (454 loc) · 21.8 KB

File metadata and controls

608 lines (454 loc) · 21.8 KB

RECYCLER VIEW

A RecyclerView is a modern, flexible, and efficient version of ListView. It is the standard for displaying lists in Android today.

While ListView only shows items vertically, RecyclerView can show items in:

  • Vertical Lists
  • Horizontal Lists
  • Grids (like Instagram Profile)
  • Staggered Grids (like Pinterest)

WHY RECYCLER VIEW IS REQUIRED (Vs ListView)

ListView was the old standard, but it had major flaws that RecyclerView fixes.

Feature ListView RecyclerView
Performance Low. Used findViewById repeatedly unless you manually implemented ViewHolder pattern. High. Forces you to use the ViewHolder pattern, so views are cached automatically.
Layouts Only Vertical lists. Flexible. Vertical, Horizontal, Grid, Staggered Grid.
Animations Very hard to implement (add/remove items). Built-in support for item animations.
Click Listeners Built-in onItemClick. No built-in listener. You must implement your own (gives more control).
Decoration Simple dividers only. ItemDecoration class allows complex dividers, spacing, and drawing.

ListView vs. RecyclerView Updates

While both use notifyDataSetChanged(), RecyclerView is much smarter.

Feature ListView RecyclerView
Refresh All adapter.notifyDataSetChanged() (Redraws the whole screen) adapter.notifyDataSetChanged() (Redraws the whole screen)
Add Single Item Not available. adapter.notifyItemInserted(position)
Remove Single Item Not available. adapter.notifyItemRemoved(position)
Update Single Item Not available. adapter.notifyItemChanged(position)

THE NEW ARCHITECTURE

When moving from ListView to RecyclerView, the files change slightly.

  1. LayoutManager (New Concept): In ListView, verticality was hardcoded. In RecyclerView, you must tell it how to arrange items (Linear, Grid, etc.).
  2. Adapter Changes: Instead of extending ArrayAdapter, we extend RecyclerView.Adapter.
  3. ViewHolder (Mandatory): It is no longer an optional optimization; it is a required inner class.

RecyclerView Layout Manager

Layout Style Constructor & Parameters Visual Description Real World Example
Linear Vertical (Default) new LinearLayoutManager(context)


Params: Just the Context (this or requireContext()).
Items stacked Top to Bottom. Standard list behavior. • WhatsApp Chat List

• Gmail Inbox

• Contacts App
Linear Horizontal new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)


Params:

1. Context

2. HORIZONTAL: Scroll direction.

3. reverse: false (Standard LTR), true (Start from right).
Items arranged Left to Right. A "Carousel" or side-scrolling strip. • Instagram Stories bar

• Netflix "Trending Now" row

• Weather App hourly forecast
Linear (Reverse) new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)


Params:

reverse: true
Items stacked Bottom to Top. When you add a new item, it appears at the bottom, and you start scrolled to the bottom. Inside a Chat Message (Messenger/WhatsApp) where newest text is at the bottom.
Grid Vertical new GridLayoutManager(context, 2)


Params:

1. Context

2. spanCount: 2 (Number of Columns).
Items arranged in a grid with Fixed Columns. All items have the same height/width box. • Instagram Profile (3 columns)

• Photo Gallery

• File Explorer
Grid Horizontal new GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)


Params:

spanCount: 2 (Becomes number of Rows now).
Items fill column 1 (top-down), then move right to column 2. Scroll is sideways. • Emoji Keyboard (paged horizontally)

• TV Streaming App "All Movies" list
Staggered Grid new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)


Params:

1. spanCount: 2 (Columns).

2. Orientation.

(Note: No Context needed).
Masonry Layout. Items have different heights, so they fit together like bricks without gaps. Pinterest Feed

• Google Keep Notes

• Wallpapers App
  1. StaggeredGridLayoutManager:
  • Unlike the normal Grid, this does not force squares.
  • If Image A is tall and Image B is short, Image C will slide up to fill the empty space below Image B. It looks organic and messy (in a good way).

RECYCLER VIEW IN FRAGMENTS

If using RecyclerView inside a Fragment:

  1. Use view.findViewById in onViewCreated.
  2. Pass requireContext() or getContext() to the LayoutManager.
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    RecyclerView rv = view.findViewById(R.id.rv_fragment);
    rv.setLayoutManager(new LinearLayoutManager(requireContext())); // Context needed here
    rv.setAdapter(myAdapter);
}

Project Structure (Contacts)

  1. AppData: Stores the global list of contacts.
  2. Person: The simple object class of one person.
  3. PersonGridAdapter: Handles the grid and clicks for the contact list.
  4. PersonListFragment: Shows the grid, handles Zoom & Delete logic.
  5. PersonDetailFragment: Shows full details of person.
  6. MainActivity: Handles navigation.

1. Data Model & Global Storage

Person.java (The Model) Simple class to hold data.

public class Person {
    String name;
    String details;
    int imageResId; // drawable ID (e.g., R.drawable.ic_face)
    int color;      // Background color (e.g., Color.BLUE)

    public Person(String name, String details, int imageResId, int color) {
        this.name = name;
        this.details = details;
        this.imageResId = imageResId;
        this.color = color;
    }
}

AppData.java (Global Storage) Don't forget to add android:name=".AppData" in your Manifest!

import android.app.Application;
import android.graphics.Color;
import java.util.ArrayList;
import java.util.List;

public class AppData extends Application {
    public static List<Person> personList = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        // Add dummy data
        personList.add(new Person("Ali", "Software Engineer", R.drawable.ic_launcher_foreground, Color.parseColor("#FFCDD2"))); // Red
        personList.add(new Person("Sara", "Doctor", R.drawable.ic_launcher_foreground, Color.parseColor("#C8E6C9"))); // Green
        personList.add(new Person("John", "Pilot", R.drawable.ic_launcher_foreground, Color.parseColor("#BBDEFB"))); // Blue
        personList.add(new Person("Mina", "Artist", R.drawable.ic_launcher_foreground, Color.parseColor("#E1BEE7"))); // Purple
    }
}

2. The XML Layouts

item_person_grid.xml (The Grid Box) A CardView makes the box look nice. We use CardView to easily make the image circular if needed, or just standard ImageViews.

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cardRoot"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:layout_margin="8dp"
    app:cardCornerRadius="12dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:id="@+id/layoutContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="10dp">

        <androidx.cardview.widget.CardView
            android:layout_width="80dp"
            android:layout_height="80dp"
            app:cardCornerRadius="40dp" 
            app:cardElevation="0dp">

            <ImageView
                android:id="@+id/imgPerson"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher" />
        </androidx.cardview.widget.CardView>

        <TextView
            android:id="@+id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="Name"
            android:textSize="18sp"
            android:textStyle="bold" />
    </LinearLayout>
</androidx.cardview.widget.CardView>

fragment_person_list.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="4dp"
        android:clipToPadding="false"/>
</FrameLayout>

3. The Adapter (The Logic Engine)

This handles:

  1. Click on Image -> Zoom
  2. Click on Box -> Details
  3. Long Click on Box -> Delete Alert
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class PersonGridAdapter extends RecyclerView.Adapter<PersonGridAdapter.MyViewHolder> {

    List<Person> list;
    OnItemAction listener; // Our custom interface

    // Interface to talk to Fragment
    public interface OnItemAction {
        void onPersonClick(Person p);              // Open Details
        void onImageClick(Person p);               // Zoom Image
        void onDeleteRequest(int position);        // Delete Alert
    }

    public PersonGridAdapter(List<Person> list, OnItemAction listener) {
        this.list = list;
        this.listener = listener;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_person_grid, parent, false);
        return new MyViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Person p = list.get(position);

        // 1. Set Data
        holder.tvName.setText(p.name);
        holder.imgProfile.setImageResource(p.imageResId);
        holder.container.setBackgroundColor(p.color);

        // 2. Click on the WHOLE BOX -> Open Details
        holder.itemView.setOnClickListener(v -> listener.onPersonClick(p));

        // 3. Click ONLY on IMAGE -> Zoom
        holder.imgProfile.setOnClickListener(v -> listener.onImageClick(p));

        // 4. Long Press -> Delete
        holder.itemView.setOnLongClickListener(v -> {
            listener.onDeleteRequest(position);
            return true; // Consumes the click so normal click doesn't fire
        });
    }

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

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;
        ImageView imgProfile;
        LinearLayout container;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
            imgProfile = itemView.findViewById(R.id.imgPerson);
            container = itemView.findViewById(R.id.layoutContainer);
        }
    }
}

4. The List Fragment (Controller)

This class implements the Adapter interface and handles the visual logic (Dialogs, Zooming).

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager; // For Grid
import androidx.recyclerview.widget.RecyclerView;

public class PersonListFragment extends Fragment {

    // Interface to talk to MainActivity (Navigation)
    public interface FragmentNavigation {
        void navigateToDetails(Person p);
    }

    FragmentNavigation navListener;
    PersonGridAdapter adapter;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_person_list, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // 1. Connect Navigation Listener (Activity)
        if (getActivity() instanceof FragmentNavigation) {
            navListener = (FragmentNavigation) getActivity();
        }

        RecyclerView rv = view.findViewById(R.id.recyclerView);

        // 2. Set Grid Layout (2 Columns) -> Left & Right
        rv.setLayoutManager(new GridLayoutManager(getContext(), 2));

        // 3. Create Adapter with Logic
        adapter = new PersonGridAdapter(AppData.personList, new PersonGridAdapter.OnItemAction() {
            
            @Override
            public void onPersonClick(Person p) {
                // Send info to Activity to change fragment
                if (navListener != null) navListener.navigateToDetails(p);
            }

            @Override
            public void onImageClick(Person p) {
                // Show Zoomed Image Dialog
                showZoomDialog(p);
            }

            @Override
            public void onDeleteRequest(int position) {
                // Show Alert Dialog
                showDeleteAlert(position);
            }
        });

        rv.setAdapter(adapter);
    }

    // --- LOGIC: DELETE ---
    private void showDeleteAlert(int position) {
        new AlertDialog.Builder(requireContext())
                .setTitle("Delete?")
                .setMessage("Are you sure you want to remove this person?")
                .setPositiveButton("Yes", (dialog, which) -> {
                    // 1. Remove from Data Source
                    AppData.personList.remove(position);
                    // 2. Notify Adapter (Efficient animation)
                    adapter.notifyItemRemoved(position);
                    adapter.notifyItemRangeChanged(position, AppData.personList.size());
                })
                .setNegativeButton("No", null)
                .show();
    }

    // --- LOGIC: ZOOM IMAGE ---
    private void showZoomDialog(Person p) {
        // Create a Dialog
        Dialog dialog = new Dialog(requireContext());
        
        // Create views programmatically (Dynamic)
        CardView card = new CardView(requireContext());
        card.setRadius(300f); // Make it very circular
        card.setCardElevation(10f);
        
        ImageView img = new ImageView(requireContext());
        img.setImageResource(p.imageResId);
        img.setScaleType(ImageView.ScaleType.CENTER_CROP);
        
        // Add Image to Card
        card.addView(img);

        // Set Dialog properties
        dialog.setContentView(card);
        
        // Set Dynamic Size (e.g., 800x800)
        dialog.getWindow().setLayout(800, 800);
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        
        dialog.show();
    }
}

5. Detail Fragment & Main Activity

MainActivity.java Implements the navigation interface to switch fragments.

public class MainActivity extends AppCompatActivity implements PersonListFragment.FragmentNavigation {

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

        // Load List Fragment initially
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, new PersonListFragment())
                    .commit();
        }
    }

    @Override
    public void navigateToDetails(Person p) {
        // 1. Create Detail Fragment
        PersonDetailFragment detailFrag = new PersonDetailFragment();

        // 2. Pass Data via Bundle
        Bundle args = new Bundle();
        args.putString("NAME", p.name);
        args.putString("INFO", p.details);
        detailFrag.setArguments(args);

        // 3. Switch Fragment
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, detailFrag)
                .addToBackStack(null) // Allows going back
                .commit();
    }
}

PersonDetailFragment.java Receives the bundle and shows info.

public class PersonDetailFragment extends Fragment {
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate simple layout with 2 TextViews
        return inflater.inflate(R.layout.fragment_person_detail, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TextView tvName = view.findViewById(R.id.tvDetailName);
        TextView tvInfo = view.findViewById(R.id.tvDetailInfo);

        if (getArguments() != null) {
            tvName.setText(getArguments().getString("NAME"));
            tvInfo.setText(getArguments().getString("INFO"));
        }
    }
}

1. What is int viewType in onCreateViewHolder?

Standard Definition: This parameter allows a single RecyclerView to show different types of layouts in the same list.

  • If your list looks the same everywhere (e.g., a simple contact list): You can ignore this parameter. By default, it is always 0.
  • When you actually use it: Think of a Chat App.
  • Right-side bubble: (My messages) viewType = 1
  • Left-side bubble: (Friend's messages) viewType = 2

Inside onCreateViewHolder, you would use it like this:

if (viewType == 1) {
    // Inflate my_message.xml
} else {
    // Inflate friend_message.xml
}

2. What is holder.itemView?

It represents the Root View of your row's XML file.

When you wrote your XML for the grid item (item_person_grid.xml), it looked like this:

<androidx.cardview.widget.CardView xmlns:android="..."
    android:id="@+id/cardRoot" ... >

    <LinearLayout ...>
       <ImageView ... />
       <TextView ... />
    </LinearLayout>

</androidx.cardview.widget.CardView>
  • The Concept: Android automatically grabs the top-most tag (the CardView) and saves it in a variable named itemView.
  • "Whole Box": Since the CardView wraps everything (the image, the text, the background), attaching a click listener to itemView means clicking anywhere inside that card triggers the code. It saves you from manually finding R.id.cardRoot.

3. Why not implements PersonGridAdapter.OnItemAction?

You absolutely can do that! It is a matter of coding style.

Option A: The Anonymous Way (What I showed)

  • Style: Creating the listener "on the fly" inside onViewCreated.
  • Pros: All your logic is in one place. You see the adapter creation and the click logic side-by-side.
  • Cons: onViewCreated gets a bit long and messy.

Option B: The "Implements" Way (What you asked)

  • Style: The Fragment class signs the contract.
  • Pros: Cleaner onViewCreated.
  • Cons: You have to scroll down to the bottom of the file to see what happens when you click.

How Option B looks:

// 1. Fragment implements the interface
public class PersonListFragment extends Fragment implements PersonGridAdapter.OnItemAction {

    @Override
    public void onViewCreated(...) {
         // 2. Pass 'this' because the Fragment IS the listener now
         adapter = new PersonGridAdapter(list, this);
    }

    // 3. The methods sit separately at the bottom
    @Override
    public void onPersonClick(Person p) { ... }
    
    @Override
    public void onImageClick(Person p) { ... }
}

4. Why notifyItemRemoved AND notifyItemRangeChanged?

This is the trickiest part of RecyclerView logic.

The Problem: The "Index Shift"

Imagine a list of 5 items: [A, B, C, D, E]

  • Indices: 0, 1, 2, 3, 4

You delete C (Index 2).

  1. notifyItemRemoved(2):
  • Visual: Row C vanishes with a cool animation.
  • Internal Logic: RecyclerView knows C is gone.
  • The Bug: RecyclerView still thinks D is at Index 3 and E is at Index 4. But in your actual Java List, D is now at Index 2!
  1. notifyItemRangeChanged(2, list.size()):
  • This tells RecyclerView: "Hey! Everything starting from Index 2 has shifted up. Please refresh their position numbers."
  • If you don't do this, clicking on D might crash or delete the wrong item because the adapter still sends the old position.

Cheat Sheet: When to use what?

Action Code Why?
Delete Item notifyItemRemoved(pos)

notifyItemRangeChanged(pos, size)
Mandatory. Fixes the index gap so future clicks are accurate.
Add Item (At End) notifyItemInserted(list.size() - 1) One line enough. No one shifted positions; we just added to the tail.
Add Item (In Middle) notifyItemInserted(pos)

notifyItemRangeChanged(pos, size)
Two lines. Items below the new one need to be pushed down (index +1).
Update Item notifyItemChanged(pos) One line enough. No items moved; only the content (text/image) changed.
Lazy/Easy Way notifyDataSetChanged() One line. Refreshes everything. Cons: No nice animations (flash effect).