news 2026/10/7 7:45:46

Android ListView 三种数据适配器实战:SimpleAdapter、SimpleCursorAdapter 与 BaseAdapter 全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android ListView 三种数据适配器实战:SimpleAdapter、SimpleCursorAdapter 与 BaseAdapter 全解析

1. 从通讯录到订单列表:为什么三种适配器都要会

Android 里的 ListView 算是个老面孔了,但直到今天,很多存量项目、教学 Demo、甚至部分 IoT 设备的配置界面还在用它。原因很简单:它稳定、API 直白、配合 Adapter 能覆盖绝大多数“一列一列展示数据”的需求。通讯录、通话记录、设备扫描结果、订单流水,本质都是 ListView 加一个适配器。

问题在于,数据源不止一种。有时候你手里是List<Map<String,Object>>,有时候是 SQLite 查出来的Cursor,有时候是一个自定义实体类列表还要渲染复杂布局。于是就有了三种适配器:SimpleAdapter、SimpleCursorAdapter、BaseAdapter。它们不是替代关系,而是按数据源类型选型的关系。

这篇内容面向的是正在写 Android 列表展示、被适配器选型卡住的开发者。我会把三种适配器的可复制代码、布局 XML、运行验证步骤都摆出来,你照着改包名就能跑。核心检索词就是 Android ListView 三种适配器实战,重点解决“我的数据长这样,到底该用哪个 Adapter”这个问题。

先说结论,方便你带着预期往下看:数据是 Map 集合、布局简单,用 SimpleAdapter;数据来自 SQLite 查询、需要绑定_id,用 SimpleCursorAdapter;数据是实体类、布局复杂或需要复用缓存,用 BaseAdapter。下面逐个拆。

2. 前置准备:TaoToken 接入与工程环境

在写适配器之前,先把两件事准备好:一个是工程环境,一个是如果你打算在开发过程中调用大模型辅助生成代码或排查报错,可以先把 TaoToken 的接入配好。TaoToken 是一个大模型 API 聚合平台,官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。它本身不替代 Android Studio,只是在你需要模型对话、代码补全、Agent 编码时提供一个统一的 Key 和 Base URL。

工程环境这边,我用的是 Android Studio,minSdkVersion设 21 以上,compileSdk34。布局文件放在res/layout/下,Java 代码放在src/main/java/你的包名/下。数据库用 SQLite,通过SQLiteOpenHelper建库建表。这些是后面三种适配器共用的基础。

如果你要在 Claude Code 或 Cline 这类工具里接入 TaoToken 做辅助开发,配置三件套是 Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,Key 在控制台创建,Model ID 按你选的模型填。控制台入口是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,API Keys 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

这里给一个 Claude Code 的配置片段,路径是~/.claude/settings.json,你可以直接复制改 Key:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoTokenKey", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

如果你用的是 Codex,配置文件在~/.codex/auth.json,结构类似,把 Base URL 和 Key 填进去即可。Cline 的 MCP 配置则在cline_mcp_settings.json里,同样是 Base URL、Key、Model ID 三件套。配好之后,你在写适配器时遇到getView报空指针,可以直接让模型帮你定位。

需要提醒的是,TaoToken 只是模型调用入口,不要把它当成数据库或生产环境的直连通道。适配器里的数据源还是老老实实走 SQLite 或内存集合。

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

这一节是主体,三种适配器分别给出布局 XML、Java 代码、以及关键参数说明。你可以按顺序建三个 Activity 分别验证,也可以放在一个 Activity 里用按钮切换。

3.1 SimpleAdapter:渲染 Map 列表

SimpleAdapter 适合数据是List<? extends Map<String,?>>的场景。它的构造参数有五个:Context、数据 List、item 布局资源、from 数组(Map 的 key)、to 数组(布局里的控件 id)。

先写 item 布局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="12dp"> <TextView android:id="@+id/tv_id" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="16sp" /> <TextView android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:textSize="16sp" /> <TextView android:id="@+id/tv_age" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="16sp" /> </LinearLayout>

主布局res/layout/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_show" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>

Java 代码,构造数据并绑定:

public class SimpleAdapterActivity extends AppCompatActivity { private ListView listShow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); listShow = findViewById(R.id.list_show); List<Map<String, Object>> data = new ArrayList<>(); for (int i = 1; i <= 20; i++) { Map<String, Object> item = new HashMap<>(); item.put("id", "ID-" + i); item.put("name", "学生" + i); item.put("age", 18 + (i % 5)); data.add(item); } SimpleAdapter adapter = new SimpleAdapter( this, data, R.layout.list_item, new String[]{"id", "name", "age"}, new int[]{R.id.tv_id, R.id.tv_name, R.id.tv_age} ); listShow.setAdapter(adapter); } }

