☰
【Android 学习】之ListView使用大全:从ArrayAdapter到SimpleCursorAdapter的适配器选型与实战
2026/10/9 16:31:55 网站建设 项目流程

1. 先搞清楚 ListView 三种适配器到底该选谁

ListView 是 Android 里最经典的列表组件,虽然现在 RecyclerView 更主流,但大量老项目、教学示例、嵌入式设备界面里仍然是 ListView 的天下。一个 ListView 界面由三部分组成:ListView 组件本身负责滚动和回收,适配器负责把数据映射成一行行 View,数据源则是你要展示的内容。适配器选错了,代码会又长又难维护;选对了,几十行就能跑通。

这篇面向 Android 初学者,把 ArrayAdapter、SimpleAdapter、SimpleCursorAdapter 三种适配器的选型逻辑讲清楚,并给出可以直接复制的布局 XML 和 Java/Kotlin 配置代码。你跟着做完,能跑出一个可滚动的列表界面,并且知道什么场景该换哪种适配器。

选型其实一句话能概括:只显示一行纯文字,用 ArrayAdapter;每行要图片加多段文字甚至按钮,用 SimpleAdapter;数据来自 SQLite 查询结果(Cursor),用 SimpleCursorAdapter。下面这张表先给你一个整体印象,后面每一节都会展开。

适配器数据源类型每行能显示什么典型场景
ArrayAdapterList 或数组单个 TextView设置项、简单菜单
SimpleAdapterList<Map<String,Object>>图片、多段文字、按钮等商品列表、联系人卡片
SimpleCursorAdapterCursor由 Cursor 列映射到 View数据库查询结果、通讯录

很多初学者会问:既然 SimpleAdapter 这么灵活,为什么还要用 ArrayAdapter?因为 ArrayAdapter 代码量最少,系统内置布局直接可用,不需要自己写 item 布局。反过来,SimpleAdapter 虽然灵活,但数据要用 List组织,写起来啰嗦,而且它不支持复杂交互逻辑。SimpleCursorAdapter 则是为数据库场景量身定做的,省去了手动把 Cursor 转成 List 的步骤。

还有一个容易踩的坑:三种适配器的构造函数参数顺序和含义不一样,尤其是 SimpleAdapter 的五个参数,初学者经常把 from 和 to 数组的顺序写反,导致界面显示空白或者直接崩溃。后面我会逐个拆解。

如果你在配置过程中需要查 Android 相关的模型能力或者让 AI 帮你解释报错,可以顺手用一下模型对话入口,把错误日志贴进去问,比翻文档快。地址是 https://taotoken.net/api ,配合 API Key 就能调用。

这一节先把选型逻辑立住,下一节开始动手写代码。记住一个原则:能用简单适配器解决的就别上复杂的,ListView 的性能瓶颈往往不在适配器类型,而在 getView 里有没有做 View 复用。

2. 环境准备与 TaoToken 前置配置

在写代码之前,先把开发环境和辅助工具准备好。Android 部分你需要 Android Studio(建议 Hedgehog 或更新版本)、一个能跑的模拟器或者真机、以及基本的 Gradle 同步能力。这些属于标准配置,不展开。重点说一下怎么用 TaoToken 来辅助你排查适配器相关的报错,因为初学者最容易卡在类型不匹配、资源找不到这类问题上。

TaoToken 是一个模型调用平台,你可以把它理解成一个统一的 API 入口,通过它调用不同的大模型来完成代码解释、报错分析、文档生成等任务。对于 Android 学习场景,最实用的用法是:把编译报错或者运行时异常贴给模型,让它告诉你哪一行参数写错了。比如 SimpleAdapter 的 from 数组和 to 数组长度不一致时,运行会抛 IllegalStateException,这种错误搜索引擎不一定能精准命中,但模型能直接定位。

