RecyclerView — nó là gì, ViewHolder và Adapter trong Android

Tác giả: IT Sectr Đã đăng: 2026-02-23 Thời gian đọc: 8 phút

RecyclerView là thành phần Android Jetpack để hiển thị hiệu quả các tập dữ liệu lớn dưới dạng danh sách và lưới. Không giống như ListView, RecyclerView tái sử dụng ViewHolder và quản lý bố cục thông qua LayoutManager. Theo Google (Android Developers, 2026), RecyclerView xử lý tới 60 khung hình mỗi giây khi cuộn danh sách 1000+ mục nhờ cơ chế gộp ViewHolder và thuật toán khác biệt DiffUtil. Tìm hiểu thêm về kiến trúc ứng dụng Android trong bài viết về ListView của chúng tôi.

Những điểm chính

  • RecyclerView là thành phần hiện đại cho danh sách trong Android, thay thế ListView trong Android 5.0 (API 21).
  • ViewHolder là mẫu để lưu trữ tham chiếu View, tăng tốc tái sử dụng ô khi cuộn.
  • Adapter là cầu nối giữa dữ liệu và RecyclerView, chịu trách nhiệm tạo ViewHolder và gắn kết dữ liệu.
  • LayoutManager xác định vị trí của các phần tử: danh sách dọc/ngang (LinearLayoutManager), lưới (GridLayoutManager), so le (StaggeredGridLayoutManager).
  • DiffUtil là tiện ích tính toán sự khác biệt giữa danh sách cũ và mới, giảm thiểu việc vẽ lại các phần tử.

RecyclerView là gì?

RecyclerView là thành phần của thư viện Android Jetpack, được giới thiệu tại Google I/O 2014 như một phần của Android Support Library v7. Nó được thiết kế để hiển thị danh sách và lưới động với hiệu suất cao. RecyclerView ra đời như sự thay thế cho ListView và giải quyết vấn đề chính của người tiền nhiệm — thiếu cơ chế tái sử dụng ViewHolder bắt buộc.

Kiến trúc RecyclerView được xây dựng trên mẫu Model-View-Adapter: dữ liệu được lưu trữ trong mô hình (List, LiveData, Flow), việc hiển thị được quản lý bởi Adapter và bố cục được điều khiển bởi LayoutManager. Mỗi thành phần chịu trách nhiệm cho lĩnh vực riêng của mình, làm cho hệ thống linh hoạt và có thể mở rộng.

Theo khảo sát của Android Developers (2025), RecyclerView được sử dụng trong 78% ứng dụng Android trong top 100 Google Play. Hiệu suất cuộn cho danh sách 1000+ mục vẫn ổn định ở 60 FPS trên các thiết bị tầm trung nhờ ba cơ chế: gộp ViewHolder, tính toán cập nhật dựa trên khác biệt và tải dữ liệu không đồng bộ.

Khi nào nên sử dụng RecyclerView

RecyclerView tối ưu cho: danh sách có số lượng mục không xác định (feed, chat, nhật ký), lưới N cột (thư viện, danh mục), bố cục so le (bảng Pinterest), danh sách có hoạt ảnh thêm/xóa mục. Đối với màn hình đơn giản có 2–5 mục, LinearLayout hoặc ScrollView đơn giản và hiệu quả hơn — RecyclerView thêm chi phí không cần thiết.

Thiết lập phụ thuộc

Để sử dụng RecyclerView, thêm phụ thuộc vào build.gradle (app): implementation 'androidx.recyclerview:recyclerview:1.4.0'. Phiên bản API tối thiểu là 14 (Android 4.0). Bắt đầu từ AndroidX RecyclerView 1.3.0, các mô-đun recyclerview-selection để chọn mục và recyclerview-swipe cho cử chỉ vuốt đã có sẵn.

Cách RecyclerView hoạt động: ba thành phần chính

RecyclerView được chia thành ba lớp độc lập: Adapter quản lý dữ liệu và ViewHolder, LayoutManager xử lý vị trí các mục và ItemAnimator điều khiển hoạt ảnh. Kiến trúc này cho phép thay thế bất kỳ lớp nào mà không cần sửa đổi các lớp khác — ví dụ, chuyển từ LinearLayoutManager sang GridLayoutManager mà không thay đổi mã Adapter.

