Android中实现工厂→部门-科室→产线→设备四级ExpandableListView
四级层级列表实现方案(Plant→Dept-Section→Line→Device)
方案选型:优先推荐RecyclerView嵌套方案
原生ExpandableListView仅支持两级,嵌套ExpandableListView会存在滚动冲突、复用性差的问题,子项嵌入RecyclerView的方案性能和扩展性更优:
- 利用RecyclerView的视图复用机制,滚动流畅度更高
- 多类型Item支持轻松适配不同层级的UI
- 后续扩展更多层级时改动更小
如果必须基于ExpandableListView改造,也可以通过将中间层级伪装成"Group"实现,但逻辑复杂度更高,不推荐。
第一步:设计嵌套式数据结构
用实体类嵌套对应层级关系,每个父节点包含下一级子节点列表:
// 工厂实体 public class Plant { public String id; public String name; public boolean isExpanded; // 记录展开状态 public List<DeptSection> deptSections; } // 部门-科室实体 public class DeptSection { public String id; public String name; public boolean isExpanded; public List<Line> lines; } // 产线实体 public class Line { public String id; public String name; public boolean isExpanded; public List<Device> devices; } // 设备实体 public class Device { public String id; public String name; }
第二步:实现多类型嵌套RecyclerView Adapter
核心思路是:
- 定义四种Item类型:PLANT、DEPT_SECTION、LINE、DEVICE
- 每个类型对应独立的ViewHolder,其中DeptSection和Line的ViewHolder中嵌入RecyclerView
- 维护各层级的展开/折叠状态,点击时切换状态并刷新对应Item
关键代码示例:
public class HierarchyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int TYPE_PLANT = 0; private static final int TYPE_DEPT_SECTION = 1; private static final int TYPE_LINE = 2; private static final int TYPE_DEVICE = 3; private List<Object> mDataList; // 混合存储所有层级的实体 // 构造方法:将Plant列表转成扁平的混合列表(初始仅显示Plant) public HierarchyAdapter(List<Plant> plantList) { mDataList = new ArrayList<>(); for (Plant plant : plantList) { mDataList.add(plant); // 默认折叠,不添加子项 } } @Override public int getItemViewType(int position) { Object item = mDataList.get(position); if (item instanceof Plant) return TYPE_PLANT; if (item instanceof DeptSection) return TYPE_DEPT_SECTION; if (item instanceof Line) return TYPE_LINE; if (item instanceof Device) return TYPE_DEVICE; return -1; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch (viewType) { case TYPE_PLANT: View plantView = inflater.inflate(R.layout.item_plant, parent, false); return new PlantViewHolder(plantView); case TYPE_DEPT_SECTION: View deptView = inflater.inflate(R.layout.item_dept_section, parent, false); return new DeptSectionViewHolder(deptView); case TYPE_LINE: View lineView = inflater.inflate(R.layout.item_line, parent, false); return new LineViewHolder(lineView); case TYPE_DEVICE: View deviceView = inflater.inflate(R.layout.item_device, parent, false); return new DeviceViewHolder(deviceView); default: throw new IllegalArgumentException("Unknown view type"); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = mDataList.get(position); switch (getItemViewType(position)) { case TYPE_PLANT: Plant plant = (Plant) item; PlantViewHolder plantHolder = (PlantViewHolder) holder; plantHolder.tvName.setText(plant.name); plantHolder.ivArrow.setRotation(plant.isExpanded ? 90 : 0); // 点击切换展开/折叠 plantHolder.itemView.setOnClickListener(v -> { plant.isExpanded = !plant.isExpanded; plantHolder.ivArrow.setRotation(plant.isExpanded ? 90 : 0); toggleChildItems(position, plant.deptSections, plant.isExpanded); }); break; case TYPE_DEPT_SECTION: DeptSection dept = (DeptSection) item; DeptSectionViewHolder deptHolder = (DeptSectionViewHolder) holder; deptHolder.tvName.setText(dept.name); deptHolder.ivArrow.setRotation(dept.isExpanded ? 90 : 0); // 配置子RecyclerView(产线列表) LineAdapter lineAdapter = new LineAdapter(dept.lines, dept.isExpanded); deptHolder.rvLines.setAdapter(lineAdapter); deptHolder.rvLines.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext())); // 点击切换展开/折叠 deptHolder.itemView.setOnClickListener(v -> { dept.isExpanded = !dept.isExpanded; deptHolder.ivArrow.setRotation(dept.isExpanded ? 90 : 0); lineAdapter.toggleExpand(dept.isExpanded); }); break; // Line和Device的绑定逻辑类似,LineViewHolder中嵌入Device的RecyclerView // ... } } // 核心方法:添加/移除子项到扁平列表 private <T> void toggleChildItems(int parentPosition, List<T> childList, boolean isExpanded) { if (isExpanded) { // 展开:在父Item后插入所有子项 mDataList.addAll(parentPosition + 1, childList); notifyItemRangeInserted(parentPosition + 1, childList.size()); } else { // 折叠:移除父Item后的所有子项(直到下一个同级父Item) int removeStart = parentPosition + 1; int removeCount = 0; while (removeStart + removeCount < mDataList.size()) { Object nextItem = mDataList.get(removeStart + removeCount); // 判断是否是当前父项的子类型(比如Plant的子项是DeptSection) if (nextItem instanceof DeptSection) { removeCount++; } else { break; } } mDataList.subList(removeStart, removeStart + removeCount).clear(); notifyItemRangeRemoved(removeStart, removeCount); } } // 各层级ViewHolder定义 static class PlantViewHolder extends RecyclerView.ViewHolder { TextView tvName; ImageView ivArrow; public PlantViewHolder(View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tv_plant_name); ivArrow = itemView.findViewById(R.id.iv_arrow); } } static class DeptSectionViewHolder extends RecyclerView.ViewHolder { TextView tvName; ImageView ivArrow; RecyclerView rvLines; public DeptSectionViewHolder(View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tv_dept_name); ivArrow = itemView.findViewById(R.id.iv_arrow); rvLines = itemView.findViewById(R.id.rv_lines); } } // LineViewHolder和DeviceViewHolder类似 // ... }
第三步:处理嵌套滚动冲突
在嵌套RecyclerView的布局中,给内层RecyclerView添加属性:
android:nestedScrollingEnabled="false"
这样内层RecyclerView不会拦截滚动事件,交由外层统一处理,避免滚动卡顿。
备选方案:基于ExpandableListView改造
如果必须用ExpandableListView,可以将中间层级(DeptSection、Line)都当作"Group",Device作为最终Child,需要自定义Adapter并手动维护层级状态:
- 用一个扁平列表存储所有Group(Plant、DeptSection、Line),并记录每个Group的层级深度
- 重写
getGroupCount返回所有Group的数量 - 重写
getChildCount(int groupPosition):如果当前Group是Line,返回对应的Device数量;否则返回0(因为下一级是Group而非Child) - 重写
getGroupView和getChildView,根据层级深度渲染不同UI - 自己维护每个Group的展开状态,点击时刷新对应位置
这种方式逻辑繁琐,容易出现状态混乱,仅适合必须兼容旧代码的场景。
内容的提问来源于stack exchange,提问作者Karan Satpute
相关产品推荐
相关产品推荐

