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

RecyclerView如何实现动态项单行折叠 点击行展示全部子项

问题根源

现有实现没有对同批次添加的清单项做分组标记,存储和渲染都是单条目平铺逻辑,自然会每个项单独占一行。要实现折叠展开效果,需要做两级列表结构+分组存储+展开状态控制三个核心改造。


分步改造方案

1. 改造数据层,增加批次分组字段

首先修改ChecklistHelper实体类,新增3个字段:

// 批次ID,同一次保存的所有清单项共用同一个batchId
private long batchId;
// 条目类型:1=分组头(折叠时显示的单行),0=普通子项
private int itemType;
// 分组下的子项列表,仅分组头条目使用这个字段
private List<ChecklistHelper> childItems;
// 自行补全对应字段的get/set方法

对应修改本地数据库的checklist表,新增batchId(长整型)、itemType(整型)两个字段。

2. 修改添加页的保存逻辑,生成分组

原来的保存逻辑有两个问题:一是ChecklistHelper在循环外初始化,所有条目会复用同一个对象导致状态错乱;二是没有给同批次条目打统一标记。修改AddChecklist的保存逻辑如下:

case R.id.btn_save:
    // 同批次条目共用同一个时间戳作为batchId,保证全局唯一
    long currentBatchId = System.currentTimeMillis();
    // 1. 先插入分组头记录
    ChecklistHelper groupHeader = new ChecklistHelper();
    groupHeader.setBatchId(currentBatchId);
    groupHeader.setItemType(1);
    groupHeader.setStatus("0");
    // 分组头显示文本可以自定义,比如默认显示"新建清单项组(共X项)"
    groupHeader.setContent("新建清单项组(共" + linearLayout.getChildCount() + "项)");
    dbHelper.insertChecklist(groupHeader.getStatus(), groupHeader.getContent(),
            DateTime.date(), DateTime.time(), currentBatchId, 1);

    // 2. 循环插入所有同批次子项
    for (int i = 0; i < linearLayout.getChildCount(); i++) {
        View v = linearLayout.getChildAt(i);
        EditText etChecklistItem = v.findViewById(R.id.et_checklist_item);
        CheckBox checkBox = v.findViewById(R.id.check_box);
        ChecklistHelper childItem = new ChecklistHelper();
        childItem.setBatchId(currentBatchId);
        childItem.setItemType(0);
        childItem.setStatus(checkBox.isChecked() ? "1" : "0");
        childItem.setContent(etChecklistItem.getText().toString());
        // 对应修改insertChecklist方法,新增batchId、itemType两个入参
        dbHelper.insertChecklist(childItem.getStatus(), childItem.getContent(),
                DateTime.date(), DateTime.time(), currentBatchId, 0);
    }
    finish();
    break;

注意:同步修改DbHelper的insertChecklist方法,新增的两个字段要写入数据库。

3. 改造列表页数据预处理,聚合分组数据

原来从数据库取出来的是所有平铺的记录,需要先聚合成分组结构,修改ChecklistItems页初始化数据的逻辑:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_checklist_items);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    
    dbHelper = new DbHelper(this);
    // 1. 取出所有原始记录
    List<ChecklistHelper> rawList = dbHelper.getChecklists();
    // 2. 聚合分组
    List<ChecklistHelper> displayList = new ArrayList<>();
    HashMap<Long, ChecklistHelper> groupMap = new HashMap<>();
    for (ChecklistHelper item : rawList) {
        if (item.getItemType() == 1) {
            // 是分组头,初始化子项列表
            item.setChildItems(new ArrayList<>());
            groupMap.put(item.getBatchId(), item);
            displayList.add(item);
        } else {
            // 是子项,归到对应分组下
            ChecklistHelper group = groupMap.get(item.getBatchId());
            if (group != null) {
                group.getChildItems().add(item);
            }
        }
    }
    // 3. 把处理好的带分组结构的数据传给适配器
    checklistAdapter = new ChecklistAdapter(displayList);
    recyclerView.setAdapter(checklistAdapter);
}

4. 新增分组头布局,改造适配器支持折叠展开

首先新增分组头布局R.layout.checklist_layout_group,布局内放一个TextView显示组名、一个ImageView显示展开/折叠箭头即可。
然后改造ChecklistAdapter,支持多视图类型、展开状态控制:

public class ChecklistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private boolean isEnableDelete = false;
    private final List<ChecklistHelper> displayList;
    // 记录每个分组的展开状态,key是分组的adapterPosition,value是是否展开
    private final SparseArray<Boolean> expandStatus = new SparseArray<>();
    private final int TYPE_GROUP = 1;
    private final int TYPE_CHILD = 0;

    public ChecklistAdapter(List<ChecklistHelper> displayList) {
        this.displayList = displayList;
        // 默认所有分组都是折叠状态
        for (int i = 0; i < displayList.size(); i++) {
            expandStatus.put(i, false);
        }
    }

    @Override
    public int getItemViewType(int position) {
        return displayList.get(position).getItemType();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        if (viewType == TYPE_GROUP) {
            View groupView = layoutInflater.inflate(R.layout.checklist_layout_group, parent, false);
            return new GroupViewHolder(groupView);
        } else {
            View childView = layoutInflater.inflate(R.layout.checklist_layout_child, parent, false);
            return new ChecklistChildViewHolder(childView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ChecklistHelper currentItem = displayList.get(position);
        if (holder instanceof GroupViewHolder) {
            GroupViewHolder groupHolder = (GroupViewHolder) holder;
            groupHolder.tvGroupName.setText(currentItem.getContent());
            // 根据展开状态切换箭头方向
            boolean isExpanded = Boolean.TRUE.equals(expandStatus.get(position));
            groupHolder.imgArrow.setRotation(isExpanded ? 90 : 0);
            
            // 点击分组头切换展开折叠
            groupHolder.itemView.setOnClickListener(v -> {
                boolean currentExpand = Boolean.TRUE.equals(expandStatus.get(position));
                expandStatus.put(position, !currentExpand);
                if (!currentExpand) {
                    // 从折叠切展开:把该组所有子项插入到分组头后面
                    displayList.addAll(position + 1, currentItem.getChildItems());
                    notifyItemRangeInserted(position + 1, currentItem.getChildItems().size());
                } else {
                    // 从展开切折叠:移除该组所有子项
                    int childCount = currentItem.getChildItems().size();
                    for (int i = 0; i < childCount; i++) {
                        displayList.remove(position + 1);
                    }
                    notifyItemRangeRemoved(position + 1, childCount);
                }
                notifyItemChanged(position);
            });
        } else if (holder instanceof ChecklistChildViewHolder) {
            ChecklistChildViewHolder childHolder = (ChecklistChildViewHolder) holder;
            childHolder.updateVisibility(isEnableDelete);
            childHolder.checkBox.setText(currentItem.getContent());
            // 处理勾选删除线逻辑
            if ("1".equals(currentItem.getStatus())) {
                childHolder.checkBox.setChecked(true);
                childHolder.checkBox.setText(lineThrough(currentItem.getContent()));
            } else {
                childHolder.checkBox.setChecked(false);
                childHolder.checkBox.setText(currentItem.getContent());
            }

            childHolder.checkBox.setOnCheckedChangeListener((compoundButton, b) -> {
                String txt = currentItem.getContent();
                if (b) {
                    childHolder.checkBox.setText(lineThrough(txt));
                    currentItem.setStatus("1");
                } else {
                    childHolder.checkBox.setText(txt);
                    currentItem.setStatus("0");
                }
            });

            childHolder.delete.setOnClickListener(v -> {
                dbHelper.deleteChecklist(currentItem.getId());
                // 删除后局部刷新即可,不用直接关闭页面
                int removePos = holder.getAdapterPosition();
                displayList.remove(removePos);
                notifyItemRemoved(removePos);
            });
        }
    }

    @Override
    public int getItemCount() {
        return displayList.size();
    }

    public void updateItems(boolean isEnableDelete){
        this.isEnableDelete = isEnableDelete;
        notifyDataSetChanged();
    }

    // 分组头ViewHolder
    public class GroupViewHolder extends RecyclerView.ViewHolder {
        TextView tvGroupName;
        ImageView imgArrow;
        public GroupViewHolder(@NonNull View itemView) {
            super(itemView);
            tvGroupName = itemView.findViewById(R.id.tv_group_name);
            imgArrow = itemView.findViewById(R.id.img_arrow);
        }
    }

    public class ChecklistChildViewHolder extends RecyclerView.ViewHolder {
        CheckBox checkBox;
        ImageView delete, edit;

        ChecklistChildViewHolder(@NonNull View itemView) {
            super(itemView);
            checkBox = itemView.findViewById(R.id.checkbox_child);
            delete = itemView.findViewById(R.id.delete_child);
            edit = itemView.findViewById(R.id.edit_child);
            delete.setVisibility(View.INVISIBLE);
            edit.setVisibility(View.INVISIBLE);
        }

        public void updateVisibility(boolean isEnableDelete){
            if (isEnableDelete) {
                delete.setVisibility(View.VISIBLE);
                edit.setVisibility(View.VISIBLE);
            } else {
                delete.setVisibility(View.INVISIBLE);
                edit.setVisibility(View.INVISIBLE);
            }
        }
    }
}

补充优化点
  • 可以给展开折叠加上RecyclerView默认的ItemAnimator,交互更自然
  • 分组头的显示文本可以自定义,比如取第一个子项的内容加“等X项”的后缀
  • 数据库查询的时候可以按batchId排序,避免分组顺序错乱
  • 编辑模式下的子项修改逻辑,可以在折叠时同步更新对应分组头的显示文本

内容的提问来源于stack exchange,提问作者nano tech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 07:45:30