前置配置分三步。第一步,拿到 API Key。访问 https://taotoken.net/api-keys 创建密钥,注意这个 Key 只在创建时完整显示一次,复制后妥善保存。第二步,确认你要调用的模型 ID,比如常见的对话模型或者代码模型,模型 ID 在文档里有列表。第三步,把 Base URL 和 Key 配置到你的调用工具里。

如果你用的是命令行工具或者 IDE 插件,配置通常是一个 JSON 或 TOML 文件。下面给一个通用的 JSON 配置片段,路径按你实际使用的工具放置:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的密钥", "model": "你的模型ID", "timeout": 60 }

注意 base_url 后面不要多加斜杠,也不要带 /v1 之类的后缀,具体以文档为准。Key 不要硬编码到提交到 Git 的代码里,建议用环境变量或者本地配置文件并加入 .gitignore。

配置完成后,你可以先用一个最简单的请求验证连通性。比如用 curl 发一条消息:

curl https://taotoken.net/api/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的密钥" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "用一句话解释 ArrayAdapter 和 SimpleAdapter 的区别"}] }'

如果返回正常的 JSON 且 choices 里有内容,说明配置成功。这一步很关键,因为后面排查 Android 报错时你要反复用到这个通道。如果返回 401,说明 Key 不对或者没带上 Authorization 头;如果返回 model not found,说明模型 ID 写错了。

对于长期做 Android 开发、需要频繁让模型帮忙读代码的场景,可以考虑 Coding Plan,额度更划算,适合持续调用。入口在 https://taotoken.net/api 的套餐页面里能找到。

环境准备好之后,下一节开始写真正的 ListView 代码。我会按 ArrayAdapter、SimpleAdapter、SimpleCursorAdapter 的顺序,每个都给完整的布局和 Java 代码,你可以直接复制到项目里跑。

3. 三种适配器可复制配置与代码实战

这一节是全文的核心,每个适配器都给出布局 XML、Java 代码、以及关键参数说明。你新建一个 Android 项目,把代码贴进去就能跑。为了减少干扰,每个示例都用独立的 Activity。

3.1 ArrayAdapter:一行文字的极简列表

ArrayAdapter 适合只显示一行文本的场景。它的构造函数最常用的是三个参数:Context、item 布局资源、数据 List。系统内置了 android.R.layout.simple_list_item_1,就是一个 TextView,直接用就行,不用自己写 item 布局。

先看主布局 activity_array.xml,只需要一个 ListView:

<?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="match_parent" android:orientation="vertical"> <ListView android:id="@+id/list_array" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>

然后是 Java 代码 ArrayAdapterActivity.java:

package com.example.listviewdemo; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; public class ArrayAdapterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_array); ListView listView = findViewById(R.id.list_array); List<String> data = new ArrayList<>(); data.add("数据项 1"); data.add("数据项 2"); data.add("数据项 3"); data.add("数据项 4"); data.add("数据项 5"); ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_list_item_1, data); listView.setAdapter(adapter); } }

关键点:泛型 String 要和数据 List 的类型一致。如果你写 ArrayAdapter,编译能过但运行时可能出问题。另外 simple_list_item_1 里的 text1 是系统固定的 ID,ArrayAdapter 会自动把字符串塞进去,你不用管。

3.2 SimpleAdapter:图片加多段文字的商品列表

SimpleAdapter 的灵活性来自自定义 item 布局。你需要准备一个 listviewrow.xml,里面放 ImageView 和多个 TextView。数据用 List<Map<String,Object>> 组织,每个 Map 对应一行,key 是自定义的字段名。

先写 item 布局 listviewrow.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="10dp"> <ImageView android:id="@+id/item_image" android:layout_width="68dp" android:layout_height="65dp" android:scaleType="centerCrop" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:orientation="vertical"> <TextView android:id="@+id/item_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textColor="#222222" /> <TextView android:id="@+id/item_price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#666666" /> <TextView android:id="@+id/item_discount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#FF0000" /> </LinearLayout> </LinearLayout>

