ListView:是什么,Android 应用中的简单列表

作者: IT Sectr 发布日期: 2026-02-23 阅读时间: 8 分钟

ListView — 用于显示垂直列表的基本 Android 组件,从 API 1(Android 1.0)开始可用。ListView 使用 ArrayAdapter 从元素创建列表,并支持自定义单元格布局。了解什么是 ListView、如何配置它以及这个古老但仍然使用的组件隐藏了哪些陷阱。在 IT Sectr,我们在遗留项目中遇到 ListView,并保持与 Android 4.4 的向后兼容性。要与现代替代方案进行比较,请阅读 关于 RecyclerView 的文章

关键要点

  • ListView — 用于垂直列表的内置 Android SDK 组件,出现在 Android 1.0 中。
  • ArrayAdapter — 通过 toString() 或自定义布局将数据数组绑定到 ListView 的标准适配器。
  • SimpleAdapter — 用于 Map 数据列表的适配器,适合没有 ViewModel 的简单屏幕。
  • onItemClickListener — 内置的点击处理器,不需要在适配器中进行额外实现。
  • convertView — ListView 中 View 的复用机制,开发者必须手动实现(类似于 ViewHolder)。

什么是 Android 中的 ListView?

ListView — 是来自 android.widget 包的 View 组,用于显示可滚动的元素列表。ListView 实现了 AdapterView 模式:数据通过适配器(Adapter)传递,适配器将数据元素转换为屏幕上显示的 View。ListView 从第一个版本(API 1)就存在于 Android SDK 中,不需要额外的库。

架构 ListView 比 RecyclerView 更简单:LayoutManager 是内置的(仅垂直滚动),没有更改动画,分隔器通过 XML 属性 android:divider 设置。ListView 自动添加 OverScrollView 和焦点高亮,简化了原型设计。根据 Statista(2025)的数据,Play Store 前 100 名中约 15% 的 Android 应用仍然包含 ListView — 主要是支持最少的遗留项目。

历史和现状

ListView 从 2008 年到 2014 年是 Android 中主要的列表组件,直到 Google I/O 2014 上推出了 RecyclerView。尽管处于部分弃用状态(soft-deprecated),ListView 仍然在 Android 的所有版本中可用,并且在 SDK API 35 中没有被标记为 @Deprecated。如果您的项目支持 Android 4.4(API 19)及更低版本,ListView 是唯一的内置选择,因为 RecyclerView 需要 AndroidX 和 gradle 依赖。

适配器类型:ArrayAdapter 和 SimpleAdapter

ArrayAdapter — 最流行的 ListView 适配器。它接受 List<T> 和每个元素的布局。默认情况下,ArrayAdapter 为每个对象调用 toString() 并在 simple_list_item_1(一个 TextView)中显示文本。对于自定义显示,需要重写 getView() 或在构造函数中传入自定义布局。

kotlin
// 使用 ArrayAdapter 的 ListView(简单文本)
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val countries = listOf(
        "俄罗斯", "美国", "德国",
        "法国", "日本", "中国", "巴西"
    )

    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

        // 点击处理
        binding.listView.onItemClickListener =
            AdapterView.OnItemClickListener { _, _, position, _ ->
                Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
            }
    }
}

SimpleAdapter 用于来自 Map 数据的列表。它接受 List<Map<String, Any>> 和一个 from/to 数组,用于将 Map 键与布局中 widget 的 ID 绑定。SimpleAdapter 方便快速原型制作,当不需要单独的适配器类时。缺点 — 缺乏 type-safe 类型化和自定义逻辑的复杂性。

xml
<!-- 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>

创建带有 ViewHolder 的自定义适配器

自定义适配器 为 ListView 重写 getView() 方法,而 RecyclerView 将其分为 onCreateViewHolder + onBindViewHolder。在 ListView 中,这两个步骤是合并的,因此开发者必须通过 convertView(可复用的 View)和 setTag() 标签独立实现 ViewHolder 模式。

kotlin
// 数据模型
data class Product(
    name: String,
    price: String,
    imageResId: Int
)

// 带有 ViewHolder 的自定义适配器
class ProductAdapter(
    context: Context,
    private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {

    // 用于缓存的 ViewHolder
    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
    }
}

与 RecyclerView 的关键区别:在 ListView 中,getView() 方法在元素每次出现在屏幕上时被调用。如果不使用 convertView 和 ViewHolder,每次滚动都会创建新的 View 对象,这会导致内存泄漏和 50 个以上元素列表的卡顿。带有 view.tag 的 ViewHolder — 对于具有自定义布局的 ListView 是强制优化。

ListView 的限制:为什么 Google 推荐 RecyclerView

ListView 有四个架构限制,Google 在 RecyclerView 中修复了这些限制:(1)仅垂直方向 — 没有网格、水平列表或级联;(2)缺乏点对点通知 — 任何数据更改都会通过 notifyDataSetChanged() 导致完整重绘,这会产生抖动;(3)没有内置的添加/删除元素动画;(4)没有职责分离 — LayoutManager 是内置的,ItemAnimator 不存在。

xml
<!-- item_product.xml — 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>

实际后果:在最多 20 个元素的列表上,ListView 和 RecyclerView 一样快。在 100 个以上元素时,差异变得明显:由于缺乏差异计算和强制 ViewHolder,ListView 开始丢失 FPS。对于频繁更新的列表(聊天、信息流、日志),ListView 不适合 — 请使用带有 Paging 3 的 RecyclerView。在 IT Sectr,我们在处理超过 50 个元素的遗留屏幕时,都会将 ListView 迁移到 RecyclerView。

