ListView: nó là gì, danh sách đơn giản trong ứng dụng Android

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

ListView là thành phần Android cơ bản để hiển thị danh sách dọc, có sẵn từ API 1 (Android 1.0). ListView tạo danh sách các mục bằng ArrayAdapter và hỗ trợ bố cục ô tùy chỉnh. Hãy tìm hiểu ListView là gì, cách cấu hình nó và những cạm bẫy mà thành phần cũ nhưng vẫn được sử dụng này ẩn giấu. Tại IT Sectr, chúng tôi gặp ListView trong các dự án kế thừa và duy trì khả năng tương thích ngược với Android 4.4. Để so sánh với giải pháp thay thế hiện đại, hãy đọc bài viết về RecyclerView.

Những điểm chính

  • ListView là thành phần SDK Android tích hợp cho danh sách dọc, được giới thiệu trong Android 1.0.
  • ArrayAdapter là bộ điều hợp tiêu chuẩn để liên kết mảng dữ liệu với ListView qua toString() hoặc bố cục tùy chỉnh.
  • SimpleAdapter là bộ điều hợp cho danh sách từ dữ liệu Map, thuận tiện cho màn hình đơn giản không có ViewModel.
  • onItemClickListener là trình xử lý nhấp chuột tích hợp không yêu cầu triển khai thêm trong bộ điều hợp.
  • convertView là cơ chế tái sử dụng View trong ListView mà nhà phát triển phải triển khai thủ công (tương tự ViewHolder).

ListView trong Android là gì?

ListView là một ViewGroup từ gói android.widget, được thiết kế để hiển thị danh sách các mục có thể cuộn. ListView triển khai mẫu AdapterView: dữ liệu được truyền qua một bộ điều hợp (Adapter), chuyển đổi các mục dữ liệu thành Views hiển thị trên màn hình. ListView đã tồn tại trong Android SDK từ phiên bản đầu tiên (API 1) và không yêu cầu thư viện bổ sung.

Kiến trúc của ListView đơn giản hơn RecyclerView: LayoutManager được tích hợp sẵn (chỉ cuộn dọc), không có hoạt ảnh thay đổi, dấu phân cách được đặt qua thuộc tính XML android:divider. ListView tự động thêm OverScrollView và đánh dấu tiêu điểm, giúp đơn giản hóa việc tạo nguyên mẫu. Theo Statista (2025), khoảng 15% ứng dụng Android trong top 100 Play Store vẫn chứa ListView — chủ yếu là các dự án kế thừa với hỗ trợ tối thiểu.

Lịch sử và mức độ liên quan

ListView là thành phần danh sách chính trong Android từ 2008 đến 2014, khi RecyclerView được giới thiệu tại Google I/O 2014. Mặc dù đã bị lỗi thời một phần (soft-deprecated), ListView vẫn khả dụng trong tất cả các phiên bản Android và không bị đánh dấu @Deprecated trong SDK API 35. Nếu dự án của bạn hỗ trợ Android 4.4 (API 19) trở xuống, ListView là lựa chọn tích hợp duy nhất vì RecyclerView yêu cầu AndroidX và phụ thuộc Gradle.

Các loại bộ điều hợp: ArrayAdapter và SimpleAdapter

ArrayAdapter là bộ điều hợp phổ biến nhất cho ListView. Nó nhận List<T> và bố cục cho mỗi mục. Theo mặc định, ArrayAdapter gọi toString() trên mỗi đối tượng và hiển thị văn bản trong simple_list_item_1 (một TextView duy nhất). Để hiển thị tùy chỉnh, bạn cần ghi đè getView() hoặc truyền bố cục tùy chỉnh cho hàm tạo.

kotlin
// ListView với ArrayAdapter (văn bản đơn giản)
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val countries = listOf(
        "Nga", "Mỹ", "Đức",
        "Pháp", "Nhật Bản", "Trung Quốc", "Brazil"
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val adapter = ArrayAdapter<String>(
            this,
            android.R.layout.simple_list_item_1,
            countries
        )
        binding.listView.adapter = adapter

        // Xử lý nhấp chuột
        binding.listView.onItemClickListener =
            AdapterView.OnItemClickListener { _, _, position, _ ->
                Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
            }
    }
}

SimpleAdapter được thiết kế cho danh sách từ dữ liệu Map. Nó nhận List<Map<String, Any>> và mảng from/to để liên kết khóa Map với ID widget trong bố cục. SimpleAdapter thuận tiện cho nguyên mẫu nhanh khi không cần lớp bộ điều hợp riêng. Nhược điểm là thiếu kiểu an toàn và phức tạp với logic tùy chỉnh.

xml
<!-- Bố cục ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@color/divider"
        android:dividerHeight="1px"
        android:scrollbars="vertical" />
</LinearLayout>

Tạo bộ điều hợp tùy chỉnh với ViewHolder

Bộ điều hợp tùy chỉnh cho ListView ghi đè phương thức getView(), mà RecyclerView chia thành onCreateViewHolder + onBindViewHolder. Trong ListView, cả hai bước được kết hợp, do đó nhà phát triển phải triển khai độc lập mẫu ViewHolder qua convertView (View có thể tái sử dụng) và phương thức setTag().