主布局 activity_simple.xml 同样只放一个 ListView:

<?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="match_parent" android:orientation="vertical"> <ListView android:id="@+id/list_simple" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>

Java 代码 SimpleAdapterActivity.java:

package com.example.listviewdemo; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class SimpleAdapterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); ListView listView = findViewById(R.id.list_simple); List<Map<String, Object>> data = new ArrayList<>(); Map<String, Object> item1 = new HashMap<>(); item1.put("img", R.drawable.ic_launcher_background); item1.put("name", "摩托罗拉 XT711"); item1.put("price", "2669 元"); item1.put("discount", "9 折"); data.add(item1); Map<String, Object> item2 = new HashMap<>(); item2.put("img", R.drawable.ic_launcher_background); item2.put("name", "iPhone 4S"); item2.put("price", "2669 元"); item2.put("discount", "9 折"); data.add(item2); Map<String, Object> item3 = new HashMap<>(); item3.put("img", R.drawable.ic_launcher_background); item3.put("name", "三星 11 5G"); item3.put("price", "2669 元"); item3.put("discount", "9 折"); data.add(item3); SimpleAdapter adapter = new SimpleAdapter( this, data, R.layout.listviewrow, new String[]{"img", "name", "price", "discount"}, new int[]{R.id.item_image, R.id.item_name, R.id.item_price, R.id.item_discount}); listView.setAdapter(adapter); listView.setOnItemClickListener((parent, view, position, id) -> Toast.makeText(SimpleAdapterActivity.this, "你选择的是 " + data.get(position).get("name"), Toast.LENGTH_SHORT).show()); } }

这里最容易错的地方是 from 和 to 两个数组的对应关系。from 是 Map 里的 key 数组,to 是 item 布局里的 View ID 数组,两者长度必须一致,顺序一一对应。上面代码里 from 第一个是 "img",to 第一个是 R.id.item_image,这样图片才会正确显示。如果你把顺序写反,比如 from 是 {"name","img"} 而 to 是 {R.id.item_image, R.id.item_name},运行时会抛异常或者显示错乱。

3.3 SimpleCursorAdapter:从数据库 Cursor 直接映射

SimpleCursorAdapter 的数据源是 Cursor,通常来自 ContentResolver 查询或者 SQLite 查询。它的构造函数需要 Cursor、from 列名数组、to View ID 数组。注意 Cursor 必须包含一个名为 _id 的列,否则会报错,这是 Android 的硬性要求。

主布局 activity_cursor.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="match_parent" android:orientation="vertical"> <ListView android:id="@+id/list_cursor" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>

Java 代码 SimpleCursorAdapterActivity.java,这里用查询通讯录举例,注意需要申请 READ_CONTACTS 权限:

package com.example.listviewdemo; import android.database.Cursor; import android.os.Bundle; import android.provider.ContactsContract; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import androidx.appcompat.app.AppCompatActivity; public class SimpleCursorAdapterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_cursor); ListView listView = findViewById(R.id.list_cursor); Cursor cursor = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER }, null, null, null); if (cursor == null) { return; } SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, android.R.layout.simple_list_item_2, cursor, new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER }, new int[]{android.R.id.text1, android.R.id.text2}, 0); listView.setAdapter(adapter); } }

注意最后一个参数 0 是 flags,旧版本常用 FLAG_AUTO_REQUERY,但那个常量已经废弃,直接传 0 即可。另外 Cursor 的生命周期要管理好,Activity 销毁时应该关闭,简单场景可以用 startManagingCursor,但更推荐在 onDestroy 里手动 cursor.close()。

如果你想让模型帮你检查这段 Cursor 查询的列名是否正确,可以把代码贴到模型对话里问,入口是 https://taotoken.net/api ,配合 Key 使用。

三个适配器的代码都跑通之后,你会发现它们的主布局几乎一样,区别全在适配器的构造和 item 布局。这就是选型的意义:数据形态决定适配器类型。

