Building a Hierarchical Tree View with Multi-Selection Capability in Android

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());
});

Tags: Android RecyclerView hierarchical-list multi-selection custom-adapter

Posted on Mon, 07 Sep 2026 16:05:43 +0000 by mcl