kotlin
// Mô hình dữ liệu
data class Product(
    name: String,
    price: String,
    imageResId: Int
)

// Bộ điều hợp tùy chỉnh với ViewHolder
class ProductAdapter(
    context: Context,
    private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {

    // ViewHolder để lưu cache
    private class ViewHolder(view: View) {
        val nameText: TextView = view.findViewById(R.id.tvName)
        val priceText: TextView = view.findViewById(R.id.tvPrice)
        val imageView: ImageView = view.findViewById(R.id.ivProduct)
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.item_product, parent, false)
            .also { it.tag = ViewHolder(it) }

        val holder = view.tag as ViewHolder
        val product = getItem(position)

        holder.nameText.text = product.name
        holder.priceText.text = product.price
        holder.imageView.setImageResource(product.imageResId)

        return view
    }
}

Sự khác biệt chính với RecyclerView: trong ListView, phương thức getView() được gọi mỗi khi một mục xuất hiện trên màn hình. Nếu không sử dụng convertView và ViewHolder, mỗi lần cuộn tạo ra một đối tượng View mới, dẫn đến rò rỉ bộ nhớ và độ trễ trên danh sách hơn 50 mục. ViewHolder với view.tag là tối ưu hóa bắt buộc cho ListView với bố cục tùy chỉnh.

Hạn chế của ListView: tại sao Google khuyên dùng RecyclerView

ListView có bốn hạn chế về kiến trúc mà Google đã khắc phục trong RecyclerView: (1) chỉ hướng dọc — không có lưới, danh sách ngang hoặc tầng; (2) không có thông báo chi tiết — bất kỳ thay đổi dữ liệu nào cũng kích hoạt vẽ lại toàn bộ qua notifyDataSetChanged(), gây giật; (3) không có hoạt ảnh thêm/xóa mục tích hợp; (4) không có phân chia trách nhiệm — LayoutManager được tích hợp, ItemAnimator không có.

xml
<!-- item_product.xml — bố cục mục ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp">

    <ImageView
        android:id="@+id/ivProduct"
        android:layout_width="56dp"
        android:layout_height="56dp"
        android:scaleType="centerCrop" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical"
        android:paddingStart="12dp">

        <TextView
            android:id="@+id/tvName"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvPrice"
            android:textSize="14sp"
            android:textColor="@color/price" />
    </LinearLayout>
</LinearLayout>

Ý nghĩa thực tế: trên danh sách lên đến 20 mục, ListView hoạt động nhanh như RecyclerView. Ở 100+ mục, sự khác biệt trở nên rõ rệt: ListView bắt đầu giảm FPS do thiếu tính toán diff và ViewHolder bắt buộc. Đối với danh sách cập nhật thường xuyên (chat, feed, log), ListView không phù hợp — hãy sử dụng RecyclerView với Paging 3. Tại IT Sectr, chúng tôi di chuyển ListView sang RecyclerView mỗi khi làm việc trên màn hình kế thừa có hơn 50 mục.

Cấu hình ListView qua thuộc tính XML

ListView cung cấp một tập hợp các thuộc tính XML để cấu hình nhanh không cần mã: android:divider (màu/drawable dấu phân cách), android:dividerHeight (độ dày), android:scrollbars (thanh cuộn), android:fastScrollEnabled (cuộn nhanh qua tay cầm), android:choiceMode (chế độ chọn: singleChoice, multipleChoice). Thuộc tính android:entries cho phép điền danh sách từ mảng tài nguyên (string-array) mà không cần bộ điều hợp.

Thuộc tínhMô tảGiá trị ví dụ
android:dividerMàu hoặc drawable dấu phân cách@color/gray_light
android:dividerHeightĐộ dày dấu phân cách tính bằng px/dp1dp
android:entriesMảng chuỗi từ tài nguyên@array/countries
android:choiceModeChế độ chọn mụcsingleChoice
android:fastScrollEnabledCuộn nhanhtrue
android:overlapAnchorNeo cho danh sách bật lên (API 21+)true

Chế độ chọn: choiceMode="singleChoice" bật kiểu RadioButton nơi mục được chọn được đánh dấu. choiceMode="multipleChoice" — hộp kiểm cho nhiều mục. Dữ liệu chọn có sẵn qua listView.checkedItemPosition hoặc listView.checkedItemIds. Quan trọng: với choiceMode, ListView sử dụng StateListDrawable cho nền — hãy đặt bộ chọn của riêng bạn nếu màu xanh tiêu chuẩn không phù hợp với thiết kế.

ListView vs RecyclerView: so sánh tính năng

Sự lựa chọn giữa ListView và RecyclerView phụ thuộc vào yêu cầu dự án. RecyclerView là tiêu chuẩn hiện đại, nhưng ListView có thể hợp lý cho các màn hình đơn giản với chi phí bảo trì mã kế thừa tối thiểu. So sánh trên tất cả các tham số chính:

Tham sốListViewRecyclerView
Kích thước thư việnTích hợp trong SDK (≈60 KB)AndroidX (≈300 KB với phụ thuộc)
Phiên bản tối thiểuAPI 1API 14 (qua AndroidX)
HướngChỉ dọcDọc, ngang, lưới, so le
ViewHolderTriển khai thủ côngBắt buộc theo kiến trúc
Hoạt ảnhChỉ chuyển tiếp mờ dầnDefaultItemAnimator (add, remove, move, change)
Cập nhật chi tiếtKhông (chỉ notifyDataSetChanged)notifyItemInserted/Removed/Changed
Trình nghe nhấponItemClickListener (tích hợp)Qua ViewHolder (không tích hợp)
Hiệu suấtĐủ cho danh sách lên đến 50 mục60 FPS ổn định cho 1000+ mục

Kết luận: cho dự án mới, hãy sử dụng RecyclerView. Chỉ giữ ListView trong mã kế thừa nơi việc di chuyển quá tốn kém. Theo hướng dẫn Material Design 3 (2026), danh sách ở mọi độ phức tạp nên sử dụng RecyclerView để đáp ứng các tiêu chuẩn hiệu suất và khả năng tiếp cận hiện đại.

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

ListView đã lỗi thời hay chưa?

ListView không bị đánh dấu @Deprecated trong Android SDK API 35, nhưng Google không khuyến nghị sử dụng nó trong các dự án mới (soft-deprecated). Thành phần này vẫn hoạt động trong tất cả các phiên bản Android. RecyclerView là sự thay thế chính thức từ năm 2014. Nếu bạn bắt đầu một dự án mới — hãy chọn RecyclerView. Nếu bạn đang duy trì dự án hiện có — ListView sẽ tiếp tục hoạt động mà không thay đổi.

Làm thế nào để thêm header/footer vào ListView?

ListView hỗ trợ các phương thức addHeaderView(View, Object, boolean)addFooterView(View, Object, boolean). Header và footer được thêm vào danh sách mục chung nhưng không tham gia vào bộ điều hợp — chúng luôn được hiển thị ở đầu và cuối danh sách. Quan trọng: thêm header/footer trước khi đặt bộ điều hợp (setAdapter()), nếu không chúng sẽ không được bao gồm. Sau khi thêm header, ID vị trí của các mục bị dịch chuyển theo số lượng header.

Tại sao ListView không cuộn được bên trong ScrollView?

Cả ListViewScrollView đều xử lý cử chỉ dọc, tạo ra xung đột: ScrollView bên ngoài chặn cuộn và ListView không cuộn được. Giải pháp: (1) không lồng ListView bên trong ScrollView — sử dụng RecyclerView với NestedScrollingChild, (2) thay thế ScrollView bằng NestedScrollView (API 21+), (3) sử dụng ListView với chiều cao cố định (android:layout_height="...dp") bên trong ScrollView, nhưng điều này sẽ phá vỡ khả năng thích ứng.

Làm thế nào để xóa ListView và cập nhật dữ liệu?

Việc xóa dữ liệu trong ListView phụ thuộc vào loại bộ điều hợp. Đối với ArrayAdapter: adapter.clear()adapter.addAll(newList) sau đó adapter.notifyDataSetChanged(). Đối với bộ điều hợp tùy chỉnh: cập nhật danh sách dữ liệu và gọi notifyDataSetChanged(). Lưu ý: listView.invalidateViews() chỉ vẽ lại Views nhưng không cập nhật dữ liệu. Luôn thay đổi dữ liệu qua bộ điều hợp, không trực tiếp.

Làm thế nào để thay đổi chiều cao của mục ListView?

Chiều cao của mục ListView được đặt trong bố cục mục. Nếu bố cục sử dụng layout_height="wrap_content", chiều cao sẽ điều chỉnh theo nội dung. Để có chiều cao cố định cho tất cả các mục, hãy sử dụng bộ điều hợp tùy chỉnh đặt LayoutParams trong getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). Đối với chiều cao khác nhau, hãy sử dụng RecyclerView với các loại view khác nhau.

Tóm tắt

  • ListView là thành phần SDK Android cơ bản cho danh sách dọc, có sẵn từ API 1.
  • ArrayAdapter là bộ điều hợp đơn giản nhất cho danh sách chuỗi; cho bố cục tùy chỉnh cần ghi đè getView().
  • ViewHolder trong ListView được triển khai thủ công qua convertView và view.tag — yêu cầu hiệu suất bắt buộc.
  • ListView hỗ trợ choiceMode (single/multiple), header/footer, fastScroll và android:entries không cần bộ điều hợp.
  • Hạn chế ListView: chỉ hướng dọc, không có hoạt ảnh, vẽ lại toàn bộ khi có bất kỳ thay đổi nào.
  • ListView không bị đánh dấu là lỗi thời, nhưng Google khuyên dùng RecyclerView cho tất cả dự án mới.
  • Cho danh sách lên đến 50 mục, ListView là đủ; cho bất cứ thứ gì phức tạp hơn — hãy sử dụng RecyclerView.

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