4. 逐项运行验证与成功结果确认

代码写完不代表跑通,这一节给你一套逐项验证的步骤,确保每个适配器都真的工作。验证的核心思路是:先确认编译通过,再确认界面渲染,最后确认交互响应。

第一步,编译验证。在 Android Studio 里点击 Build > Make Project,观察 Build 窗口。如果出现 cannot find symbol 或者 resource not found,通常是布局文件名拼错、ID 引用错误、或者 drawable 资源不存在。比如 SimpleAdapter 示例里用了 R.drawable.ic_launcher_background,如果你项目里没有这个资源,就换成自己项目里已有的图片,或者临时用 android.R.drawable.ic_menu_gallery 代替。

第二步,注册 Activity。三个 Activity 都要在 AndroidManifest.xml 里声明,否则启动会闪退。示例:

<activity android:name=".ArrayAdapterActivity" /> <activity android:name=".SimpleAdapterActivity" /> <activity android:name=".SimpleCursorAdapterActivity" />

第三步,逐个启动验证。先跑 ArrayAdapterActivity,预期结果是屏幕上出现五行文字,可以上下滚动。如果列表空白,检查 setAdapter 是否调用、数据 List 是否为空、ListView 的宽高是否是 match_parent。

再跑 SimpleAdapterActivity,预期结果是每行左边一张图,右边三行文字(名称、价格、折扣),点击某一行弹出 Toast 显示商品名。如果图片不显示,检查 ImageView 的宽高是否给了具体值,以及 Map 里的 key 和 from 数组是否一致。如果点击没反应,检查 setOnItemClickListener 是否在 setAdapter 之后调用。

最后跑 SimpleCursorAdapterActivity,这个需要动态申请 READ_CONTACTS 权限。在 Android 6.0 以上,你必须在代码里请求权限,否则 Cursor 返回空。最简单的验证方式是在设置里手动给应用授权,或者加一段运行时权限请求代码。预期结果是显示通讯录里的姓名和号码,每行两段文字。

第四步,用日志确认数据量。在每个适配器 setAdapter 之后加一行 Log:

Log.d("ListViewDemo", "adapter count = " + adapter.getCount());

如果 count 是 0,说明数据源为空,问题出在数据准备阶段而不是适配器。如果 count 大于 0 但界面空白,问题出在布局或者 View 映射。

第五步,验证滚动性能。往 ArrayAdapter 的数据里塞 100 条,快速滑动,观察是否卡顿。ArrayAdapter 和 SimpleAdapter 内部都做了 View 复用,正常情况不会卡。如果你自己写了自定义 Adapter 而没复用 convertView,才会卡。这也是为什么初学阶段优先用系统适配器。

跑完这五步,三个适配器都正常显示,说明你已经掌握了 ListView 的基本用法。接下来是排错环节,把常见的报错和处理方式列出来。

5. 本篇常见报错排查

这一节按真实报错信息来组织,你遇到哪个就查哪个。每个报错都给出原因和修复方式。

第一个高频报错:java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification。这个通常出现在你修改了数据 List 但没有调用 adapter.notifyDataSetChanged()。修复方式是在数据变更后调用 notifyDataSetChanged,并且确保数据变更在主线程。如果你用的是 SimpleCursorAdapter,数据变更后要重新查询 Cursor 或者调用 swapCursor。

第二个:java.lang.IllegalArgumentException: column '_id' does not exist。这是 SimpleCursorAdapter 特有的,Cursor 查询结果里必须包含 _id 列。修复方式是在 query 的 columns 数组里显式加上 _id,比如 ContactsContract.CommonDataKinds.Phone._ID。如果你查的是自己建的 SQLite 表,建表时主键要命名为 _id。

第三个:java.lang.NullPointerException在 setAdapter 那一行。常见原因是 findViewById 返回 null,也就是布局里没有对应的 ID,或者 setContentView 传错了布局文件。检查 R.layout.xxx 和布局里的 android:id 是否匹配。