关键点:from数组的顺序要和to数组一一对应。from里的 key 必须和 Map 里的 key 完全一致,大小写敏感。如果某个 key 在 Map 里不存在,对应 TextView 会显示空,不会崩,但你会看到空白列。

3.2 SimpleCursorAdapter:绑定数据库游标

SimpleCursorAdapter 的数据源是Cursor,通常来自 SQLite 查询。它比 SimpleAdapter 多一个约束:Cursor 里必须包含一个名为_id的列,否则会抛IllegalArgumentException。

先建库建表。写一个DBHelper:

public class DBHelper extends SQLiteOpenHelper { public DBHelper(Context context) { super(context, "student.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE student (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"); for (int i = 1; i <= 20; i++) { db.execSQL("INSERT INTO student (name, age) VALUES (?, ?)", new Object[]{"学生" + i, 18 + (i % 5)}); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS student"); onCreate(db); } }

查询并绑定:

public class CursorAdapterActivity extends AppCompatActivity { private ListView listShow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); listShow = findViewById(R.id.list_show); DBHelper helper = new DBHelper(this); SQLiteDatabase db = helper.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT _id, name, age FROM student", null); SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, R.layout.list_item, cursor, new String[]{"_id", "name", "age"}, new int[]{R.id.tv_id, R.id.tv_name, R.id.tv_age}, 0 ); listShow.setAdapter(adapter); } }

注意最后那个0是 flags 参数,传FLAG_REGISTER_CONTENT_OBSERVER可以让适配器自动监听 Cursor 变化。Cursor 在 Activity 销毁前不要close(),否则列表会读不到数据。如果你在onDestroy里关,要确保适配器已经不再使用它。

3.3 BaseAdapter:自定义复杂布局与缓存

BaseAdapter 最灵活,适合实体类列表和复杂 item 布局。核心是重写getCount、getItem、getItemId、getView。getView里用 ViewHolder 模式做缓存,避免重复findViewById。

先定义实体类:

public class Student { private int id; private String name; private int age; public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public String getName() { return name; } public int getAge() { return age; } }

自定义适配器:

public class StudentAdapter extends BaseAdapter { private List<Student> students; private int source; private LayoutInflater inflater; public StudentAdapter(Context context, List<Student> students, int source) { this.students = students; this.source = source; this.inflater = LayoutInflater.from(context); } @Override public int getCount() { return students.size(); } @Override public Object getItem(int position) { return students.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(source, parent, false); holder = new ViewHolder(); holder.tvId = convertView.findViewById(R.id.tv_id); holder.tvName = convertView.findViewById(R.id.tv_name); holder.tvAge = convertView.findViewById(R.id.tv_age); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Student stu = students.get(position); holder.tvId.setText(String.valueOf(stu.getId())); holder.tvName.setText(stu.getName()); holder.tvAge.setText(String.valueOf(stu.getAge())); return convertView; } private static class ViewHolder { TextView tvId; TextView tvName; TextView tvAge; } }

绑定到 ListView:

List<Student> students = new ArrayList<>(); for (int i = 1; i <= 20; i++) { students.add(new Student(i, "学生" + i, 18 + (i % 5))); } StudentAdapter adapter = new StudentAdapter(this, students, R.layout.list_item); listShow.setAdapter(adapter);

inflate的第三个参数传parent, false很重要。传null会导致 item 的layout_width失效,列表项宽度异常。这是很多人踩过的坑。

4. 验证请求与成功结果

三种适配器写完后,分别运行验证。验证的核心是看列表能不能正常滚动、数据有没有错位、点击有没有响应。

第一种验证:SimpleAdapter。运行SimpleAdapterActivity,你应该看到 20 行数据,每行三列:ID、姓名、年龄。上下滚动,数据顺序不变。如果某列空白,检查from数组的 key 和 Map 的 key 是否一致。

第二种验证:SimpleCursorAdapter。运行CursorAdapterActivity,同样 20 行。这里重点看_id列有没有显示。如果抛IllegalArgumentException: column '_id' does not exist,说明你的查询语句没把_id查出来。改成SELECT _id, name, age FROM student即可。

第三种验证:BaseAdapter。运行后滚动列表,观察 Logcat 里有没有重复的inflate日志。如果你在getView里加了日志,正常情况下一屏只 inflate 一屏数量的 item,滚动时复用 convertView,不会无限增长。这就是 ViewHolder 缓存生效的标志。

点击事件验证,给 ListView 加监听:

listShow.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Object item = parent.getItemAtPosition(position); if (item instanceof Student) { Student stu = (Student) item; Toast.makeText(getApplicationContext(), stu.getName(), Toast.LENGTH_SHORT).show(); } else if (item instanceof Cursor) { Cursor cursor = (Cursor) item; int idx = cursor.getColumnIndex("name"); Toast.makeText(getApplicationContext(), cursor.getString(idx), Toast.LENGTH_SHORT).show(); } else if (item instanceof Map) { Map map = (Map) item; Toast.makeText(getApplicationContext(), String.valueOf(map.get("name")), Toast.LENGTH_SHORT).show(); } } });