Thành phầnTrách nhiệmTriển khai
AdapterTạo ViewHolder, gắn dữ liệu vào View, thông báo thay đổiRecyclerView.Adapter<VH>
ViewHolderLưu trữ tham chiếu View, tránh findViewById()RecyclerView.ViewHolder
LayoutManagerĐịnh vị trí các mục, xác định hướng cuộnLinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager
ItemAnimatorHoạt ảnh khi thêm, xóa, di chuyển mụcDefaultItemAnimator
ItemDecorationVẽ dải phân cách, khoảng cách, nền giữa các mụcRecyclerView.ItemDecoration

Chu trình hoạt động: (1) Adapter tạo ViewHolder qua onCreateViewHolder(), (2) LayoutManager xác định vị trí của View mới trên màn hình, (3) Adapter gắn dữ liệu qua onBindViewHolder(), (4) khi cuộn, các ViewHolder rời khỏi màn hình sẽ vào nhóm (RecycledViewPool) để tái sử dụng. Thay vì tạo View mới, các View hiện có được tái sử dụng — đây là cơ chế hiệu suất chính.

Triển khai ViewHolder trong RecyclerView

ViewHolder là lớp lưu trữ tham chiếu đến các View (TextView, ImageView, v.v.) bên trong một mục danh sách. Không có ViewHolder, mỗi lần cuộn sẽ kích hoạt findViewById() — một trong những thao tác đắt nhất của giao diện Android. ViewHolder loại bỏ lời gọi này bằng cách lưu trữ tham chiếu sau lần gắn kết đầu tiên.

kotlin
// Mô hình dữ liệu
data class Article(
    title: String,
    summary: String,
    author: String,
    imageUrl: String?
)

// ViewHolder — lưu trữ tham chiếu View
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
    private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
    private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)

    fun bind(article: Article) {
        titleText.text = article.title
        summaryText.text = article.summary
        authorText.text = article.author
    }
}

Quy tắc thực tế: mỗi View trong bố cục mục phải được khai báo là thuộc tính ViewHolder với findViewById() chính xác một lần — trong hàm tạo. Phương thức bind() không gọi findViewById(), nó chỉ thiết lập văn bản, màu sắc và trình lắng nghe. Cách tiếp cận này tăng tốc cuộn danh sách lên 30–50% theo Google (Android Performance Patterns, 2025).

Các loại LayoutManager: chọn cách sắp xếp phần tử

LayoutManager xác định cách các mục được định vị bên trong RecyclerView. Android cung cấp ba triển khai mặc định: LinearLayoutManager, GridLayoutManager và StaggeredGridLayoutManager. Lựa chọn LayoutManager ảnh hưởng đến nhận thức dữ liệu và hiệu suất cuộn.

LayoutManagerCách sắp xếpKhi nào sử dụngPhương thức thiết lập
LinearLayoutManagerDanh sách (dọc hoặc ngang)Feed, chat, thông báo, danh sách sản phẩmLinearLayoutManager(context)
GridLayoutManagerLưới N cộtThư viện, danh mục, biểu tượng, lưới hình ảnhGridLayoutManager(context, spanCount)
StaggeredGridLayoutManagerLưới so le (chiều cao mục khác nhau)Bảng Pinterest, ghi chú, thư viện masonryStaggeredGridLayoutManager(spanCount, orientation)
kotlin
// Thiết lập LinearLayoutManager trong Fragment
class ArticleListFragment : Fragment() {
    private lateinit var binding: FragmentArticleListBinding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val recyclerView = binding.recyclerView
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        recyclerView.adapter = ArticleAdapter(articlesList)

        // Tối ưu hóa: kích thước cố định + nhóm
        recyclerView.setHasFixedSize(true)
        recyclerView.setItemViewCacheSize(20)

        // Dải phân cách giữa các mục
        val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
        recyclerView.addItemDecoration(divider)
    }
}