第四个:SimpleAdapter 显示空白但 count 正常。原因是 from 数组里的 key 和 Map 里的 key 不一致,或者 to 数组里的 ID 在 item 布局里不存在。逐个核对,建议把 from 和 to 打印出来对照。

第五个:android.content.res.Resources$NotFoundException: Resource ID #0x0。说明你引用的资源 ID 是 0,通常是 R.drawable.xxx 写错了,或者 Map 里 put 的图片资源不存在。检查 drawable 目录下是否有对应文件。

第六个:401 错误,如果你在用 TaoToken 辅助排查时遇到401 Unauthorized,说明 API Key 无效或者没带上。检查 Authorization 头格式是否为Bearer sk-xxx,以及 Key 是否被删除或过期。重新在 https://taotoken.net/api-keys 生成一个即可。

第七个:local proxy failed或者连接超时。这类错误通常是网络配置问题,检查你的 base_url 是否写成了https://taotoken.net/api,不要多加路径。如果公司网络有出口限制,换一个网络环境再试。

第八个:OAuth 相关报错,比如OAuth token expired。如果你用的是需要 OAuth 的工具,重新走一遍授权流程,确保 token 刷新正常。对于 API Key 方式调用,一般不会遇到这个问题。

第九个:Cursor 查询返回 null。除了权限问题,还要检查 ContentResolver 的 URI 是否正确。通讯录用 ContactsContract.CommonDataKinds.Phone.CONTENT_URI,不要用已经废弃的 Contacts.People.CONTENT_URI,后者在新版本上会直接抛异常。

第十个:列表项点击无响应。检查 ListView 的 item 布局里是否有可点击的控件(比如 Button),它们会抢占点击事件。如果 item 里有 Button,需要给 Button 设置 focusable=false,或者改用其他交互方式。

排查的核心方法是:先看 Logcat 的完整堆栈,定位到具体行号,再对照上面的清单。不要只看最后一行异常信息,前面的 Caused by 才是根因。如果你把堆栈贴给模型分析,记得把包名和业务代码脱敏。

6. 继续深入与工具链建议

三个适配器跑通之后,你可能会想:ListView 是不是过时了?答案是:新项目优先用 RecyclerView,但 ListView 仍然值得学,因为它的适配器思想是相通的,而且大量存量代码需要维护。理解 ArrayAdapter 的数据绑定、SimpleAdapter 的 Map 映射、SimpleCursorAdapter 的 Cursor 映射,对你后面理解 RecyclerView.Adapter 的三件套(onCreateViewHolder、onBindViewHolder、getItemCount)有直接帮助。

如果你想继续深入,建议按这个顺序推进:先给 SimpleAdapter 加上 ViewHolder 模式优化性能,再尝试自己写一个继承 BaseAdapter 的自定义适配器,最后迁移到 RecyclerView。每一步都可以让模型帮你 review 代码,把关键片段贴到模型对话里问,入口是 https://taotoken.net/api ,配合 Key 使用。

对于需要长期做 Android 开发、频繁调用模型辅助编码的场景,Coding Plan 的额度更适合持续使用,具体在 https://taotoken.net/api 的套餐页可以查看。接入文档在 https://taotoken.net/api 的文档区,里面有各语言的调用示例。

最后给一个实用技巧:把三个适配器的示例代码放在同一个项目里,用不同的按钮跳转,这样你可以随时对比它们的差异。项目结构建议按功能分包,adapter 相关的 Activity 放一个包,布局文件按 Activity 命名,资源文件统一管理。这样等你以后回头看,能快速回忆起每种适配器的适用边界。

代码跑通只是开始,真正掌握的标准是:给你一个新的列表需求,你能在三十秒内判断该用哪种适配器,并且知道 item 布局该怎么写。这个判断力来自反复练习,把上面的示例改一改,换成你自己的数据,跑起来看看效果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询