Android CheckBox multi-selection and inverse selection to clear selected options

Foreword

The epidemic has quietly left with the arrival of this spring. Do you still remember the time when you filled out the questionnaire?

Not much to say, what this article wants to implement is a questionnaire list, that is, Listview nested Listview to realize checkbox multiple selection and reverse selection to clear selected options

Text

The idea is to define a Map collection to store the selected state of each checkbox item. The default checkbox status is false, traverse the collection, select whichever item is selected, add the position of the item to the collection, and set the status to true, set the status to false if the checkbox is not selected, set the checkbox status to true if the collection is not empty, and vice versa

1. Import library

implementation 'com.google.code.gson:gson:2.2.2'

2. Prepare data

Create a new DataJson class and define the interface data as an immutable string JSON

public class DataJson {
    public static final String JSON = "{\\
" +
            " "data": {\\
" +
            " "problems": [\\
" +
            " {\\
" +
            " "bodydetails": [\\
" +
            " {\\
" +
            " "ConditionName": "Fever",\\
" +
            " "ConditionValue": "10"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "Dry Cough",\\
" +
            " "ConditionValue": "11"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "Diarrhea",\\
" +
            " "ConditionValue": "12"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "There is no abnormality in the body",\\
" +
            " "ConditionValue": "100"\\
" +
            " }\\
" +
            " ],\\
" +
            " "problem": "Does the body have the following conditions:"\\
" +
            " },\\
" +
            " {\\
" +
            " "bodydetails": [\\
" +
            " {\\
" +
            " "ConditionName": "Scratch",\\
" +
            " "ConditionValue": "10"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "Blister",\\
" +
            " "ConditionValue": "11"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "Herpes",\\
" +
            " "ConditionValue": "12"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "There is no abnormality in the palm",\\
" +
            " "ConditionValue": "100"\\
" +
            " }\\
" +
            " ],\\
" +
            " "problem": "Whether the following conditions appear on the palm:"\\
" +
            " },\\
" +
            " {\\
" +
            " "bodydetails": [\\
" +
            " {\\
" +
            " "ConditionName": "Mouth ulcer",\\
" +
            " "ConditionValue": "10"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "bleeding gums",\\
" +
            " "ConditionValue": "11"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "Inflammation of flat conductor",\\
" +
            " "ConditionValue": "12"\\
" +
            " },\\
" +
            " {\\
" +
            " "ConditionName": "There is no abnormality in the oral cavity",\\
" +
            " "ConditionValue": "100"\\
" +
            " }\\
" +
            " ],\\
" +
            " "problem": "Whether the following conditions occur in the oral cavity:"\\
" +
            " }\\
" +
            " ]\\
" +
            " },\\
" +
            " "msg": "Acquired successfully",\\
" +
            " "status": "Y"\\
" +
            "}";
}

When I got the data returned by the server, I was also confused. Why are the status codes of each item the same? How to distinguish between storage, so it is more important to have a good relationship with the background, otherwise you will do a lot of work, here you can define the entity class and add the flag logo yourself, create a ResponseBean entity class and add a flag field

public class ResponseBean {

    /**
     * status : Y
     * msg : get success
     * data : {"problems":[{"bodydetails":[{"ConditionName":"Fever","ConditionValue":"10"},{"ConditionName" ":"Dry cough","ConditionValue":"11"},{"ConditionName":"Diarrhea","ConditionValue":"12"},{" ConditionName":"There is no abnormality in the body","ConditionValue":"100"}],"problem":"Does the body have the following conditions:"},{"bodydetails" :[{"ConditionName":"Scratch","ConditionValue":"10"},{"ConditionName":"Blisters","ConditionValue":" 11"},{"ConditionName":"Herpes","ConditionValue":"12"},{"ConditionName":"There is no abnormality in the palm","ConditionValue\ ":"100"}],"problem":"Does the palm have the following conditions:"},{"bodydetails":[{"ConditionName":"Mouth Ulcers", "ConditionValue":"10"},{"ConditionName":"Bleeding gums","ConditionValue":"11"},{"ConditionName":"Flat Inflammation of the conductor","ConditionValue":"12"},{"ConditionName":"No abnormality in the oral cavity","ConditionValue":"100"}],"problem ":"Is there any of the following conditions in the mouth:"}]}
     */

