Giới thiệu
Ví dụ này minh họa cơ chế View Recycling (tái sử dụng view) của RecyclerView (thế hệ mới của ListView) thông qua một danh sách thành phố với CardView. RecyclerView chỉ tạo số lượng ViewHolder vừa đủ lấp đầy màn hình + một chút, sau đó tái sử dụng khi cuộn.
Các bước thực hiện
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project. Thêm dependency RecyclerView và CardView nếu chưa có:
dependencies {
implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation "androidx.cardview:cardview:1.0.0"
}
Bước 2: Giao diện chính (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/card_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</LinearLayout>
Bước 3: Layout item CardView (res/layout/card_row.xml)
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_country"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:textSize="18sp"
android:textStyle="bold" />
</androidx.cardview.widget.CardView>
Bước 4: Adapter (src/DataAdapter.java)
package com.app.sample;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> {
private ArrayList<String> cities;
public DataAdapter(ArrayList<String> cities) {
this.cities = cities;
}
@Override
public DataAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_row, viewGroup, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(DataAdapter.ViewHolder viewHolder, int i) {
viewHolder.tv_country.setText(cities.get(i));
}
@Override
public int getItemCount() {
return cities.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tv_country;
public ViewHolder(View view) {
super(view);
tv_country = view.findViewById(R.id.tv_country);
}
}
}
Bước 5: MainActivity (src/MainActivity.java)
package com.app.sample;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ArrayList<String> cities;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
RecyclerView recyclerView = findViewById(R.id.card_recycler_view);
recyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
cities = new ArrayList<>();
cities.add("Mumbai");
cities.add("Chennai");
cities.add("Kochin");
cities.add("Bangalore");
cities.add("Hyderabad");
cities.add("Amaravati");
cities.add("Goa");
cities.add("Trivandram");
cities.add("Coimbatore");
cities.add("Vellore");
cities.add("Salem");
cities.add("Bhubaneswar");
cities.add(""); // Mục trống test
cities.add("Cuttack");
cities.add("Mysore");
RecyclerView.Adapter adapter = new DataAdapter(cities);
recyclerView.setAdapter(adapter);
// Xử lý click item
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
GestureDetector gestureDetector = new GestureDetector(getApplicationContext(),
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) { return true; }
});
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && gestureDetector.onTouchEvent(e)) {
int position = rv.getChildAdapterPosition(child);
if (position != RecyclerView.NO_POSITION) {
Toast.makeText(getApplicationContext(), cities.get(position), Toast.LENGTH_SHORT).show();
}
}
return false;
}
@Override public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
@Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
});
}
}
Bước 6: Manifest (Manifests/AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.app.sample">
<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>
</application>
</manifest>
Chạy ứng dụng
Kết nối thiết bị, nhấn Run. Cuộn danh sách, nhấn vào item để thấy Toast hiển thị tên thành phố.

Giải thích cơ chế Recycling
- onCreateViewHolder: Chỉ được gọi khi cần ViewHolder mới (chưa có trong pool). Số lần gọi ≈ số item hiển thị trên màn hình + 1-2.
- onBindViewHolder: Được gọi mỗi khi một item cuộn vào màn hình. Gán dữ liệu mới vào ViewHolder đã tái sử dụng.
- ViewHolder Pattern: Lưu trữ reference các View con (findViewById) để tránh gọi lại tốn kém.
- RecycledViewPool: RecyclerView nội bộ quản lý pool ViewHolder theo viewType.
So sánh ListView vs RecyclerView
| Đặc điểm | ListView | RecyclerView |
|---|---|---|
| ViewHolder | Tùy chọn (getTag/setTag) | Bắt buộc (bắt buộc implement) |
| LayoutManager | Cố định (vertical list) | Linh hoạt (Linear, Grid, Staggered, Custom) |
| Item Animator | Khó tùy chỉnh | Mặc định có animation, dễ custom |
| Item Decoration | Không có | Có (divider, spacing, v.v.) |
| Hiệu năng | Tốt | Tốt hơn, linh hoạt hơn |
Kết luận
RecyclerView là lựa chọn chuẩn hiện nay. Hiểu cơ chế recycling giúp viết Adapter đúng, tránh bug hiển thị sai dữ liệu khi cuộn nhanh (do ViewHolder tái sử dụng chưa kịp bind dữ liệu mới).