You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中如何让可展开列表头部显示正确的子项数量

解决Kotlin中ExpandableListView分组头部子项数量显示异常的问题

我来帮你搞定这个头疼的分组数量显示问题!结合你描述的场景(API返回今日/明日/未来任务及对应总数,用HashMap存分组数据),核心问题大概率是分组的总数和子项数据没有正确绑定,或者你误用了子项列表的size来显示总数(如果API返回的总数和实际子项数量不一致的话)。下面是一步步的解决方案:


1. 重构分组数据模型,避免HashMap的匹配问题

别再用单纯的HashMap<String, List<Task>>了,创建一个自定义数据类把分组的标题、总数、子项列表绑定在一起,从根源上避免键值对不匹配的问题:

// 定义分组数据类,把标题、总数、子项打包
data class TaskGroup(
    val groupTitle: String,
    val totalTaskCount: Int,
    val taskList: List<Task>
)

2. 重写ExpandableListAdapter,直接绑定API返回的总数

自定义Adapter时,在getGroupView方法里直接使用TaskGroup里的totalTaskCount,而不是去计算taskList.size(毕竟API返回的总数可能包含未加载的分页任务,或者和实际返回的子项数量有差异)。示例代码如下:

class TaskExpandableAdapter(
    private val context: Context,
    private val groupList: List<TaskGroup>
) : BaseExpandableListAdapter() {

    // 获取分组总数
    override fun getGroupCount(): Int = groupList.size

    // 获取当前分组的子项数量(用于列表展开/折叠逻辑)
    override fun getChildrenCount(groupPosition: Int): Int = groupList[groupPosition].taskList.size

    override fun getGroup(groupPosition: Int): Any = groupList[groupPosition]

    override fun getChild(groupPosition: Int, childPosition: Int): Any = groupList[groupPosition].taskList[childPosition]

    override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()

    override fun getChildId(groupPosition: Int, childPosition: Int): Long = childPosition.toLong()

    override fun hasStableIds(): Boolean = true

    // 渲染分组头部视图
    override fun getGroupView(
        groupPosition: Int,
        isExpanded: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {
        val headerView = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.layout_group_header, parent, false)
        
        // 获取当前分组的数据
        val currentGroup = groupList[groupPosition]
        
        // 设置分组标题和总数
        headerView.findViewById<TextView>(R.id.tv_group_title).text = currentGroup.groupTitle
        // 这里直接用API返回的totalTaskCount,而不是taskList.size!
        headerView.findViewById<TextView>(R.id.tv_task_count).text = "(${currentGroup.totalTaskCount})"
        
        // 可选:根据展开状态调整箭头图标
        val ivArrow = headerView.findViewById<ImageView>(R.id.iv_expand_arrow)
        ivArrow.setImageResource(if (isExpanded) R.drawable.ic_arrow_up else R.drawable.ic_arrow_down)
        
        return headerView
    }

    // 渲染子项视图
    override fun getChildView(
        groupPosition: Int,
        childPosition: Int,
        isLastChild: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {
        val childView = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.layout_task_item, parent, false)
        
        val currentTask = groupList[groupPosition].taskList[childPosition]
        childView.findViewById<TextView>(R.id.tv_task_title).text = currentTask.title
        // 其他子项UI逻辑...
        
        return childView
    }
}

3. 组装API数据并绑定Adapter

从API获取ToDoListResponse后,把数据转换成TaskGroup列表,再设置给ExpandableListView:

// 假设你的API接口返回ToDoListResponse,包含todayCount、todayTasks等字段
apiClient.getTodoList().enqueue(object : Callback<ToDoListResponse> {
    override fun onResponse(call: Call<ToDoListResponse>, response: Response<ToDoListResponse>) {
        response.body()?.let { todoResp ->
            val taskGroups = mutableListOf<TaskGroup>().apply {
                add(TaskGroup("今日", todoResp.todayCount, todoResp.todayTasks))
                add(TaskGroup("明日", todoResp.tomorrowCount, todoResp.tomorrowTasks))
                add(TaskGroup("未来", todoResp.futureCount, todoResp.futureTasks))
            }
            
            // 初始化Adapter并绑定到ExpandableListView
            val adapter = TaskExpandableAdapter(requireContext(), taskGroups)
            expandableListView.setAdapter(adapter)
            
            // 可选:默认展开所有分组
            for (i in 0 until adapter.groupCount) {
                expandableListView.expandGroup(i)
            }
        }
    }

    override fun onFailure(call: Call<ToDoListResponse>, t: Throwable) {
        // 处理请求失败逻辑,比如显示Toast
        Toast.makeText(context, "加载任务失败:${t.message}", Toast.LENGTH_SHORT).show()
    }
})

4. 常见排查点

如果还是有问题,检查这几个地方:

  • API数据准确性:确认返回的todayCount、tomorrowCount等字段值是否正确,有没有和实际子项数量不符的情况。
  • 视图复用问题:在getGroupView里一定要确保每次都重新绑定数据,不要因为convertView复用导致旧数据残留。
  • 分组匹配问题:之前用HashMap时,可能存在分组标题字符串不匹配的情况(比如大小写、空格),用TaskGroup列表完全避免了这个问题。

内容的提问来源于stack exchange,提问作者Jay Mistry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:01:12