news 2026/9/29 20:41:20

Android ListView 组件实战:ArrayAdapter 与 SimpleAdapter 配 TaoToken 的 settings.json 骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android ListView 组件实战:ArrayAdapter 与 SimpleAdapter 配 TaoToken 的 settings.json 骨架

1. 从两个 Adapter 的切换说起:ListView 初学者的真实痛点

Android 里的 ListView 是个老牌组件,但它的使用逻辑放到今天依然值得练手:数据源和界面之间隔着一层 Adapter,你换数据源、换行布局,本质上都是在换 Adapter。ArrayAdapter 适合一行只有一段文字的极简列表,SimpleAdapter 适合一行里有图片、姓名、年龄这种多字段组合。很多初学者卡住的地方不是不会写 Adapter,而是两个 Adapter 的构造参数对不上号,切来切去就乱。

更麻烦的是,当你想在列表里加一个「模型切换」或者「请求通道」的入口时,多模型 API Key 分散在代码、local.properties、BuildConfig里,改一个环境就要重新编译。这篇就把两件事合在一起做:用 ArrayAdapter 和 SimpleAdapter 各跑通一个列表,再把多模型 Key 统一收进一份settings.json骨架,通过 TaoToken 的兼容接口读取。你跟着做,能拿到两个可运行的列表和一个可复用的配置结构。

适合谁:刚学完 Activity 和基础控件、想搞懂 Adapter 参数含义的 Android 初学者;以及手上有多个模型 Key、想统一管理请求入口的开发者。下面所有代码都可以直接贴进 Android Studio 跑。

2. TaoToken 前置:统一 Key 与 settings.json 骨架

TaoToken 在这里扮演的角色是「统一请求入口」。你不需要在每个 Adapter 或每个页面里硬编码不同厂商的 Key,而是把 Key 和模型信息集中写进一份settings.json,运行时读出来,再交给网络层。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。

先准备 Key:打开控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面创建一个 Key,页面地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建后复制出来,后面写进配置文件。

settings.json的骨架我建议放在app/src/main/assets/settings.json,这样打包进 APK 后可以直接用 AssetManager 读取。骨架长这样:

{ "endpoint": "https://taotoken.net/api", "defaultModel": "claude-3-5-sonnet", "models": [ { "name": "claude-3-5-sonnet", "display": "Claude 3.5 Sonnet", "apiKey": "sk-你的Key" }, { "name": "gpt-4o-mini", "display": "GPT-4o mini", "apiKey": "sk-你的Key" } ] }

字段含义:endpoint是统一请求地址,defaultModel是默认模型,models数组里每一项对应一个模型,display用来在 ListView 里显示,apiKey就是你在控制台拿到的 Key。这样 ArrayAdapter 可以直接绑定display字段,SimpleAdapter 可以把display和name一起显示成两列。

注意:settings.json里不要提交真实 Key 到公开仓库。本地调试可以放,正式项目建议用构建变量注入,或者只保留占位符。

3. 可复制配置:ArrayAdapter 与 SimpleAdapter 两套写法

3.1 ArrayAdapter 绑定模型列表

ArrayAdapter 的构造参数是(Context, 行布局资源, 数据数组/List)。这里数据源就是模型显示名列表,行布局用系统自带的android.R.layout.simple_list_item_1。

public class ModelListActivity extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List<String> modelNames = new ArrayList<>(); modelNames.add("Claude 3.5 Sonnet"); modelNames.add("GPT-4o mini"); modelNames.add("Claude 3 Opus"); ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_list_item_1, modelNames ); setListAdapter(adapter); } }

关键点:simple_list_item_1是系统内置的单行布局,里面只有一个 TextView,所以 ArrayAdapter 只能显示一段文字。如果你要显示「模型名 + 模型标识」两段信息,就得换 SimpleAdapter。

3.2 SimpleAdapter 绑定多字段行

SimpleAdapter 的构造参数是(Context, List<Map>, 行布局, 数据字段数组, 控件 id 数组)。数据字段数组和控件 id 数组必须一一对应,顺序不能错。

先写行布局item_model.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"> <ImageView android:id="@+id/img_icon" android:layout_width="40dp" android:layout_height="40dp" android:src="@mipmap/ic_launcher" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginStart="12dp"> <TextView android:id="@+id/tv_display" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#666666" /> </LinearLayout> </LinearLayout>

再写 Activity:

public class ModelDetailListActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_model_detail); List<Map<String, Object>> data = new ArrayList<>(); String[][] models = { {"Claude 3.5 Sonnet", "claude-3-5-sonnet"}, {"GPT-4o mini", "gpt-4o-mini"}, {"Claude 3 Opus", "claude-3-opus"} }; for (String[] m : models) { Map<String, Object> row = new HashMap<>(); row.put("img", R.mipmap.ic_launcher); row.put("display", m[0]); row.put("name", m[1]); data.add(row); } SimpleAdapter adapter = new SimpleAdapter( this, data, R.layout.item_model, new String[]{"img", "display", "name"}, new int[]{R.id.img_icon, R.id.tv_display, R.id.tv_name} ); ListView listView = findViewById(R.id.list_models); listView.setAdapter(adapter); } }

activity_model_detail.xml里放一个 ListView,id 设为list_models即可。到这里两个 Adapter 都能渲染列表了,但列表里的模型还没有和settings.json真正连起来。

3.3 读取 settings.json 并填充数据

把前面两个 Activity 里的硬编码数据换成从 assets 读取:

private List<Map<String, Object>> loadModelsFromSettings() { List<Map<String, Object>> result = new ArrayList<>(); try { InputStream is = getAssets().open("settings.json"); int size = is.available(); byte[] buffer = new byte[size]; is.read(buffer); is.close(); String json = new String(buffer, StandardCharsets.UTF_8); JSONObject root = new JSONObject(json); JSONArray models = root.getJSONArray("models"); for (int i = 0; i < models.length(); i++) { JSONObject m = models.getJSONObject(i); Map<String, Object> row = new HashMap<>(); row.put("img", R.mipmap.ic_launcher); row.put("display", m.getString("display")); row.put("name", m.getString("name")); result.add(row); } } catch (Exception e) { e.printStackTrace(); } return result; }

这样 SimpleAdapter 的数据源就来自settings.json,你改配置不用改 Java 代码。ArrayAdapter 同理,把display字段抽出来组成List<String>即可。

4. 验证请求:确认列表渲染与请求通道都跑通

列表渲染的验证很直接:运行 App,看到三行模型信息,点击任意一行能拿到对应的name字段。可以在onItemClick里打日志:

listView.setOnItemClickListener((parent, view, position, id) -> { Map<String, Object> item = (Map<String, Object>) parent.getItemAtPosition(position); Log.d("ModelList", "selected model = " + item.get("name")); });

请求通道的验证,用 curl 先确认 Key 和 endpoint 可用:

curl -X POST "https://taotoken.net/api/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的Key" \ -d '{ "model": "claude-3-5-sonnet", "messages": [{"role": "user", "content": "只回复 ok"}] }'

返回体里能看到choices字段就说明通道正常。Android 端用 OkHttp 发同样的请求:

OkHttpClient client = new OkHttpClient(); MediaType JSON = MediaType.parse("application/json; charset=utf-8"); String body = "{\"model\":\"claude-3-5-sonnet\",\"messages\":[{\"role\":\"user\",\"content\":\"只回复 ok\"}]}"; Request request = new Request.Builder() .url("https://taotoken.net/api/v1/chat/completions") .addHeader("Authorization", "Bearer " + apiKey) .post(RequestBody.create(body, JSON)) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("TaoToken", "request failed", e); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d("TaoToken", "code=" + response.code() + " body=" + response.body().string()); } });

两个验证都过了,说明列表和请求通道是通的。想直接在网页里试模型对话,可以打开 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,选模型发一句话看返回。

5. 本篇常见错排查

5.1 SimpleAdapter 显示空白

最常见的原因是from数组和to数组长度不一致,或者 Map 里的 key 和from里的字符串对不上。比如你写了row.put("display", ...),但from里写的是"title",那一列就是空的。检查方法:把from和to打印出来,逐项对照。

5.2 ArrayAdapter 报「You must supply a resource ID for a TextView」

这个错说明你传的行布局里没有 TextView,或者传了0。ArrayAdapter 要求行布局根节点或子节点里至少有一个 TextView。用simple_list_item_1不会出这个问题,自定义布局时记得放 TextView。

5.3 settings.json 读取抛 FileNotFoundException

确认文件路径是app/src/main/assets/settings.json,不是res/raw。assets 目录要手动创建,位置在app/src/main/下面。另外文件名大小写敏感,Settings.json和settings.json是两个文件。

5.4 请求返回 401

先检查Authorization头是不是Bearer sk-xxx格式,中间有一个空格。再检查 Key 有没有复制完整,控制台里重新生成一个对比。如果 curl 能通、Android 不通,多半是 Key 被写死在代码里但没同步更新。

5.5 列表点击后拿不到 name 字段

parent.getItemAtPosition(position)返回的是 Object,SimpleAdapter 返回的是 Map,ArrayAdapter 返回的是 String。如果你混用了两个 Adapter,强转类型会崩。统一用Map接收,或者分别处理。

6. 把配置和列表拆开,后面换模型不用改界面

这套结构跑通之后,你会发现真正稳定的部分是settings.json和请求通道,ListView 只是展示层。以后加模型,改 JSON 就行;换行布局,改item_model.xml就行;换 Adapter,只要保证数据字段对齐,界面代码几乎不用动。如果你后面要做长期编码或 Agent 类功能,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Claude Code 相关配置参考 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。

我自己的习惯是:settings.json里只放模型元数据和 Key 占位,真实 Key 用构建脚本注入,这样本地调试和打包发布互不干扰。列表点击事件里不要直接发请求,先弹一个确认框,避免误触消耗额度。

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

50米巨幕视觉风暴!中湃传媒以极致Keynote美学,助推第四代博越L震撼上市

在汽车新品发布会的舞台上&#xff0c;视觉呈现早已不只是简单的背景板&#xff0c;而是品牌实力的直观表达、产品价值的深度传递&#xff0c;更是整场发布会能否引爆全场、实现破圈传播的核心关键。当第四代博越L携“造每个人的智能精品车”的核心主张惊艳登场&#xff0c;当5…

作者头像 李华
网站建设 2026/9/29 20:40:13

No boot device available报错排查修复:从强制重启到WinPE重建引导

干这行最怕的不是蓝屏&#xff0c;而是一行白底黑字的英文提示&#xff1a;No boot device available。上周同事在远程桌面里重启一台营业厅收银机&#xff0c;命令就一句shutdown -r -f。十几秒后设备对面发来照片&#xff0c;屏幕停在主板LOGO消失后的黑屏上&#xff0c;光一…

作者头像 李华
网站建设 2026/9/29 20:39:58

客户数据不是通讯录:数字化客户经营要建立这5类标签

很多企业的客户数据看起来不少&#xff1a;通讯录几千人、微信群很多、表格也很长。但真正要做复购、唤醒或精准触达时&#xff0c;却不知道该找谁、说什么、什么时候联系。核心观点客户数字化不是把联系方式存进系统&#xff0c;而是建立对客户的持续认知。标签的作用&#xf…

作者头像 李华
网站建设 2026/9/29 20:38:52

Type-C双向供电主从切换方案:LDR6500 IO电平硬切Sink/Source

上周把一个 Type-C 双向供电原型板调通了&#xff0c;最折腾的地方反而不是 DC-DC&#xff0c;也不是功率路径&#xff0c;而是这个口到底该当“供电方”还是“受电方”。同一个 Type-C 口&#xff0c;插到电脑上要当 Sink&#xff0c;从电脑的 PD 口取电&#xff1b;插到充电器…

作者头像 李华
网站建设 2026/9/29 20:38:39

国内MCP资源平台有哪些?TaoToken统一Key接入MCP工具网站推荐

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

作者头像 李华