通过 XML 属性配置 ListView

ListView 提供了一组 XML 属性用于无需代码的快速配置:android:divider(分隔器的颜色/图片)、android:dividerHeight(厚度)、android:scrollbars(滚动条)、android:fastScrollEnabled(通过手柄快速滚动)、android:choiceMode(选择模式:singleChoice、multipleChoice)。android:entries 属性允许从资源数组(string-array)填充列表而无需适配器。

属性描述示例值
android:divider分隔器的颜色或 drawable@color/gray_light
android:dividerHeight分隔器的厚度(px/dp)1dp
android:entries来自资源的字符串数组@array/countries
android:choiceMode元素选择模式singleChoice
android:fastScrollEnabled快速滚动true
android:overlapAnchor弹出列表的锚点(API 21+)true

选择模式:choiceMode="singleChoice" 启用 RadioButton 样式,选中的元素被高亮。choiceMode="multipleChoice" — 多元素的复选框。选择数据可通过 listView.checkedItemPositionlistView.checkedItemIds 获取。重要提示:在 choiceMode 下,ListView 使用 StateListDrawable 作为背景 — 如果默认蓝色不符合设计,请设置自己的选择器。

ListView vs RecyclerView:功能比较

选择 ListView 和 RecyclerView 取决于项目的需求。RecyclerView 是现代标准,但对于维护旧代码成本最低的简单屏幕,ListView 可能是合理的。所有关键参数的比较:

参数ListViewRecyclerView
库大小内置于 SDK(约 60 KB)AndroidX(含依赖约 300 KB)
最低版本API 1API 14(通过 AndroidX)
方向仅垂直垂直、水平、网格、级联
ViewHolder手动实现架构强制要求
动画仅淡入淡出过渡DefaultItemAnimator(添加、移除、移动、更改)
点对点更新无(仅 notifyDataSetChanged)notifyItemInserted/Removed/Changed
点击监听器onItemClickListener(内置)通过 ViewHolder(无内置)
性能对于最多 50 个元素的列表足够1000+ 元素稳定 60 FPS

结论:对于新项目,请使用 RecyclerView。仅在迁移成本不合理地高的遗留代码中保留 ListView。根据 Material Design 3(2026)指南,任何复杂度的列表都应使用 RecyclerView 以符合现代性能和可访问性标准。

常见问题

ListView 是否已弃用?

ListView 在 Android SDK API 35 中没有被标记为 @Deprecated,但 Google 不建议在新项目中(soft-deprecated)使用它。该组件在所有 Android 版本中仍然可用。RecyclerView 自 2014 年起成为官方替代品。如果您开始一个新项目 — 选择 RecyclerView。如果您维护现有项目 — ListView 将继续工作而无需更改。

如何向 ListView 添加 header/footer?

ListView 支持 addHeaderView(View, Object, boolean)addFooterView(View, Object, boolean) 方法。Header 和 footer 被添加到元素的总列表中,但不参与适配器 — 它们始终显示在列表的开头和结尾。重要提示:在设置适配器(setAdapter())之前添加 header/footer,否则它们不会被考虑。添加 header 后,元素位置的 ID 会偏移 header 的数量。

为什么 ListView 在 ScrollView 内无法滚动?

ListViewScrollView 都处理垂直手势,这会产生冲突:外部 ScrollView 拦截滚动,而 ListView 无法滚动。解决方案:(1)不要将 ListView 放在 ScrollView 内 — 使用带有 NestedScrollingChild 的 RecyclerView,(2)将 ScrollView 替换为 NestedScrollView(API 21+),(3)在 ScrollView 内使用固定高度(android:layout_height="...dp")的 ListView,但这会破坏响应式布局。

如何清除 ListView 并更新数据?

ListView 中的数据清除取决于适配器类型。对于 ArrayAdapter:adapter.clear()adapter.addAll(newList) 后跟 adapter.notifyDataSetChanged()。对于自定义适配器:更新数据列表并调用 notifyDataSetChanged()。注意:listView.invalidateViews() 只重绘 View,但不会更新数据。始终通过适配器更改数据,而不是直接操作。

如何更改 ListView 元素的高度?

ListView 元素的高度在 item 布局中设置。如果布局使用 layout_height="wrap_content",高度会根据内容调整。对于所有元素的固定高度,使用在 getView() 中设置 LayoutParams 的自定义适配器:view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight)。对于不同的高度,使用具有不同视图类型的 RecyclerView。

总结

  • ListView — 从 API 1 开始可用的 Android SDK 基本垂直列表组件。
  • ArrayAdapter — 最简单的字符串列表适配器;对于自定义布局需要重写 getView()。
  • ViewHolder 在 ListView 中通过 convertView 和 view.tag 手动实现 — 强制性能条件。
  • ListView 支持 choiceMode(单/多选)、header/footer、fastScroll 和无需适配器的 android:entries。
  • ListView 的限制:仅垂直方向、无动画、每次更改完全重绘。
  • ListView 未被标记为已弃用,但 Google 推荐所有新项目使用 RecyclerView。
  • 对于最多 50 个元素的列表,ListView 足够;对于任何更复杂的情况 — 请使用 RecyclerView。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读