Overview
Creating nested navigation structures is a frequent requirmeent in mobile application design. This guide outlines the process of implementing a recursive list using RecyclerView that accommodates multiple item selection states simultaneously.
Data Model Construction
A stable data structure is crucial for managing hierarchy. We define an entity that tracks unique idantifiers, parent references, and the current selection flag.
public class TreeNodeEntity {
private final int uniqueIdentifier;
private final int parentIdentifier;
private final String labelText;
private final int depthLevel;
private boolean isCheckedStatus;
public TreeNodeEntity(int id, int pid, String label, int level, boolean checked) {
this.uniqueIdentifier = id;
this.parentIdentifier = pid;
this.labelText = label;
this.depthLevel = level;
this.isCheckedStatus = checked;
}
public boolean isItemChecked() { return isCheckedStatus; }
public void setCheckedState(boolean state) { this.isCheckedStatus = state; }
public int getDepthLevel() { return depthLevel; }
public String getLabelText() { return labelText; }
public int getUniqueIdentifier() { return uniqueIdentifier; }
}
Implementing the Adapter
The adapter handles the mapping between data and views. Visual indentation is applied dynamically based on the node's depth to signify nesting leveels.
public class NodeListAdapter extends RecyclerView.Adapter<NodeListAdapter.ViewHolderNode> {
private final List<TreeNodeEntity> itemsFlatList;
private SelectionHandlerCallback callbackHandler;
public NodeListAdapter(List<TreeNodeEntity> data, SelectionHandlerCallback handler) {
this.itemsFlatList = data;
this.callbackHandler = handler;
}
@NonNull
@Override
public ViewHolderNode onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_tree_item, parent, false);
return new ViewHolderNode(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolderNode holder, int position) {
TreeNodeEntity currentItem = itemsFlatList.get(position);
// Calculate horizontal padding for indentation
int density = (int) holder.itemView.getResources().getDisplayMetrics().density;
int indentPx = currentItem.getDepthLevel() * 20 * density;
holder.textLabel.setPadding(indentPx, 16, 16, 16);
holder.textLabel.setText(currentItem.getLabelText());
holder.checkInput.setChecked(currentItem.isItemChecked());
holder.itemView.setOnClickListener(v -> handleItemClick(position));
}
private void handleItemClick(int position) {
TreeNodeEntity item = itemsFlatList.get(position);
item.setCheckedState(!item.isItemChecked());
notifyItemChanged(position);
if (callbackHandler != null) {
callbackHandler.onUpdateStatus(item);
}
}
@Override
public int getItemCount() {
return itemsFlatList.size();
}
static class ViewHolderNode extends RecyclerView.ViewHolder {
TextView textLabel;
CheckBox checkInput;
public ViewHolderNode(View itemView) {
super(itemView);
textLabel = itemView.findViewById(R.id.node_title);
checkInput = itemView.findViewById(R.id.checkbox_select);
}
}
public void setSelectionCallback(SelectionHandlerCallback callback) {
this.callbackHandler = callback;
}
}
Managing Callbacks
To decouple UI logic from business logic, an interface defines the contract for state updates. This enables the hosting Activity to aggregate selected items efficiently.
public interface SelectionHandlerCallback {
void onUpdateStatus(TreeNodeEntity modifiedItem);
}
// Integration Example
nodeListAdapter.setSelectionCallback(item -> {
// Logic to process the selected state change
viewModel.saveSelection(item.getUniqueIdentifier(), item.isItemChecked());
});