实现ListView整体垂直滚动,点击项后子任务横向无压缩右移展开
我刚好折腾过类似的层级任务横向展开的需求,你的问题核心就是要避免子任务展开时把父任务挤变形,同时还要有层级右移的视觉效果。下面是亲测有效的实现方案,一步步来:
解决方案步骤
1. 先搞定递归的任务数据结构
首先得有个支持嵌套层级的数据类,用来存任务、子任务,还要记录当前是否展开:
data class Task( val id: String, val title: String, val children: MutableList<Task> = mutableListOf(), var isExpanded: Boolean = false, // 标记当前任务是否展开 val level: Int = 0 // 记录任务层级,用来做右移缩进 )
这里的level字段特别重要,用来控制每个任务项的左边距,实现层级越深越靠右的效果。
2. 设计任务项的布局(item_task.xml)
要避免压缩,核心是让父任务的内容区域宽度固定,子任务在横向方向展开。这里用ConstraintLayout搭配横向RecyclerView来实现:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingVertical="8dp"> <!-- 任务标题区域,固定宽度,防止被压缩 --> <TextView android:id="@+id/tv_task_title" android:layout_width="120dp" android:layout_height="wrap_content" android:gravity="center_vertical" android:paddingHorizontal="16dp" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <!-- 展开/折叠按钮 --> <ImageView android:id="@+id/iv_expand_toggle" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginStart="8dp" android:src="@drawable/ic_expand_more" app:layout_constraintStart_toEndOf="@id/tv_task_title" app:layout_constraintTop_toTopOf="@id/tv_task_title" /> <!-- 子任务横向容器,默认隐藏,展开时显示 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_sub_tasks" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintStart_toEndOf="@id/iv_expand_toggle" app:layout_constraintTop_toTopOf="@id/tv_task_title" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManagerOrientation="horizontal" /> </androidx.constraintlayout.widget.ConstraintLayout>
这里的关键细节:
- 任务标题设固定宽度(比如120dp),不会因为子任务展开被挤窄
- 子任务用横向RecyclerView,展开时在父任务右侧横向排列,层级缩进靠
level控制
3. 实现递归的RecyclerView Adapter
因为任务是嵌套层级,Adapter要递归处理子任务,同时搞定展开/折叠逻辑:
class TaskAdapter(private val tasks: List<Task>) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() { inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tvTitle: TextView = itemView.findViewById(R.id.tv_task_title) val ivToggle: ImageView = itemView.findViewById(R.id.iv_expand_toggle) val rvSubTasks: RecyclerView = itemView.findViewById(R.id.rv_sub_tasks) fun bind(task: Task) { tvTitle.text = task.title // 根据层级设置左边距,实现右移缩进 val indent = task.level * 32 // 每层缩进32dp,可根据需求调整 val params = tvTitle.layoutParams as MarginLayoutParams params.leftMargin = indent tvTitle.layoutParams = params // 显示/隐藏子任务容器 rvSubTasks.visibility = if (task.isExpanded) View.VISIBLE else View.GONE // 更新展开按钮图标 ivToggle.setImageResource(if (task.isExpanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more) // 给子任务设置Adapter,递归嵌套 rvSubTasks.adapter = TaskAdapter(task.children) // 点击任务切换展开状态 itemView.setOnClickListener { task.isExpanded = !task.isExpanded notifyItemChanged(adapterPosition) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_task, parent, false) return TaskViewHolder(view) } override fun onBindViewHolder(holder: TaskViewHolder, position: Int) { holder.bind(tasks[position]) } override fun getItemCount() = tasks.size }
核心逻辑说明:
- 用
level计算左边距,实现层级右移的视觉效果 - 点击时切换
isExpanded状态,通知Adapter更新当前item,显示/隐藏子任务横向列表 - 子任务的RecyclerView复用同一个Adapter,实现递归嵌套展示
4. 主布局和初始化设置
主布局里的RecyclerView要设置横向的LinearLayoutManager,因为整个任务列表是横向展开的:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_main_tasks" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingVertical="16dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManagerOrientation="horizontal" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
然后在Activity/Fragment里初始化示例数据和Adapter:
// 示例嵌套任务数据 val rootTasks = listOf( Task( id = "1", title = "Task A", children = mutableListOf( Task( id = "1-1", title = "Task AA", level = 1, children = mutableListOf( Task(id = "1-1-1", title = "Task AAA", level = 2) ) ) ) ), Task(id = "2", title = "Task B", level = 0) ) rv_main_tasks.adapter = TaskAdapter(rootTasks)
额外优化提示
- 如果任务层级很深,建议用
DiffUtil优化Adapter更新,避免不必要的视图刷新 - 可以缓存子任务的Adapter实例,减少重复创建带来的性能损耗
这样实现后,点击任务时子任务会在父任务右侧横向展开,每个层级的任务自动右移缩进,而且父任务的内容完全不会被压缩,完美符合你的需求。
内容的提问来源于stack exchange,提问作者Shashi
相关产品推荐
相关产品推荐

