尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

CursorTreeAdapter自定义Cursor:从默认光标到个性化交互的完整配置指南

发布时间:2026/9/29 17:24:13

资讯中心
01
ARTICLE

CursorTreeAdapter自定义Cursor:从默认光标到个性化交互的完整配置指南

CursorTreeAdapter自定义Cursor:从默认光标到个性化交互的完整配置指南
1. CursorTreeAdapter 自定义 Cursor 踩坑复盘为什么默认光标总是不听话CursorTreeAdapter 是 Android 里专门给 ExpandableListView 喂数据的适配器它把「组」和「子项」两级数据分别用两个 Cursor 暴露出来。你只要继承它重写五个方法就能让树形列表跑起来。听起来简单但真正上手你会发现官方文档几乎没讲清楚_id到底干嘛用自己写 ContentProvider 时 uri 各种对不上SQLite 查询一嵌套子 View 就展不开。这篇就围绕 CursorTreeAdapter 自定义 Cursor 的完整配置把默认光标替换成个性化交互的每一步拆开讲适合正在做通讯录、部门成员树、分类菜单这类两级列表的 Android 开发者。我第一次接这个需求是做公司组织架构页部门是组成员是子项。数据全在 SQLite 里一个部门下几十号人滚动时重复查询导致卡顿点击展开还偶发错位。后来才明白问题不在 ExpandableListView而在 Cursor 的构造和_id的语义没吃透。CursorTreeAdapter 要求顶层 Cursor 暴露组getChildrenCursor()返回的 Cursor 暴露子项而且两个 Cursor 都必须包含名为_id的列否则类直接不工作。这个_id不是随便的序号它是「一组数据里成员的唯一标识」你自己操控数据时可以自定义但数据库内分组 id 必须保持一致否则展开时子项会串组。默认光标系统给的简单 Cursor只能做最基础的绑定样式、点击态、拖拽反馈全是默认灰。要个性化就得自定义 Cursor 的列结构 重写绑定方法 控制查询时机。下面从环境准备到可复制配置再到验证和排错一步步来。核心检索词先记住CursorTreeAdapter 自定义 Cursor、ExpandableListView 树形光标配置、getChildrenCursor 返回子 Cursor。2. TaoToken 前置准备给 Cursor 配置一个稳定的模型调用入口这一节讲的是「前置」——不是让你去注册一堆账号而是把后面写代码时可能用到的模型调用能力先备好。很多同学在调 CursorTreeAdapter 的同时还想接一个 AI 辅助生成绑定代码或排查报错这时候一个统一的 API 入口能省不少事。TaoToken 提供的就是这样一个入口官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 这个不加 UTM。它兼容常见的模型对话接口你可以把它当成一个「模型网关」在写 Android 代码卡壳时用模型对话快速问一句「CursorTreeAdapter 的 bindChildView 为什么没触发」。具体怎么拿 Key进控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 创建一个 Key。拿到之后如果你用的是 Claude Code 这类编码工具可以走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 把 Base URL 指向 https://taotoken.net/api Key 填进去Model ID 按文档选。这样你在 IDE 里写 CursorTreeAdapter 的五个重写方法时能直接让模型补全或解释报错。注意TaoToken 只是模型调用入口不是编辑器替代品也不碰你的生产数据库。你本地 SQLite 的读写、ContentProvider 的 uri 配置还是得自己写。前置准备的意义在于当你在第 5 节遇到local proxy failed或reading choices这类报错时能快速用模型对话定位是网络配置还是代码逻辑问题。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。把这三样Base URL、Key、Model ID记牢后面配置片段会用到。3. 可复制配置CursorTreeAdapter 自定义 Cursor 的完整代码片段这一节是全文核心直接给可复制的配置。先看数据层SQLite 建两张表组表和子项表子项表用group_id关联组表的_id。注意 CursorTreeAdapter 要求每个 Cursor 都有_id列所以查询时要么SELECT _id, name, ...要么用别名SELECT group_id AS _id。CREATE TABLE dept ( _id INTEGER PRIMARY KEY AUTOINCREMENT, dept_name TEXT NOT NULL ); CREATE TABLE member ( _id INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER NOT NULL, member_name TEXT NOT NULL, avatar TEXT );然后是自定义 CursorTreeAdapter 的骨架。五个必须重写的方法newGroupView、bindGroupView、newChildView、bindChildView、getChildrenCursor。前四个管视图创建和绑定最后一个管子 Cursor 查询。public class DeptTreeAdapter extends CursorTreeAdapter { private LayoutInflater inflater; private SQLiteDatabase db; public DeptTreeAdapter(Cursor cursor, Context context, SQLiteDatabase db) { super(cursor, context); this.inflater LayoutInflater.from(context); this.db db; } Override protected Cursor getChildrenCursor(Cursor groupCursor) { int groupId groupCursor.getInt(groupCursor.getColumnIndexOrThrow(_id)); // 关键子 Cursor 必须含 _id 列这里用 member 表的 _id return db.rawQuery( SELECT _id, member_name, avatar FROM member WHERE group_id ?, new String[]{String.valueOf(groupId)} ); } Override protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { View v inflater.inflate(R.layout.item_dept, parent, false); v.setTag(new GroupHolder(v)); return v; } Override protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { GroupHolder holder (GroupHolder) view.getTag(); holder.name.setText(cursor.getString(cursor.getColumnIndexOrThrow(dept_name))); // 自定义光标样式展开时换箭头 holder.arrow.setImageResource(isExpanded ? R.drawable.ic_arrow_up : R.drawable.ic_arrow_down); } Override protected View newChildView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { View v inflater.inflate(R.layout.item_member, parent, false); v.setTag(new ChildHolder(v)); return v; } Override protected void bindChildView(View view, Context context, Cursor cursor, boolean isExpanded) { ChildHolder holder (ChildHolder) view.getTag(); holder.name.setText(cursor.getString(cursor.getColumnIndexOrThrow(member_name))); // 个性化交互点击态、拖拽手柄 holder.dragHandle.setVisibility(View.VISIBLE); } static class GroupHolder { TextView name; ImageView arrow; GroupHolder(View v) { name v.findViewById(R.id.dept_name); arrow v.findViewById(R.id.arrow); } } static class ChildHolder { TextView name; ImageView dragHandle; ChildHolder(View v) { name v.findViewById(R.id.member_name); dragHandle v.findViewById(R.id.drag_handle); } } }如果你用 ContentProvideruri 配置要对应上。比如content://com.example.provider/dept查组content://com.example.provider/member/#查某组子项。getChildrenCursor里换成context.getContentResolver().query(...)即可但注意 uri 的匹配规则要在UriMatcher里写全否则子 View 展不开。配置片段settings 风格用于记录你的接入参数{ base_url: https://taotoken.net/api, api_key: 你的Key, model_id: 按文档选择, note: CursorTreeAdapter 调试时用模型对话排查报错 }把这段 JSON 存成taotoken.json放项目根目录方便自己查。注意 Base URL 和 Key 要配套Model ID 别填错否则第 5 节会报 401。4. 验证请求与成功结果点击、拖拽、展开的实测步骤配置写完怎么验证自定义 Cursor 真的生效分三步。第一步验证组 Cursor。在 Activity 里初始化Cursor groupCursor db.rawQuery(SELECT _id, dept_name FROM dept, null); DeptTreeAdapter adapter new DeptTreeAdapter(groupCursor, this, db); ExpandableListView listView findViewById(R.id.tree_list); listView.setAdapter(adapter);跑起来后组列表应该正常显示部门名。如果空白先查_id列是否存在——groupCursor.getColumnIndex(_id)返回 -1 就是没这列CursorTreeAdapter 直接罢工。第二步验证子 Cursor。点击某个部门getChildrenCursor被调用返回成员 Cursor。你可以在方法里打日志Log.d(TreeAdapter, groupId groupId , count cursor.getCount());展开后成员出现说明子 Cursor 查询通了。实测下来如果成员重复出现多半是group_id关联写错或者_id用了组 id 导致串组。第三步验证个性化交互。点击子项bindChildView里的点击态应该触发长按拖拽手柄能拖动排序。拖拽需要配合ExpandableListView的setOnChildClickListener和自定义DragListener这里不展开但核心是绑定方法里拿到的 cursor 位置要和实际数据一致否则拖拽后数据错位。成功结果长这样部门展开箭头切换正常成员列表无重复点击有高亮拖拽后顺序更新且数据库同步。如果这些都过了说明自定义 Cursor 配置成功。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错逐个拆。401 Unauthorized如果你在调试时用模型对话辅助Key 填错或过期会报 401。检查taotoken.json里的 api_key 是否和控制台一致Base URL 是否是https://taotoken.net/api。注意别把官网地址当 API 地址填。local proxy failed本地代理配置冲突。Android 模拟器或 IDE 里如果设了代理模型请求会走不通。检查gradle.properties或 IDE 的 HTTP Proxy 设置关掉不必要的代理或者确认代理规则没拦taotoken.net。reading choices 报错模型返回结构解析失败通常是 Model ID 填错或请求体格式不对。对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 检查请求 JSONmodel字段要和文档一致。OAuth 相关报错如果你用 Claude Code 接入走的是 OAuth 流程报错多半是回调地址或 token 过期。重新走一遍授权确认 Base URL 和 Key 配套。Claude Code 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 按步骤来。另外CursorTreeAdapter 本身的坑_id列缺失、getChildrenCursor返回 null、ContentProvider uri 不匹配这三个占报错的大头。排查顺序先确认两个 Cursor 都有_id再确认 uri 匹配规则最后看绑定方法有没有被调用。6. 语义一致 CTA把配置落到你的项目里到这里CursorTreeAdapter 自定义 Cursor 的完整链路就通了建表带_id、重写五个方法、getChildrenCursor返回子 Cursor、绑定方法里做个性化样式。你可以在自己的项目里直接复制第 3 节的代码把dept和member换成你的表名跑一遍第 4 节的验证步骤。如果调试时卡在报错用模型对话 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 快速问一句比翻文档快。长期做 Android 编码或 Agent 相关开发可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 把 Base URL、Key、Model ID 三件套配好写绑定代码时让模型补全。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后提醒一句_id是 CursorTreeAdapter 的命门组和子项的_id语义要分清组用组 id子项用成员 id别混。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。