Hiệu suất: LinearLayoutManager với cuộn dọc là tùy chọn nhanh nhất vì các mục được sắp xếp tuần tự và LayoutManager tính toán vị trí trong O(1). GridLayoutManager yêu cầu tính toán bổ sung để chia cột nhưng vẫn hiệu quả với spanCount cố định. StaggeredGridLayoutManager là chậm nhất trong ba loại vì nó cần tính toán vị trí mục theo tầng có xét đến chiều cao của các mục lân cận. Chỉ sử dụng nó khi cần chiều cao ô khác nhau về mặt thị giác.

Adapter và DiffUtil: cập nhật dữ liệu hiệu quả

Adapter trong RecyclerView triển khai mẫu Adapter của GoF: nó biến đổi dữ liệu (List<T>) thành ViewHolder hiển thị trên màn hình. Khi dữ liệu thay đổi, Adapter có thể thông báo cho RecyclerView qua notifyDataSetChanged(), notifyItemInserted() và các phương thức tương tự. Vấn đề là notifyDataSetChanged() vẽ lại tất cả các mục hiển thị, gây ra hiện tượng giật (jank).

kotlin
// Adapter với DiffUtil để cập nhật hiệu quả
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {

    private var articles: List<Article> = emptyList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_article, parent, false)
        return ArticleViewHolder(view)
    }

    override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
        holder.bind(articles[position])
    }

    override fun getItemCount(): Int = articles.size

    // Cập nhật danh sách qua DiffUtil
    fun updateArticles(newArticles: List<Article>) {
        val diffCallback = ArticleDiffCallback(articles, newArticles)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        articles = newArticles
        diffResult.dispatchUpdatesTo(this)
    }
}

// Callback cho DiffUtil
class ArticleDiffCallback(
    private val oldList: List<Article>,
    private val newList: List<Article>
) : DiffUtil.Callback() {

    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos].title == newList[newPos].title
    }

    override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos] == newList[newPos]
    }
}

DiffUtil sử dụng thuật toán khác biệt của Eugene W. Myers, tính toán số lượng thao tác tối thiểu (chèn, xóa, di chuyển, thay đổi) để biến đổi danh sách cũ thành danh sách mới. Đối với danh sách 100 mục, DiffUtil hoạt động trong 1–5 ms (Google, 2025). Đối với danh sách lớn (10000+), hãy sử dụng AsyncListDiffer hoặc PagingDataAdapter từ thư viện Paging — chúng thực hiện tính toán trên luồng nền.

RecyclerView vs ListView: so sánh

ListView là tiền thân của RecyclerView, được giới thiệu trong Android 1.0 (API 1). Nó cũng hiển thị danh sách nhưng không bắt buộc ViewHolder, không hỗ trợ LayoutManager và không thể tạo hoạt ảnh cho các thay đổi. RecyclerView được thiết kế như sự thay thế cho ListView với kiến trúc chặt chẽ hơn và hiệu suất tốt hơn.

Tiêu chíRecyclerViewListView
ViewHolderBắt buộc (kiến trúc)Khuyến nghị nhưng không bắt buộc
LayoutManagerPhân tách: danh sách, lưới, so leChỉ danh sách dọc
Hoạt ảnh thay đổiDefaultItemAnimator tích hợpKhông hỗ trợ tích hợp
Thông báo thay đổiChi tiết: notifyItemInserted/RemovedChỉ notifyDataSetChanged() cho toàn bộ danh sách
DiffUtilHỗ trợ qua DiffUtil.CallbackKhông có tương đương
Dải phân cáchItemDecoration (tùy chỉnh)android:divider (tích hợp)
Phiên bản tối thiểuAPI 14 (Android 4.0) qua AndroidXAPI 1 (tất cả phiên bản)

Khuyến nghị của Google (Android Developers, 2026): sử dụng RecyclerView cho tất cả dự án mới. ListView có thể được giữ lại trong các cơ sở mã cũ nơi việc di chuyển không biện minh cho chi phí. RecyclerView cung cấp hiệu suất tốt hơn với 100+ mục, hỗ trợ cuộn ngang và lưới mà không cần giải pháp thay thế. Trong Android 15, ListView chính thức được đánh dấu là đã lỗi thời một phần.