注意getItemAtPosition返回的类型:SimpleAdapter 返回 Map,SimpleCursorAdapter 返回 Cursor,BaseAdapter 返回你getItem里返回的对象。类型判断错了会 ClassCastException。

如果你在开发过程中用 TaoToken 的模型对话来验证代码逻辑,可以打开 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,把报错信息贴进去让它帮你分析。模型对话入口适合做这种单点验证,长期编码任务则更适合 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。

5. 本篇常见错排查

这一节列几个真实会遇到的报错,对照着改。

第一个:java.lang.IllegalArgumentException: column '_id' does not exist。出现在 SimpleCursorAdapter。原因是查询语句没包含_id。SQLite 表如果主键不叫_id,要么改表结构,要么在查询里用AS _id别名。比如SELECT id AS _id, name FROM student。

第二个:java.lang.NullPointerException在getView里。常见于 BaseAdapter 的convertView.findViewById返回 null。检查 item 布局里控件的 id 是否和代码里一致,以及inflater.inflate的布局资源是否正确。

第三个:local proxy failed或401。这通常不是适配器的问题,而是你在用 Claude Code 或 Cline 接入 TaoToken 时 Base URL 或 Key 配错了。检查ANTHROPIC_BASE_URL是否填的https://taotoken.net/api,Key 是否在控制台正确创建。401 一般是 Key 无效,local proxy failed 一般是 Base URL 写成了带路径的地址。

第四个:reading choices相关报错。这出现在模型返回结构解析时,通常是 Model ID 填错或模型不支持当前接口格式。对照接入文档确认 Model ID。

第五个:OAuth 相关报错。如果你在 Codex 里用 OAuth 方式登录,但配置里又填了 API Key,会冲突。二选一,用 Key 就删掉 OAuth 配置。

第六个:列表滑动卡顿。BaseAdapter 里没用 ViewHolder,每次getView都findViewById。按 3.3 的写法加 ViewHolder 缓存。

第七个:item 宽度不对。inflate第三个参数传了null。改成parent, false。

6. 选型建议与后续接入

三种适配器跑通之后,选型其实就一句话:看你的数据源类型和布局复杂度。Map 集合用 SimpleAdapter,Cursor 用 SimpleCursorAdapter,实体类加复杂布局用 BaseAdapter。实际项目里 BaseAdapter 用得最多,因为实体类更可控,ViewHolder 缓存也更好优化。

如果你后续要把这些代码接入到更大的工程里,或者用 Agent 帮你批量重构适配器,可以在 TaoToken 的 Coding Plan 里配置长期编码任务。API Keys 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 管理,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Claude Code 的接入配置参考第二节的 settings.json 片段,把 Key 换成你自己的即可。

最后留一个实用技巧:如果你不确定该用哪个适配器,先看数据源。数据源是List<Map>就别硬转实体类,直接用 SimpleAdapter 最快;数据源是数据库查询结果,SimpleCursorAdapter 省去手动转 List 的步骤;数据源是网络请求返回的实体类列表,BaseAdapter 配合 ViewHolder 是标准做法。三种都跑一遍,你就有手感了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 7:44:46

YOLO小目标检测实战:篮球排球网球三类球体检测调优指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 7:43:12

每日安全情报报告 · 2026-10-06

每日安全情报报告 2026-10-06 由 AI 整理发布 覆盖范围&#xff1a;2026-10-05 ~ 2026-10-06&#xff08;近 24–48 小时新增 / 活跃威胁&#xff09; 数据来源&#xff1a;CISA KEV、NVD、MITRE CVE、The Hacker News、FreeBuf/安全客&#xff08;CN-SEC 聚合&#xff09;、V…

作者头像 李华
网站建设 2026/10/7 7:43:09

嵌入式系统全栈解析:从软硬件协同开发到系统集成与未来趋势

1. 产业全景&#xff1a;嵌入式系统正在重新定义“计算”的边界1.1 重新理解嵌入式系统&#xff1a;它其实无处不在入行这么多年&#xff0c;被问得最多的一句话是&#xff1a;嵌入式系统到底是个什么产业&#xff1f;很多人一听到“嵌入式”三个字&#xff0c;第一反应是单片机…

作者头像 李华