    private String status;
    private String msg;
    private DataBean data;

    @Override
    public String toString() {
        return "ResponseBean{" +
                "status='" + status + '\'' +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public DataBean getData() {
        return data;
    }

    public void setData(DataBean data) {
        this.data = data;
    }

    public static class DataBean {
        private List<ProblemsBean> problems;

        @Override
        public String toString() {
            return "DataBean{" +
                    "problems=" + problems +
                    '}';
        }

        public List<ProblemsBean> getProblems() {
            return problems;
        }

        public void setProblems(List<ProblemsBean> problems) {
            this.problems = problems;
        }

        public static class ProblemsBean {
            /**
             * bodydetails : [{"ConditionName":"Fever","ConditionValue":"10"},{"ConditionName":"Dry cough","ConditionValue":\ "11"},{"ConditionName":"Diarrhea","ConditionValue":"12"},{"ConditionName":"There is no abnormality in the body","ConditionValue ":"100"}]
             * problem: Does the body have the following conditions:
             */

            private String problem;
            private List<BodydetailsBean> bodydetails;

            @Override
            public String toString() {
                return "ProblemsBean{" +
                        "problem='" + problem + '\'' +
                        ", bodydetails=" + bodydetails +
                        '}';
            }

            public String getProblem() {
                return problem;
            }

            public void setProblem(String problem) {
                this.problem = problem;
            }

            public List<BodydetailsBean> getBodydetails() {
                return body details;
            }

            public void setBodydetails(List<BodydetailsBean> bodydetails) {
                this.bodydetails = bodydetails;
            }

            public static class BodydetailsBean {
                /**
                 * ConditionName : Fever
                 * ConditionValue: 10
                 */

                private String ConditionName;
                private String ConditionValue;
                private String flag;

                @Override
                public String toString() {
                    return "BodydetailsBean{" +
                            "ConditionName='" + ConditionName + '\'' +
                            ", ConditionValue='" + ConditionValue + '\'' +
                            ", flag=" + flag +
                            '}';
                }

                public String getConditionName() {
                    return ConditionName;
                }

                public void setConditionName(String ConditionName) {
                    this.ConditionName = ConditionName;
                }

                public String getConditionValue() {
                    return ConditionValue;
                }

                public void setConditionValue(String ConditionValue) {
                    this.ConditionValue = ConditionValue;
                }

                public String getFlag() {
                    return flag;
                }

                public void setFlag(String flag) {
                    this.flag = flag;
                }
            }
        }
    }
}

Because the interface data is an array nested in an array, as shown in the figure below

Here we need to solve the problem of item click failure or incomplete display in Listview nesting

Need to customize a CustomListView class to inherit the parent class ListView and rewrite the onMeasure method to redefine the height

public class CustomListView extends ListView {

    public CustomListView(Context context) {
        super(context);
    }

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int newHeightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, newHeightSpec);
    }
}

Next create the xml for the title item

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

    <TextView
        android:id="@ + id/item_title_tv"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_marginTop="30dp"
        android:gravity="center_vertical"
        android:paddingStart="10dp"
        android:paddingLeft="10dp"
        android:paddingEnd="10dp"
        android:paddingRight="10dp"
        android:text="Title"
        android:textColor="#000000" />

    <com.wyc.checkbox.CustomListView
        android:id="@ + id/item_list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:divider="@null"
        android:dividerHeight="0dp"
        android:overScrollMode="never" />

</LinearLayout>

In the xml that creates the child option item

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:padding="10dp"
    android:layout_height="100dp">

    <TextView
        android:id="@ + id/item_check_tv"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Title"
        android:textColor="#000000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/item_check_box"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <CheckBox
        android:id="@ + id/item_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBaseline_toBaselineOf="@id/item_check_tv"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/item_check_tv" />
</android.support.constraint.ConstraintLayout>

Next is the outer title adapter, create a ResponseAdapter class and inherit BaseAdapter, and then define a Map array here to store the option status code, all of which are false by default

