| 方案 | 技術棧 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 原生 ListView + Adapter | Android SDK | 輕量、無依賴 | 需手寫雙層適配器、項目類型判斷、性能一般 | 簡單項目、舊版本維護 |
| RecyclerView + 多類型 Item | AndroidX | 性能優、靈活、支持動畫、標準化 | 需實現多 ViewHolder、SpanSizeLookup | 現代 App 首選 |
| ExpandableListView | Android SDK | 自帶分組展開/收起動畫 | 定制困難、不支持橫向二級布局、已棄用趨勢 | 簡單樹形結構 |
| 雙 RecyclerView (主從聯動) | AndroidX | 邏輯清晰、獨立滾動、易於複雜交互 | 佔用更多內存、需同步選中狀態 | 大量科室、複雜篩選 |
| FlowLayout / FlexboxLayout | 第三方庫 | 完美支持標簽流式/網格排列 | 引入依賴、需配合 RecyclerView | 二級科室標簽雲展示 |
// 一級科室
public class Department {
public int id;
public String name;
public List<SubDepartment> subList;
public boolean expanded; // 展開狀態
}
// 二級科室
public class SubDepartment {
public int id;
public String name;
public int parentId;
}
<!-- item_department_parent.xml (一級) -->
<LinearLayout xmlns:android="..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView android:id="@+id/tvName" ... />
<ImageView android:id="@+id/ivArrow" ... />
</LinearLayout>
<!-- item_sub_department.xml (二級) -->
<TextView xmlns:android="..."
android:id="@+id/tvSubName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp 16dp"
android:background="@drawable/bg_sub_dept"
android:layout_margin="4dp"
android:textSize="14sp" />
public class DeptAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_PARENT = 0;
private static final int TYPE_CHILD = 1;
private List<Department> data;
private OnSubDeptClickListener listener;
@Override
public int getItemViewType(int position) {
// 扁平化數據:父節點 + 其展開的子節點
return getItem(position) instanceof Department ? TYPE_PARENT : TYPE_CHILD;
}
// 需將樹形結構扁平化為 List<Object> flatList
// 父節點插入後,若 expanded 則緊接插入其子節點列表
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_PARENT) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_department_parent, parent, false);
return new ParentVH(v);
} else {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sub_department, parent, false);
return new ChildVH(v);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Object item = flatList.get(position);
if (holder instanceof ParentVH) {
((ParentVH)holder).bind((Department)item);
} else {
((ChildVH)holder).bind((SubDepartment)item);
}
}
// 父 ViewHolder
class ParentVH extends RecyclerView.ViewHolder {
TextView tvName; ImageView ivArrow;
ParentVH(View v) { super(v); tvName=v.findViewById(R.id.tvName); ivArrow=v.findViewById(R.id.ivArrow); }
void bind(Department dept) {
tvName.setText(dept.name);
ivArrow.setRotation(dept.expanded ? 180 : 0);
itemView.setOnClickListener(v -> toggleExpand(dept));
}
}
// 子 ViewHolder
class ChildVH extends RecyclerView.ViewHolder {
TextView tvName;
ChildVH(View v) { super(v); tvName=v.findViewById(R.id.tvSubName); }
void bind(SubDepartment sub) {
tvName.setText(sub.name);
itemView.setOnClickListener(v -> listener.onSubDeptClick(sub));
}
}
void toggleExpand(Department dept) {
dept.expanded = !dept.expanded;
rebuildFlatList(); // 重建扁平列表
notifyDataSetChanged(); // 或用 DiffUtil 精細更新
}
}
RecyclerView rv = findViewById(R.id.rvDepartments);
GridLayoutManager glm = new GridLayoutManager(this, 4); // 4 列
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return adapter.getItemViewType(position) == TYPE_PARENT ? 4 : 1; // 父佔滿行,子佔 1 格
}
});
rv.setLayoutManager(glm);
rv.setAdapter(adapter);
<LinearLayout android:orientation="horizontal" ...>
<RecyclerView android:id="@+id/rvParent" android:layout_width="120dp" android:layout_height="match_parent" />
<RecyclerView android:id="@+id/rvChild" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" />
</LinearLayout>
// 邏輯
rvParent.setAdapter(parentAdapter);
parentAdapter.setOnItemClickListener(dept -> {
childAdapter.setData(dept.subList); // 更新右側數據
childAdapter.notifyDataSetChanged();
});
Android Department List、Medical App UI、Hospital RegistrationFlowLayout 庫,自動換行排版。核心在於樹形數據扁平化與多類型 ViewHolder 綁定,掌握此模式可應對所有分組列表場景。