javaandroidandroid-fragmentsexpandablelistviewcheckedtextview

How to check/uncheck CheckedTextView (child items) items inside ExpandableListView?


My design : I have created a custom adapter(SignalsExpandableListAdapter)with a CheckedTextView for my ExpandableListView.

public class SignalsExpandableListAdapter extends BaseExpandableListAdapter {
private Context context;
private List<String> listDataHandler;
private HashMap<String,List<String>> listHashMap;

public SignalsExpandableListAdapter(Context context, List<String> listDataHandler, HashMap<String, List<String>> listHashMap) {
    this.context = context;
    this.listDataHandler = listDataHandler;
    this.listHashMap = listHashMap;
}

Then data is filled inside a fragment with HashMap and List as below. This works well. I can see children and parent elements inside my ExpandableListView.

ArrayList<List<String[]>> deviceMList = new ArrayList<>();
final List<String[]> mList = new ArrayList<>();
deviceMList.add(mList);

What I'm looking for I : When I select any child (can be multiple) I want to indicate that selection with a tick using CheckedTextView. And also I want to uncheck when I select any checked child item. (A simple check/uncheck design ). As well when any child is selected another function is called for plotting. This is where I got stuck. Every time when I select one child, random multiple children checkedTextViews are also indicated as checked not only the one I select.

After some searching I tried 3 attempts to overcome this and to check only the child I select. But it seems like neither of my attempts is working. I'm not sure what is going on. My attempts are mentioned below. I would really appreciate any suggestions on this.

I tried Attempt 4 (Described below in section II.) but still seems like check/unchecks is not happening in real-time.

Attempt 1 : Trying to make checked true inside Fragment

listAdapter = new SignalsExpandableListAdapter(context,signalsListDataHeader,signalsListHash);
signalsExpandableListView.setAdapter(listAdapter);
signalsExpandableListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
//tickCheckboxes();

signalsExpandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        CheckedTextView sensorCheckedView  = (CheckedTextView) v.findViewById(R.id.sensorsCheckedTextView);
        if(!mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition))) {
            sensorCheckedView.setChecked(true);
            try {
                if(Plot==null) {
                    Log.e(LOG_TAG, "Plot is null!");
                }
                mService.mPlotManager.addSignal(deviceMList.get(groupPosition).get(childPosition), Plot);
            } catch (Exception e) {
                Log.e(LOG_TAG, "Error! + e);
                e.printStackTrace();
            }
        } else {
            mService.mPlotManager.removeSignal(deviceMList.get(groupPosition).get(childPosition));
            //signalsExpandableListView.setItemChecked(childPosition,false);
            sensorCheckedView.setChecked(false);
        }
        return true;
    }
});

Attempt 2 : Creating a different function and call it inside Fragment. Created function is as below and function calling is commented above Attempt 1.

//loop through groups and then children
public void tickCheckboxes() {
    //CheckedTextView sensorCheckedView  = (CheckedTextView) signalsExpandableListView.findViewById(R.id.sensorsCheckedTextView);
    for (int i = 0; i < deviceMList.size(); i++) {
        for (int j = 0; j < deviceMList.get(i).size(); j++) {
            if (mService.mPlotManager.ifPropertyExist(deviceMList.get(i).get(j))) {
                signalsExpandableListView.setItemChecked(j, true);
                //sensorCheckedView.setChecked(true);
            } else {
                signalsExpandableListView.setItemChecked(j, false);
                //sensorCheckedView.setChecked(false);
            }
        }
    }
}

Attempt 3 : Accessing child element with ExpandableListView Adapter. I updated the getChildView() method as below.

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) {
    final String childText = (String) getChild(groupPosition,childPosition);
    if(view == null){
        LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.sensors_list_items,null);
    }
    CheckedTextView txtListChild = (CheckedTextView) view.findViewById((R.id.sensorsCheckedTextView));
    txtListChild.setText(childText);
    txtListChild.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (txtListChild.isChecked()) {
                txtListChild.setChecked(false);
            } else {
                txtListChild.setChecked(true);
            }
        }
    });
    return view;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true;
}

List Items xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <CheckedTextView
        android:id="@+id/sensorsCheckedTextView"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/listPreferredItemHeightSmall"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"
        android:gravity="center_vertical"
        android:checkMark="?android:attr/listChoiceIndicatorMultiple"
        android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
        android:textColor="@color/black"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"/>


</LinearLayout>

Group xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/deviceNameTextView"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/listPreferredItemHeightSmall"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"
        android:gravity="center_vertical"
        android:text="Device Name"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="@color/colorAccent"
        android:textColor="@color/colorPrimary"
        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"/>
</LinearLayout>

What I'm looking for II : For check/uncheck I modified Adapter as in Attempt 4 with the help of the answer mentioned below. I cannot see the checked mark in real-time. When I click a child then I want to scroll up/down to visually see the checkmark. Same as with the uncheck. It is strange. It seems like a kind of "refreshing" is required to see the check/uncheck state in real-time. I would appreciate any suggestions on how to get rid of this.

Thank you.

Attempt 4: Modified adapter. This works for a check/uncheck but can not visually see it in real-time.