Then judge whether the option list is empty, and load the option adapter if it is not empty

public class ResponseAdapter extends BaseAdapter {

    private List<ResponseBean.DataBean.ProblemsBean> mDataList;
    private Map<String, Boolean> mCheckMap = new HashMap<>();

    public ResponseAdapter(List<ResponseBean. DataBean. ProblemsBean> dataList) {
        this.mDataList = dataList;
        for (ResponseBean. DataBean. ProblemsBean bean : mDataList) {
            for (ResponseBean.DataBean.ProblemsBean.BodydetailsBean bodydetail : bean.getBodydetails()) {
                mCheckMap.put(bodydetail.getFlag(), false);
            }
        }
    }

    @Override
    public int getCount() {
        return mDataList. size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    public Map<String, Boolean> getCheckMap() {
        return mCheckMap;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_response, null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView. getTag();
        }
        ResponseBean.DataBean.ProblemsBean problemsBean = mDataList.get(position);
        holder.itemTitleTv.setText(problemsBean.getProblem());
        List<ResponseBean.DataBean.ProblemsBean.BodydetailsBean> bodyList = problemsBean.getBodydetails();
        if (bodyList != null & amp; & amp; bodyList. size() > 0) {
            holder.itemListView.setVisibility(View.VISIBLE);
            holder.itemListView.setAdapter(new ProblemAdapter(problemsBean.getBodydetails(), mCheckMap));
        } else {
            holder.itemListView.setVisibility(View.GONE);
        }
        return convertView;
    }

    final static class ViewHolder {
        TextView itemTitleTv;
        ListView itemListView;

        public ViewHolder(View view) {
            itemListView = view.findViewById(R.id.item_list_view);
            itemTitleTv = view.findViewById(R.id.item_title_tv);
        }
    }
}

The next step is the adapter of sub-options, create a ProblemAdapter class and inherit BaseAdapter, add a Map array, and then add a change monitoring event for the checkbox in getView, the selection will be stored as true, otherwise it will be false, and the adapter will be refreshed in time, the next step is to get the options Status code, here is to select from the big item, the status value is defined by yourself, and the next thing will be said, the default storage is false,

public class ProblemAdapter extends BaseAdapter {
    public static final String TAG = "ProblemAdapter";

    private List<ResponseBean.DataBean.ProblemsBean.BodydetailsBean> mBodyDetails;
    private Map<String, Boolean> mCheckMap;


    public ProblemAdapter(List<ResponseBean.DataBean.ProblemsBean.BodydetailsBean> bodydetails, Map<String, Boolean> checkMap) {
        this.mBodyDetails = bodydetails;
        this.mCheckMap = checkMap;
        Log.d(TAG, "====================================");
        for (String integer : mCheckMap. keySet()) {
            Log.d(TAG, "key= " + integer + " , value = " + mCheckMap.get(integer));
        }
    }

    public Map<String, Boolean> getCheckMap() {
        return mCheckMap;
    }

    @Override
    public int getCount() {
        return mBodyDetails. size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_check_view, null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView. getTag();
        }
        holder.itemCheckTv.setText(mBodyDetails.get(position).getConditionName());
        final ResponseBean.DataBean.ProblemsBean.BodydetailsBean bean = mBodyDetails.get(position);
        holder.itemCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    mCheckMap.put(bean.getFlag(), true);
                } else {
                    mCheckMap.put(bean.getFlag(), false);
                }
                notifyDataSetChanged();
            }
        });

        if (mCheckMap. get("1100")) {
            mCheckMap.put("110", false);
            mCheckMap.put("111", false);
            mCheckMap.put("112", false);
            notifyDataSetChanged();
        }

        if (mCheckMap. get("2100")) {
            mCheckMap.put("210", false);
            mCheckMap.put("211", false);
            mCheckMap.put("212", false);
            notifyDataSetChanged();
        }

        if (mCheckMap. get("3100")) {
            mCheckMap.put("310", false);
            mCheckMap.put("311", false);
            mCheckMap.put("312", false);
            notifyDataSetChanged();
        }

        if (mCheckMap != null & amp; & amp; mCheckMap. get(bean. getFlag())) {
            holder.itemCheckBox.setChecked(true);
        } else {
            holder.itemCheckBox.setChecked(false);
        }

        return convertView;
    }

    final static class ViewHolder {
        TextView itemCheckTv;
        CheckBox itemCheckBox;

        public ViewHolder(View view) {
            itemCheckTv = view.findViewById(R.id.item_check_tv);
            itemCheckBox = view.findViewById(R.id.item_check_box);
        }
    }
}

