소스코드만으로 판단할 때 Expandable RecyclerView를 구현하고 싶으신듯 합니다. 먼저 간단히 데이터 클래스를 ParentItem, ChildIrem으로 하겠습니다. Adapter에서 사용하기 쉽게 둘다 같은 인터페이스를 구현하거나 부모 클래스를 상속받도록 합니다.
public interface ListItem {
int getViewType();
}
public class ParentItem implements ListItem {
public static final int VIEW_TYPE = R.layout.item_parent;
private int type;
private String text;
// 라사이클러뷰에서 자식 아이템들이 접혔는지 펴졌는지 상태를 보관하는 변수
private boolean expanded;
private List<ChildItem> children = new ArrayList<>();
public ParentItem(int type, String text, boolean expanded) {
this.type = type;
this.text = text;
this.expanded = expanded;
}
public ParentItem(ParentItem another) {
this.type = another.type;
this.text = another.text;
this.expanded = another.expanded;
this.children = another.children;
}
public int getType() {
return type;
}
public String getText() {
return text;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public List<ChildItem> getChildren() {
return children;
}
@DrawableRes
public int getDrawableRes() {
return expanded ? R.drawable.ic_expand_less : R.drawable.ic_expand_more;
}
@Override
public int getViewType() {
return VIEW_TYPE;
}
}
다름으로 Adapter 입니다.
public class ExpandableAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private List<ListItem> items = new ArrayList<>();
private ParentViewHolder.Listener listener;
public ExpandableAdapter(ParentViewHolder.Listener listener) {
this.listener = listener;
}
public void submitList(List<ListItem> items) {
this.items = items;
// 무조건 notifyDataSetChange를 호출하는 것은 효율적이지 않으므로 권장되지 않음.
// 변경된 아이템만 갱신하기 위하여 notify*Change를 대신 사용하거나 , AsyncDiffUtil또는
// ListAdapter를 사용하는 것이 좋음.
notifyDataSetChanged();
}
public ListItem getItem(int position) {
return this.items.get(position);
}
@Override
public int getItemViewType(int position) {
return getItem(position).getViewType();
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
switch (viewType) {
case ParentItem.VIEW_TYPE:
return new ParentViewHolder(itemView, listener);
case ChildItem.VIEW_TYPE:
return new ChildViewHolder(itemView);
default:
throw new IllegalArgumentException("Cannot find view for " + viewType);
}
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
ListItem item = getItem(position);
holder.bind(item);
}
@Override
public int getItemCount() {
return items.size();
}
}
다음은 ViewHolder 들입니다. ViewHolder도. 공통클래스를 이용합니다.
public abstract class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
}
abstract void bind(ListItem item);
}
public class ParentViewHolder extends BaseViewHolder {
interface Listener {
void onAddChildClicked(int parentPosition);
void onToggleClicked(ParentItem item);
}
private final ItemParentBinding binding;
private final Listener listener;
public ParentViewHolder(@NonNull View itemView, Listener listener) {
super(itemView);
binding = ItemParentBinding.bind(itemView);
this.listener = listener;
binding.addBtn.setOnClickListener(v -> {
listener.onAddChildClicked(getAdapterPosition());
});
}
@Override
public void bind(ListItem item) {
ParentItem parentItem = (ParentItem) item;
binding.toggleImg.setOnClickListener(v -> {
listener.onToggleClicked(parentItem);
});
binding.toggleImg.setImageResource(parentItem.getDrawableRes());
binding.titleTxt.setOnClickListener(v -> {
listener.onToggleClicked(parentItem);
});
binding.titleTxt.setText(parentItem.getText());
}
}
public class ChildViewHolder extends BaseViewHolder {
private final ItemChildBinding binding;
public ChildViewHolder(@NonNull View itemView) {
super(itemView);
binding = ItemChildBinding.bind(itemView);
}
@Override
public void bind(ListItem item) {
ChildItem childItem = (ChildItem) item;
binding.titleTxt.setText(childItem.getText());
}
}
이렇게 하면, 남은 것은 리사이클뷰의 아이템을 토글할 때 , 해당 ParentItem의 expanded의 값을 변경할 다음 ExpandableAdapter에 변경된 아이템을 submitList해주면 됩니다.
public ExpandableActivity extends AppCompatActivity() implements ParentViewHolder.Listener {
...
@Override
public void onAddChildClicked(int parentPosition) {
//해당 포지션에 해당하는 ParentItem을 찾아 자식 아이템 추가.
}
@Override
public void onToggleClicked(ParentItem item) {
// ParemtItem 의 expanded 를 업데이트하고 adapter 를 갱신.
}
}
onAddChildClicked과 onToggleClicked는 모두 파라미터로 AdapterPosition 을 사용해도 되고 ParentItem을 사용해도 됩니다.
제 샘플에서는 둘다 구현가능하다는 걸 보여주기 위해 섞어서 사용했고, 실제 적용하실 때는 position 이나 ParentItem 하나만 사용하시기 바랍니다. 이제 나머지는 ExpandableActivity에서 ParentViewHolder.Listener를 구현해 주시기만 하면 됩니다.
그리고, expanded 를 갱신할 때 adapter 내부에서 SparseArray등을 통하여 바로 처리할 수도 있으나, 어댑터의 데이터를 가지고 있는 클래스가 존재할 경우(이게 좋은 방식입니다만), 데이터 불일치가 생길 수 잇으므로, 어댑터에 사용되는 데이터는 한곳에이 있는 데이터만을 사용할 수 있는 것이 바람직하므로 개인적으로는 꺼려하는 방식입니다. 실제 아이템의 토글상태를 처리하는 부분은 단순한 자바 코드 문제이므로 쉽게 구현하실 수 있으리라 생각합니다.