public class SignalsExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<String> listDataHandler;
    private HashMap<String,List<String>> listHashMap;
    private MService mService;
    private ArrayList<List<String[]>> deviceMList ;

    public SignalsExpandableListAdapter(Context context, List<String> listDataHandler, HashMap<String, List<String>> listHashMap,mService service, ArrayList<List<String[]>> dMList ) {
        this.context = context;
        this.listDataHandler = listDataHandler;
        this.listHashMap = listHashMap;
        this.mService = service;
        this.deviceMList =dMList;
    }

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) {
    final String childText = (String) getChild(groupPosition,childPosition);
    if(view == null){
        LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.sensors_list_items,null);
    }
    CheckedTextView txtListChild = (CheckedTextView) view.findViewById((R.id.sensorsCheckedTextView));
    txtListChild.setText(childText);

if(mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition))) {
        txtListChild.setChecked(true);
    } else {
        txtListChild.setChecked(false);
    }
    return view;
}

Solution

  • I assume that your mService with a class named PlottingService, then try following changes to the adapter:

    public class SignalsExpandableListAdapter extends BaseExpandableListAdapter {
        private Context context;
        private List<String> listDataHandler;
        private HashMap<String,List<String>> listHashMap;
        private PlottingService mService;
    
        public SignalsExpandableListAdapter(Context context, List<String> listDataHandler, HashMap<String, List<String>> listHashMap, PlottingService ps) {
            this.context = context;
            this.listDataHandler = listDataHandler;
            this.listHashMap = listHashMap;
            this.mService = ps;
        }
    
        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) {
            final String childText = (String) getChild(groupPosition,childPosition);
            if(view == null){
                LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.sensors_list_items,null);
            }
            CheckedTextView txtListChild = (CheckedTextView) view.findViewById((R.id.sensorsCheckedTextView));
            txtListChild.setText(childText);
    
            // Always set check state according to data.
            if(mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition))) {
                txtListChild.setChecked(true);
            } else {
                txtListChild.setChecked(false);
            }
    
            return view;
        }
    

    Inside Fragment, do Attempt 1, only change: listAdapter = new SignalsExpandableListAdapter(context,signalsListDataHeader,signalsListHash); to listAdapter = new SignalsExpandableListAdapter(context,signalsListDataHeader,signalsListHash, mService);

    Hope that helps!

    Updated:- In Fragment, comment out the OnChildClickListener and in the adapter, use the following getChildView:

    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View view, ViewGroup parent) {
        String childText = (String) getChild(groupPosition,childPosition);
        if(view == null){
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.sensors_list_items,null);
        }
        CheckedTextView txtListChild = (CheckedTextView) view.findViewById((R.id.sensorsCheckedTextView));
        txtListChild.setText(childText);
    
        // Always set check state according to data.
        if(mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition))) {
            txtListChild.setChecked(true);
        } else {
            txtListChild.setChecked(false);
        }
    
        txtListChild.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CheckedTextView sensorCheckedView = (CheckedTextView)v;
                if(!sensorCheckedView.isChecked()) {
                    sensorCheckedView.setChecked(true);
                    try {
                        if(Plot==null) {
                            Log.e(LOG_TAG, "Plot is null!");
                        }
                        mService.mPlotManager.addSignal(deviceMList.get(groupPosition).get(childPosition), Plot);
                    } catch (Exception e) {
                        Log.e(LOG_TAG, "Error!" + e);
                        e.printStackTrace();
                    }
                } else {
                    mService.mPlotManager.removeSignal(deviceMList.get(groupPosition).get(childPosition));
                    sensorCheckedView.setChecked(false);
                }
            }
        });
        return view;
    }
    

    Updated 2:- In Fragment, comment out the OnChildClickListener and in the adapter, add a inner class and use the following getChildView:

    class Position {
        int group, child;
        Position(int group, int child) {
            this.group = group;
            this.child = child;
        }
    }
    
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) {
        String childText = (String) getChild(groupPosition,childPosition);
        if(view == null){
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.sensors_list_items,null);
        }
        CheckedTextView txtListChild = (CheckedTextView) view.findViewById((R.id.sensorsCheckedTextView));
        txtListChild.setText(childText);
    
        // Always set check state according to data.
        if(mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition))) {
            txtListChild.setChecked(true);
        } else {
            txtListChild.setChecked(false);
        }
    
        txtListChild.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CheckedTextView sensorCheckedView = (CheckedTextView)v;
                int groupPosition = ((Position)v.getTag()).group;
                int childPosition = ((Position)v.getTag()).child;
                if(!sensorCheckedView.isChecked()) {
                    sensorCheckedView.setChecked(true);
                    try {
                        if(Plot==null) {
                            Log.e(LOG_TAG, "Plot is null!");
                        }
                        mService.mPlotManager.addSignal(deviceMList.get(groupPosition).get(childPosition), Plot);
                    } catch (Exception e) {
                        Log.e(LOG_TAG, "Error!" + e);
                        e.printStackTrace();
                    }
                } else {
                    mService.mPlotManager.removeSignal(deviceMList.get(groupPosition).get(childPosition));
                    sensorCheckedView.setChecked(false);
                }
            }
        });
        txtListChild.setTag(new Position(groupPosition, childPosition));
        return view;
    }
    

    If Updated 2 still have the issue of multiple checked items, then the possible cause is mService.mPlotManager.ifPropertyExist(deviceMList.get(groupPosition).get(childPosition)). Instead of using mService to verify if an item is selected or not, it may be a better idea to change listHashMap from HashMap<String, List<String>> to HashMap<String, List<ChildItem>> [The accepted answer in Values of counter changes after scrolling ExpendableListView, you need a boolean field instead of the integer field, quantity]. Then when a child item is clicked, check and update with the list.