ListView 是 Android 中一个非常经典且重要的控件,它用于垂直滚动地显示一个项目列表,尽管现在更现代的 RecyclerView 已经成为主流,但理解 ListView 的工作原理对于学习 Android UI 开发至关重要,因为 RecyclerView 的许多核心概念(如适配器、视图回收)都源于它。

ListView 是什么?
ListView 是一个 ViewGroup,它允许你将一个垂直的、可滚动的项目列表显示在屏幕上,当列表项的数量超出屏幕可见范围时,用户可以上下滚动来查看所有内容。
核心特点:
- 垂直滚动:默认只能垂直滚动。
- 数据驱动:
ListView本身不存储数据,它需要通过一个“适配器”(Adapter)来获取数据并将其显示出来。 - 视图回收:这是
ListView性能优化的关键,它不会为列表中的每一个项目都创建一个 View 对象,而是只创建足够填满屏幕的 View,并在滚动时回收这些视图,然后填充新的数据,从而极大地节省了内存,避免了频繁的findViewById和对象创建带来的性能问题。
ListView 的核心三要素
要使用 ListView,你必须理解以下三个核心组件:
-
ListView(控件本身)
(图片来源网络,侵删)- 这是你在 XML 布局文件中声明的 UI 控件。
- 它负责显示列表,并处理用户的滚动事件。
-
Adapter(适配器)- 角色:
ListView和数据源之间的“桥梁”。 - 作用:
- 从数据源(如数组、
ArrayList、数据库等)获取数据。 - 创建代表列表项的
View。 - 将数据绑定到
View上,然后将View返回给ListView进行显示。
- 从数据源(如数组、
- 常用子类:
ArrayAdapter:最简单的适配器,用于将数组或List中的字符串绑定到TextView上。SimpleAdapter:可以将List<Map<String, ?>>类型的数据绑定到各种View(如ImageView,TextView等)上。BaseAdapter:最灵活的适配器,你需要继承它并实现四个核心方法,当你需要自定义列表项的复杂布局时,就必须使用它。
- 角色:
-
数据源
- 任何提供数据的集合,
String[](字符串数组)List<String>(字符串列表)List<Map<String, Object>>(键值对列表,常用于SimpleAdapter)- 数据库查询结果
- 任何提供数据的集合,
如何使用 ListView(完整步骤)
下面我们通过一个最经典的例子:自定义一个包含图片和文本的列表项,来演示完整的使用流程。
步骤 1:准备数据源
你需要一个数据列表,这里我们创建一个 List<Map<String, Object>>,每个 Map 代表一个列表项,包含“图片”和“标题”信息。

// 在 Activity 或 Fragment 中准备数据
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map;
for (int i = 1; i <= 20; i++) {
map = new HashMap<>();
map.put("image", R.drawable.ic_launcher_foreground); // 使用一个固定的图片资源
map.put("title", "列表项 " + i);
list.add(map);
}
return list;
}
步骤 2:创建列表项的布局文件
每个列表项长什么样,需要由一个独立的 XML 布局文件来定义,我们创建一个 list_item.xml,里面包含一个 ImageView 和一个 TextView。
res/layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<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="16dp">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="列表项标题"
android:textSize="18sp" />
</LinearLayout>
步骤 3:在主布局中添加 ListView
在 Activity 的主布局文件(activity_main.xml)中,添加 ListView 控件。
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
步骤 4:创建自定义适配器
为了使用我们自定义的 list_item.xml 布局,我们需要创建一个继承自 BaseAdapter 的自定义适配器。
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.Map;
public class MyAdapter extends BaseAdapter {
private Context context;
private List<Map<String, Object>> data;
// 构造函数
public MyAdapter(Context context, List<Map<String, Object>> data) {
this.context = context;
this.data = data;
}
// 1. 返回数据源的总数量
@Override
public int getCount() {
return data.size();
}
// 2. 返回指定位置的数据项
@Override
public Object getItem(int position) {
return data.get(position);
}
// 3. 返回指定位置的数据项的 ID
@Override
public long getItemId(int position) {
return position;
}
// 4. **核心方法**:创建并返回指定位置的列表项 View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 优化:检查 convertView 是否为空,如果不为空,直接复用,避免重复创建 View
if (convertView == null) {
// convertView 为空,说明需要创建一个新的 View
// 使用 LayoutInflater 将 list_item.xml 布局文件转换为 View 对象
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
}
// 从 convertView 中找到子视图
ImageView ivIcon = convertView.findViewById(R.id.iv_icon);
TextView tvTitle = convertView.findViewById(R.id.tv_title);
// 获取当前位置的数据
Map<String, Object> item = data.get(position);
// 将数据设置到子视图中
ivIcon.setImageResource((Integer) item.get("image"));
tvTitle.setText((String) item.get("title"));
// 返回填充好数据的 View
return convertView;
}
}
步骤 5:在 Activity 中设置 ListView
在你的 MainActivity.java 中,获取 ListView 实例,创建适配器,并将其设置给 ListView。
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 找到 ListView 控件
listView = findViewById(R.id.list_view);
// 2. 准备数据源
List<Map<String, Object>> data = getData();
// 3. 创建适配器
adapter = new MyAdapter(this, data);
// 4. 将适配器设置给 ListView
listView.setAdapter(adapter);
}
// 步骤1中定义的 getData() 方法
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map;
for (int i = 1; i <= 20; i++) {
map = new HashMap<>();
map.put("image", R.drawable.ic_launcher_foreground);
map.put("title", "列表项 " + i);
list.add(map);
}
return list;
}
}
运行代码,你就能看到一个包含图片和文本的列表了。
ListView 的事件处理
我们需要监听列表项的点击事件。
// 在 MainActivity 的 onCreate 方法中,设置适配器之后添加
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// parent: AdapterView (这里是 ListView)
// view: 被点击的列表项的 View 对象
// position: 被点击项在列表中的位置 (从 0 开始)
// id: 被点击项的 ID (通常是 position)
// 获取被点击项的数据
Map<String, Object> clickedItem = (Map<String, Object>) parent.getItemAtPosition(position);
String title = (String) clickedItem.get("title");
Toast.makeText(MainActivity.this, "你点击了: " + title, Toast.LENGTH_SHORT).show();
}
});
ListView vs. RecyclerView
| 特性 | ListView |
RecyclerView |
|---|---|---|
| 布局管理 | 只能垂直列表。 | 支持线性、网格、瀑布流等多种布局。 |
| 视图回收 | 内置,但不可定制。 | 内置,且高度可定制(通过 LayoutManager)。 |
| 动画支持 | 内置简单的插入/删除动画,但难以自定义。 | 内置强大的动画 API,可以轻松实现复杂的 item 动画。 |
| ViewHolder模式 | 不强制,开发者需要手动实现,否则性能会很差。 | 强制要求。RecyclerView.Adapter 要求你必须使用 ViewHolder,这极大地提升了性能和代码整洁度。 |
| 用途 | 简单的、垂直列表。 | 复杂的、高性能的、可定制的列表或网格。 |
- 对于简单的垂直列表,
ListView仍然可以快速实现。 - 在现代 Android 开发中,强烈推荐使用
RecyclerView,它更灵活、性能更好、可扩展性更强,学习ListView是为了更好地理解RecyclerView的基础。
性能优化(getView 方法)
ListView 的性能瓶颈几乎都在 getView 方法中,优化 getView 是关键:
-
使用
ViewHolder模式:这是最重要的优化!- 问题:
getView方法在每次滚动时都会被调用,如果在里面每次都调用findViewById(),会导致大量的重复查找,非常耗时。 - 解决方案:创建一个静态的
ViewHolder内部类,在convertView为空时,将找到的子视图(ImageView,TextView等)缓存在ViewHolder对象中,并将ViewHolder对象作为convertView的Tag存储起来,当下次convertView被复用时,直接从Tag中取出ViewHolder,从而避免了findViewById()的调用。
优化后的
getView方法:// 在 MyAdapter 类中添加一个静态的 ViewHolder 内部类 static class ViewHolder { ImageView ivIcon; TextView tvTitle; } // 修改 getView 方法 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); // 创建 ViewHolder 对象 holder = new ViewHolder(); // 查找子视图并缓存到 holder 中 holder.ivIcon = convertView.findViewById(R.id.iv_icon); holder.tvTitle = convertView.findViewById(R.id.tv_title); // 将 holder 对象存储到 convertView 的 Tag 中 convertView.setTag(holder); } else { // convertView 不为空,直接从 Tag 中取出 holder holder = (ViewHolder) convertView.getTag(); } // 使用 holder 中的视图来设置数据 Map<String, Object> item = data.get(position); holder.ivIcon.setImageResource((Integer) item.get("image")); holder.tvTitle.setText((String) item.get("title")); return convertView; } - 问题:
-
避免在
getView中执行耗时操作:不要在getView里进行网络请求、复杂的文件读写或数据库查询,这些操作应该在数据获取阶段完成,getView只负责显示数据。 -
合理使用
android:layout_height:确保列表项的高度是固定的(如wrap_content或具体数值),避免动态计算高度导致的性能问题。
希望这份详细的指南能帮助你全面理解 Android ListView 的使用!