Finally, MainActivity integrates resources. Let’s look at the layout first. The layout is a title bar plus a Listview title bar. Click on the selected items to facilitate your own testing.

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="select effect"
            android:textColor="#000000" />

        <TextView
            android:id="@ + id/checkBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="10dp"
            android:text="Selected item"
            android:textColor="#ff0000" />
    </RelativeLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#f2f2f2" />

    <ListView
        android:id="@ + id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@null"
        android:dividerHeight="0dp"
        android:overScrollMode="never" />

</LinearLayout>

The last is the main integration code, which is mainly to prepare the local data during initialization. Here, the status flag is customized, and the loop list is incremented by i to determine whether the check box is selected, and finally added to mDataBeanList.

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "Log_MainActivity";
    private List<ResponseBean.DataBean.ProblemsBean> mDataBeanList = new ArrayList<>();
    private ListView mListView;
    private ResponseAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R. layout. activity_main);
        initData();
        mListView = findViewById(R.id.listView);
        mAdapter = new ResponseAdapter(mDataBeanList);
        mListView.setAdapter(mAdapter);
        TextView checkBtn = findViewById(R.id.checkBtn);
        checkBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Map<String, List<String>> map = checkItem();
                Log.d(TAG, "check map = " + map.toString());
            }
        });


    }

    /**
     * Get selected items
     *
     * @return
     */
    private Map<String, List<String>> checkItem() {
        Map<String, List<String>> map = new HashMap<>();
        if (mAdapter != null) {
            Map<String, Boolean> checkMap = mAdapter. getCheckMap();
            for (ResponseBean. DataBean. ProblemsBean problemsBean : mDataBeanList) {
                List<String> problemList = new ArrayList<>();
                for (ResponseBean.DataBean.ProblemsBean.BodydetailsBean bodydetail : problemsBean.getBodydetails()) {
                    if (checkMap. get(bodydetail. getFlag())) {
                        problemList.add(bodydetail.getConditionValue() + "-------------" + bodydetail.getConditionName());
                    }
                }
                map.put(problemsBean.getProblem(), problemList);
            }
        }
        return map;
    }

    private void initData() {
        ResponseBean responseBean = new Gson().fromJson(DataJson.JSON, ResponseBean.class);
        Log.d(TAG, "responseBean = " + responseBean.toString());
        List<ResponseBean.DataBean.ProblemsBean> dataList = responseBean.getData().getProblems();

        //Quasi local data, custom subscript, used to determine whether the check box is selected
        for (int i = 0; i < dataList. size(); i ++ ) {
            ResponseBean.DataBean.ProblemsBean problemsBean = dataList.get(i);
            List<ResponseBean.DataBean.ProblemsBean.BodydetailsBean> beanList = new ArrayList<>();
            for (int j = 0; j < problemsBean. getBodydetails(). size(); j ++ ) {
                //data conversion
                ResponseBean.DataBean.ProblemsBean.BodydetailsBean bean = problemsBean.getBodydetails().get(j);
                ResponseBean.DataBean.ProblemsBean.BodydetailsBean newBean = new ResponseBean.DataBean.ProblemsBean.BodydetailsBean();
                newBean.setConditionValue(bean.getConditionValue());
                newBean.setConditionName(bean.getConditionName());
                //custom subscript
                newBean.setFlag((i + 1) + bean.getConditionValue());
                beanList.add(newBean);
            }
            problemsBean.setBodydetails(beanList);
            mDataBeanList.add(problemsBean);
        }
        Log.d(TAG, "data bean list = " + mDataBeanList.toString());
    }
}

Run the program and call it a day. The code will be uploaded later. If you have any questions, please leave a comment and reply as soon as possible.