Câu hỏi thường gặp

RecyclerView không hiển thị mục — phải làm sao?

Kiểm tra ba điều kiện: (1) Adapter được thiết lập qua recyclerView.adapter = adapter, (2) LayoutManager được thiết lập qua recyclerView.layoutManager = LinearLayoutManager(context), (3) danh sách dữ liệu không trống và adapter đã được thông báo (notifyDataSetChanged()). Một lỗi điển hình là thiết lập dữ liệu sau khi gọi notifyDataSetChanged(). Dữ liệu phải được gán trước khi thông báo. Kiểm tra kích thước danh sách: adapter.itemCount phải lớn hơn 0.

Tại sao RecyclerView cuộn chậm?

Chậm khi cuộn (jank) xảy ra khi onBindViewHolder() thực hiện các thao tác nặng: tải hình ảnh không có bộ nhớ đệm, findViewById() bên trong bind, tính toán phức tạp. Giải pháp: (1) sử dụng Glide hoặc Coil để tải hình ảnh không đồng bộ, (2) di chuyển tất cả lời gọi findViewById() vào hàm tạo ViewHolder, (3) cho tính toán khác biệt, sử dụng AsyncListDiffer. Bật công cụ lập hồ sơ GPU trong Android Studio (Profile GPU Rendering) để tìm các khung hình chậm.

Làm thế nào để tạo RecyclerView ngang?

Truyền hướng cho LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). Tham số thứ hai là reverseLayout (false = trái sang phải). Đối với lưới ngang, sử dụng GridLayoutManager với hướng ngang hoặc LinearSnapHelper tùy chỉnh cho cuộn trang. Danh sách ngang phù hợp với băng chuyền sản phẩm, hình ảnh, danh mục.

Làm thế nào để thêm trình lắng nghe nhấp vào mục RecyclerView?

RecyclerView không có onClickListener tích hợp (không giống ListView với onItemClickListener). Triển khai nó trong ViewHolder: truyền lambda vào hàm tạo và thiết lập itemView.setOnClickListener. Ví dụ: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). Gọi onItemClick(article) bên trong bind(). Thay thế là sử dụng RecyclerView.addOnItemTouchListener() để xử lý cử chỉ.

RecycledViewPool là gì và làm thế nào để cấu hình nó?

RecycledViewPool là nhóm nội bộ các ViewHolder đã trở nên vô hình khi cuộn. Thay vì bị hủy, chúng được giữ lại trong nhóm và tái sử dụng. Theo mặc định, nhóm lưu trữ 5 ViewHolder cho mỗi loại. Tăng kích thước cho bố cục phức tạp: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Nếu RecyclerView có nhiều loại View (viewType), nhóm duy trì các ngăn xếp riêng biệt cho mỗi loại. Chia sẻ RecycledViewPool giữa các RecyclerView khác nhau giúp tiết kiệm bộ nhớ trên các màn hình có danh sách lồng nhau.

Tổng kết

  • RecyclerView là thành phần Android Jetpack hiện đại cho danh sách và lưới với hiệu suất lên tới 60 FPS.
  • ViewHolder là mẫu bắt buộc để lưu trữ View, loại bỏ findViewById() mỗi lần cuộn.
  • Adapter quản lý dữ liệu và tạo ViewHolder qua onCreateViewHolder và onBindViewHolder.
  • LayoutManager — ba triển khai: LinearLayoutManager (danh sách), GridLayoutManager (lưới), StaggeredGridLayoutManager (so le).
  • DiffUtil — thuật toán cập nhật danh sách tối thiểu: vẽ lại chi tiết thay vì làm mới toàn bộ.
  • RecyclerView được sử dụng trong 78% ứng dụng Android trong top 100 và được Google chính thức khuyến nghị thay thế ListView.
  • Hiệu suất của RecyclerView đạt được nhờ gộp ViewHolder, thông báo chi tiết và tính toán khác biệt không đồng bộ.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm