javaandroidonclicklistenerexpandablerecyclerview

How to set OnClickListener to Expandable RecycleView


I want to set OnClickListeners to the items from my Expandable Recycleview. Each item from the Recycleview should have a button ( like this https://i.sstatic.net/cuQWk.jpg : a + button to add tasks and an "x" button for each task to delete it)

I have tried to implement it from some other examples of onClickListeners but nothing worked so far

this is the ADAPTER:

public class ExpandableAdapter extends ExpandableRecyclerViewAdapter<RoutineViewHolder, TaskViewHolder> {
    public ExpandableAdapter(List<? extends ExpandableGroup> groups) {
        super(groups);
    }

    @Override
    public RoutineViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_routine, parent, false);
        return new RoutineViewHolder(v);
    }

    @Override
    public TaskViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_task, parent, false);
        return new TaskViewHolder(v);
    }

    @Override
    public void onBindChildViewHolder(TaskViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
        final Tasks tasks = (Tasks) group.getItems().get(childIndex);
        holder.bind(tasks);

    }

    @Override
    public void onBindGroupViewHolder(RoutineViewHolder holder, int flatPosition, ExpandableGroup group) {
        final Routine routine = (Routine) group;
        holder.bind(routine);

    }

RoutineViewHolder:

import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;

public class RoutineViewHolder extends GroupViewHolder implements View.OnClickListener {
    private TextView mTextView;



    public RoutineViewHolder(View itemView) {
        super(itemView);

        mTextView = itemView.findViewById(R.id.exp_routine);
        itemView.setOnClickListener(this);
    }


    public void bind(Routine routine){
        mTextView.setText(routine.getTitle());
    }


}

TaskViewHolder:

public class TaskViewHolder extends ChildViewHolder {
    private TextView mTextView;
    private CheckBox mCheckBox;
    private Boolean checkVal;

    public TaskViewHolder(View itemView) {
        super(itemView);
        mTextView = itemView.findViewById(R.id.exp_task);
        mCheckBox=itemView.findViewById(R.id.exp_task_checkbox);
    }

    public void bind(Tasks tasks) {
        mTextView.setText(tasks.name);
        checkVal=((tasks.checkBox==1)?Boolean.TRUE:Boolean.FALSE);
        mCheckBox.setChecked(checkVal);
    }
}

as you can see I have 2 ViewHolders : RoutineViewHolder and TaskViewHolder. I am very confused to where and how I should set the OnClickListener since I want it to behave different for the "Routines" and "Tasks" because they would have different buttons.

"Tasks" should have the + button to add tasks underneath it and each task should have an X button to delete that specific task

the expandable recycleview is made out of 2 more of these "Tasks" cathegories.


Solution

  • I would prefer a solution where I can listen all these callbacks from some higher level (like Activity) where I can change data objects and refresh the RecyclerView keep things in sync based on callbacks. (This is eventually what you will need if you scale this.)

    I implemented your code and modified a little to get the expected result. Sample result

    For this solution:


    Code

    1. Add new file ListActionListener.java

    This is the interface.

    public interface ListActionListener {
           // Know add was clicked on given routine
           void onAddTaskClicked(Routine routine);
           // Know delete was clicked on given task.
           void onDeleteTaskClicked(Routine routine, Tasks task, int index);
           // Know checkbox clicked on given task (with new checked status)
           void onTaskCheckChanged(Routine routine, Tasks task, int index, boolean checked);
    }
    
    

    2. Make your activity implement this interface. ExpandableListActivity.java

    This is my sample activity that you see in the screenshots.

    public class ExpandableListActivity extends AppCompatActivity implements ListActionListener{
        ExpandableAdapter adapter;
        RecyclerView recyclerView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_expandable_list);
            recyclerView = findViewById(R.id.recyclerView);
            loadList();
        }
    
        private void loadList() {
            List<Routine> routines = getDummyRoutineList();
            adapter = new ExpandableAdapter(routines, this);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            recyclerView.setAdapter(adapter);
        }
    
        private List<Routine> getDummyRoutineList() {
            List<Routine> list = new ArrayList<Routine>();
            Tasks rt1 = new Tasks("R1 Tasks1", 1);
            Tasks rt2 = new Tasks("R1 Tasks2", 0);
            Tasks rt3 = new Tasks("R1 Tasks3", 1);
            Tasks rt4 = new Tasks("R1 Tasks4", 0);
            Tasks rt5 = new Tasks("R1 Tasks5", 0);
            List<Tasks> r1Tasks = new ArrayList<>();
            r1Tasks.add(rt1);
            r1Tasks.add(rt2);
            r1Tasks.add(rt3);
            r1Tasks.add(rt4);
            r1Tasks.add(rt5);
            Routine r1 = new Routine("Routine 1", r1Tasks);
    
    
            Tasks r2t1 = new Tasks("R2 Tasks1", 1);
            Tasks r2t2 = new Tasks("R2 Tasks2", 0);
            Tasks r2t3 = new Tasks("R2 Tasks3", 1);
            Tasks r2t4 = new Tasks("R2 Tasks4", 0);
            Tasks r2t5 = new Tasks("R2 Tasks5", 1);
            List<Tasks> r2Tasks = new ArrayList<>();
            r2Tasks.add(r2t1);
            r2Tasks.add(r2t2);
            r2Tasks.add(r2t3);
            r2Tasks.add(r2t4);
            r2Tasks.add(r2t5);
            Routine r2 = new Routine("Routine 2", r2Tasks);
    
            list.add(r1);
            list.add(r2);
            return list;
        }
    
        @Override
        public void onAddTaskClicked(Routine routine) {
            Toast.makeText(this, "On Add Clicked", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onDeleteTaskClicked(Routine routine, Tasks task, int index) {
            Toast.makeText(this, "On Delete Clicked", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onTaskCheckChanged(Routine routine, Tasks task, int index, boolean checked) {
            Toast.makeText(this, "On Check changed:"+checked, Toast.LENGTH_SHORT).show();
        }
    }
    

    3. Add "X" button to Task Row layout

    This is my sample XML file, your XML may look different. Main thing is to add button for Delete.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="8dp"
        >
    
        <Button
            android:id="@+id/btn_delete"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="X"
            />
    
        <CheckBox
            android:id="@+id/exp_task_checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            />
    
        <TextView
            android:id="@+id/exp_task"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/btn_delete"
            android:layout_toRightOf="@+id/exp_task_checkbox"
            />
    
    </RelativeLayout>
    

    4. Add "+" button to Routine Layout file

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="8dp"
        >
    
        <Button
            android:id="@+id/btn_add"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="+"
            />
    
        <TextView
            android:id="@+id/exp_routine"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/btn_delete"
            android:layout_toRightOf="@+id/exp_task_checkbox"
            />
    
    </RelativeLayout>
    

    5. Update Adapter to accept a ListActionListener

    public class ExpandableAdapter extends ExpandableRecyclerViewAdapter<RoutineViewHolder, TaskViewHolder> {
        ListActionListener listActionListener;
        public ExpandableAdapter(List<? extends ExpandableGroup> groups, ListActionListener listActionListener) {
            super(groups);
            this.listActionListener = listActionListener;
        }
    
        @Override
        public RoutineViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_routine, parent, false);
            return new RoutineViewHolder(v);
        }
    
        @Override
        public TaskViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_task, parent, false);
            return new TaskViewHolder(v);
        }
    
        @Override
        public void onBindChildViewHolder(TaskViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
            final Tasks tasks = (Tasks) group.getItems().get(childIndex);
            holder.bind((Routine)group, childIndex, tasks, listActionListener);
        }
    
        @Override
        public void onBindGroupViewHolder(RoutineViewHolder holder, int flatPosition, ExpandableGroup group) {
            final Routine routine = (Routine) group;
            holder.bind(routine, listActionListener);
    
        }
    }
    

    6. Update TaskViewHolder.java

    To accepte listener and invoke callback

    public class TaskViewHolder extends ChildViewHolder {
        private TextView mTextView;
        private CheckBox mCheckBox;
        private Boolean checkVal;
        private Button btnDelete;
    
        public TaskViewHolder(View itemView) {
            super(itemView);
            mTextView = itemView.findViewById(R.id.exp_task);
            mCheckBox=itemView.findViewById(R.id.exp_task_checkbox);
            btnDelete = itemView.findViewById(R.id.btn_delete);
    
        }
    
        public void bind(final Routine parentRoutine, final int childIndex, final Tasks tasks, final ListActionListener listActionListener) {
            mTextView.setText(tasks.name);
            checkVal=((tasks.checkBox==1)?Boolean.TRUE:Boolean.FALSE);
            mCheckBox.setChecked(checkVal);
            //add delete button click
            btnDelete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listActionListener.onDeleteTaskClicked(parentRoutine, tasks, childIndex);
                }
            });
    
            mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
                    //to avoid initial call back
                    if(checked != checkVal) {
                        listActionListener.onTaskCheckChanged(parentRoutine, tasks, childIndex, checked);
                        checkVal = checked;
                    }
                }
            });
        }
    }
    

    7. Update RoutineViewHolder.java

    To accepte listener and invoke callback.

    public class RoutineViewHolder extends GroupViewHolder implements View.OnClickListener {
        private TextView mTextView;
        private Button btnAdd;
    
        public RoutineViewHolder(View itemView) {
            super(itemView);
            mTextView = itemView.findViewById(R.id.exp_routine);
            btnAdd = itemView.findViewById(R.id.btn_add);
            itemView.setOnClickListener(this);
        }
    
    
        public void bind(final Routine routine, final ListActionListener listActionListener) {
            mTextView.setText(routine.getTitle());
            btnAdd.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listActionListener.onAddTaskClicked(routine);
                }
            });
        }
    }
    

    Bingo.... Run the code... :)