Android 实例-个人理财工具 之五 账单明细显示A:SimpleCursorAdapter 配 TaoToken 统一 Key 通道
发布时间:2026/9/27 22:41:39来源:尧图网络
1. 账单明细页为什么卡在 SimpleCursorAdapter 这一步做 Android 个人理财工具到第五篇前面的记账录入已经能跑通了数据也老老实实躺在 SQLite 里。真正让人挠头的是明细页表头、脚注都摆好了中间那个 ListView 却空着或者显示出来的金额全是整数小数点后面的分直接消失。这个场景里核心检索词就是 Android、ListView、Adapter、SQLite、SimpleCursorAdapter——它们串起来就是一条数据链路SQLite 查询返回 CursorCursor 通过 SimpleCursorAdapter 把列映射到 item 布局里的 TextView最后 ListView 负责渲染。SimpleCursorAdapter 适合谁适合表结构固定、列和控件一一对应的账单列表。它不需要你手写 ViewHolder也不用自己管理 Cursor 生命周期初始化时把from列名数组和to控件 id 数组对齐就能出效果。但它的坑也很集中列名写错会直接崩金额用整数存储再除 100 会丢精度Cursor 没关会导致重复查询时资源泄漏。这篇就把这些环节拆开给出可复制的 Adapter 初始化代码、列映射配置以及用 TaoToken 统一 Key 通道接入 settings.json 的骨架最后在真机上验证明细列表正确显示。我试过把金额直接select fee/100塞进 Cursor结果 ListView 上全是取整后的数字999999.99 变成 1000000排查半天才发现是 SQLite 整数除法加 Adapter 取字符串的锅。下面按步骤来你可以直接对照改。2. TaoToken 前置统一 Key 通道与 settings.json 骨架在写 Adapter 之前先把配置通道理清楚。个人理财工具后面如果要接模型做账单分类、备注补全或者用 Coding Plan 辅助写查询逻辑Key 散落在各个文件里会很难维护。TaoToken 提供统一 Key 通道官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。统一 Key 的好处是Android 工程里只维护一份 settings.json模型对话、编码辅助、控制台管理都走同一个通道换环境时改一处即可。settings.json 骨架可以这样放建议放在项目根目录的config/下不要提交到公开仓库{ taotoken: { baseUrl: https://taotoken.net/api, apiKey: sk-你的统一Key, channel: unified, timeoutMs: 30000, models: { chat: 默认对话模型, coding: 默认编码模型 } }, app: { dbName: cola_bills.db, dbVersion: 1, pageSize: 50 } }Key 的获取和控制台管理在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。如果你只是先验证模型能不能用可以直接去模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 试一条如果是长期编码、Agent 场景走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 更合适。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite ClaudeCodeAnthropic 相关在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。注意settings.json 里的 apiKey 不要硬编码进 Java 源码也不要在 Log 里打印完整 Key。Android 端可以用 BuildConfig 注入或者运行时从 assets 读取后只保留内存引用。3. 可复制配置Cursor 列映射与 SimpleCursorAdapter 初始化3.1 表结构与查询语句假设账单表 bills 的字段是rowid、name账目、fee费用整数单位分、sdate日期、desc备注。查询时不要用fee/100这种整数除法改成把分转成带两位小数的字符串或者直接在 Java 层格式化。推荐 SQL 这样写SELECT rowid AS _id, name, fee, sdate, desc FROM bills WHERE sdate LIKE 2024-11% ORDER BY sdate DESC;这里有个关键点SimpleCursorAdapter 要求 Cursor 里必须有一个叫_id的列否则会抛IllegalArgumentException: column _id does not exist。所以查询里用rowid AS _id显式别名别直接用rowid当列名去映射。3.2 item 布局 grid_items.xml每个列表项是一个横向 LinearLayout五个 TextView 对应五列?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:orientationhorizontal android:layout_widthmatch_parent android:layout_heightwrap_content android:padding6dp TextView android:idid/item_id android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:textSize12sp / TextView android:idid/item_name android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:textSize14sp / TextView android:idid/item_fee android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:textSize14sp android:textStylebold / TextView android:idid/item_date android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:textSize12sp / TextView android:idid/item_desc android:layout_width0dp android:layout_heightwrap_content android:layout_weight3 android:textSize12sp / /LinearLayout3.3 Adapter 初始化代码public class BillListActivity extends Activity { private BillDbHelper billDb; private ListView lv; private SimpleCursorAdapter adapter; private Cursor cursor; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.grid_bills); billDb new BillDbHelper(this); lv findViewById(R.id.listview); cursor billDb.getBills(2024-11); String[] from new String[]{_id, name, fee, sdate, desc}; int[] to new int[]{ R.id.item_id, R.id.item_name, R.id.item_fee, R.id.item_date, R.id.item_desc }; adapter new SimpleCursorAdapter( this, R.layout.grid_items, cursor, from, to, 0 ); adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { Override public boolean setViewValue(View view, Cursor c, int columnIndex) { if (view.getId() R.id.item_fee) { int feeInCents c.getInt(columnIndex); String yuan String.format(Locale.CHINA, %.2f, feeInCents / 100.0); ((TextView) view).setText(yuan); return true; } return false; } }); lv.setAdapter(adapter); } Override protected void onDestroy() { super.onDestroy(); if (cursor ! null !cursor.isClosed()) { cursor.close(); } if (billDb ! null) { billDb.close(); } } }这里用 ViewBinder 处理金额把整数分转成两位小数的元避免 SQL 里整数除法丢精度。from数组里的_id对应查询别名to数组顺序必须和from一一对应错位会导致数据串列。3.4 表头与脚注布局 grid_bills.xml?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:orientationvertical android:layout_widthmatch_parent android:layout_heightmatch_parent LinearLayout android:idid/layouthead android:orientationhorizontal android:background#CDED8B android:layout_widthmatch_parent android:layout_heightwrap_content android:padding6dp TextView android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:textID android:textStylebold / TextView android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:text账目 android:textStylebold / TextView android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:text费用(元) android:textStylebold / TextView android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 android:text日期 android:textStylebold / TextView android:layout_width0dp android:layout_heightwrap_content android:layout_weight3 android:text备注 android:textStylebold / /LinearLayout ListView android:idid/listview android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / LinearLayout android:idid/layoutfoot android:background#CDED8B android:layout_widthmatch_parent android:layout_heightwrap_content android:padding6dp TextView android:idid/totalitem android:layout_widthmatch_parent android:layout_heightwrap_content android:textStylebold android:text当月收入:0.00 支出:0.00 小计:0.00 / /LinearLayout /LinearLayout4. 验证请求与成功结果4.1 真机运行步骤第一步把 settings.json 放到app/src/main/assets/config/settings.json在 Application 初始化时读取确认 baseUrl 和 apiKey 能正常解析。第二步编译安装到真机进入账单明细页。第三步观察 ListView 是否显示表头下方五列数据金额是否带两位小数。如果要用 TaoToken 通道验证模型侧是否通可以在控制台或模型对话页发一条测试请求确认 Key 有效。Android 端接入时网络请求建议放在子线程主线程只更新 UI。4.2 成功结果对照真机运行后明细列表应该呈现类似下面的效果ID账目费用(元)日期备注1餐饮35.502024-11-01午餐2交通12.002024-11-01地铁3购物199.992024-11-02日用品脚注显示当月收入、支出、小计。金额不再出现 999999.99 变成 1000000 的情况因为格式化在 Java 层用%.2f完成没有走 SQLite 整数除法。4.3 用 adb 快速核对数据adb shell run-as com.example.cola cd databases sqlite3 cola_bills.db sqlite SELECT rowid, name, fee, sdate, desc FROM bills LIMIT 5;确认数据库里 fee 存的是整数分比如 3550 对应 35.50 元。如果数据库里就是错的那问题在录入环节不在 Adapter。5. 本篇常见错排查5.1 崩溃column _id does not existSimpleCursorAdapter 强制要求 Cursor 有_id列。查询里写SELECT rowid AS _id, ...不要只写rowid。如果表本身有自增主键且列名不是_id也要用 AS 别名。5.2 金额显示为整数或精度丢失原因通常是 SQL 里写了fee/100SQLite 对整数做除法会截断。解决办法有两个一是查询直接返回fee在 ViewBinder 里除以 100.0 并格式化二是 SQL 里用printf(%.2f, fee/100.0)转成字符串。推荐第一种逻辑更清晰。5.3 列表数据串列或空白检查from和to的顺序是否严格对应。from第 1 个列名映射到to第 1 个控件 id依次类推。如果某个 TextView 在 item 布局里 id 写错对应列会空白但不一定崩。5.4 Cursor 未关闭导致重复数据每次查询返回新 Cursor 后旧 Cursor 要 close。在 onDestroy 里关闭当前 Cursor 和数据库 helper。如果用了 LoaderManager 或 CursorLoader交给框架管理不要手动 close。5.5 真机上 ListView 不滚动或高度为 0ListView 放在 LinearLayout 里时高度不要写wrap_content配fill_parent混用建议用0dp加layout_weight1让它占满中间剩余空间。外层如果套了 ScrollViewListView 会测量异常明细页不要用 ScrollView 包 ListView。5.6 TaoToken 请求超时或 401先确认 settings.json 里 baseUrl 是 https://taotoken.net/api 没有多余斜杠apiKey 没有前后空格请求头里 Authorization 格式正确。401 一般是 Key 无效或过期去 API Keys 页面重新生成。超时先调大 timeoutMs再检查真机网络是否正常。接入细节以接入文档为准。6. 继续把明细页做完整明细列表能正确显示之后下一步通常是加点击跳转编辑、按月份筛选、以及把脚注的合计做成动态查询。SimpleCursorAdapter 在数据量不大时够用如果后面要加复杂 item 动画或多种布局再考虑换成 RecyclerView 加 CursorAdapter 或 Room。当前这套代码可以直接复制进你的工程重点盯住三处查询里的_id别名、from/to 的顺序、金额在 ViewBinder 里格式化。把这三处对齐真机上明细列表就能稳定显示后面接 TaoToken 统一 Key 通道做智能分类时也不会被数据层拖后腿。
网站建设高端定制企业官网