Complete the multi-selection function with the multi-selection mode of ListView

Keywords: Android xml Java encoding

When it comes to using ListView to do this, many people's first idea is to change the display mode by maintaining a selected collection and then judging whether it is selected in adapter (I've seen multiple selections implemented in this way more than once).
But what many people don't know is that ListView actually has its own multi-selection mode, which makes it easy to complete the multi-selection function by putting the effect map first.

ListView was designed with multi-selection in mind, so ListView actually has four modes: CHOICE_MODE_NONE, CHOICE_MODE_SINGLE, CHOICE_MODE_MULTIPLE, CHOICE_MODE_MULTIPLE_MODAL. CHOICE_MODE_NONE is a general mode, CHOICE_MODE_SINGLE is a single-selection mode, CHOICE_MODE_MUIPLE, CHOICE_MODE_MULTIPLE_MODAL is a multi-selection mode, but both are multi-selection modes. One difference is that when the mode is CHOICE_MODE_MULTIPLE_MODAL, the user must enter the multi-selection mode by pressing any list item for a long time, otherwise the multi-selection mode cannot be carried out. Of course, you can also turn on this state by calling the setItemChecked(int position, boolean value) of ListView to set one of its list items to be selected. We chose CHOICE_MODE_MULTIPLE mode.
The above code first identifies the selector lv_item_selecter.xml that identifies the selected and unselected states in the list

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@color/lv_checked" android:state_activated="true"></item>
    <item android:drawable="@color/lv_unchecked" android:state_activated="false"></item>

</selector>

Then the corresponding color value, color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="lv_unchecked">#00ff00</color>
    <color name="lv_checked">#ff0000</color>

</resources>

It should be noted that because the status identifier of the selected item in ListView is activated, the state set by our selector is state_activated, which is invalid with state_selected or state_pressed.
Next, we set the selector to the layout of our ListView item
item.xml

<?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:background="@drawable/lv_item_selecter"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/txt"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center" />

</LinearLayout>

Layout file activity_main.xml

<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" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_multichoice"
            android:layout_width="120dp"
            android:layout_height="60dp"
            android:text="Open multiple selection" />

        <Button
            android:id="@+id/btn_selectall"
            android:layout_width="120dp"
            android:layout_height="60dp"
            android:text="All election" />

        <Button
            android:id="@+id/btn_unselectall"
            android:layout_width="120dp"
            android:layout_height="60dp"
            android:text="Cancel all elections" />
    </LinearLayout>

    <TextView
        android:id="@+id/txt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>

</LinearLayout>

Then the adapter MyAdapter.java

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

/**
 * @author lhq
 */
public class MyAdapter extends BaseAdapter {
    private List<String> list;
    private LayoutInflater inflater;

    public MyAdapter(List<String> list, Context context) {
        super();
        this.list = list;
        this.inflater = LayoutInflater.from(context);
    }

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

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (null == convertView) {
            holder = new ViewHolder();
            convertView = inflater.inflate(R.layout.item, null);
            holder.txt = (TextView) convertView.findViewById(R.id.txt);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.txt.setText(list.get(position));
        return convertView;
    }

    class ViewHolder {
        TextView txt;
    }

}

Next is our activity.

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
    private ListView lv;
    private Button multiChoiceBtn, selectAllBtn, unSelectAllBtn;
    private TextView txt;
    private List<String> list = new ArrayList<String>();
    /**
     * Marking is currently in a multiple-choice state
     */
    private boolean isMultiChoice = false;
    private MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initVariables();
        initView();
        initEvent();

    }

    /**
     * Initialization data
     */
    private void initVariables() {
        for (int i = 0; i < 20; i++) {
            list.add("I am an entry." + i);
        }

        adapter = new MyAdapter(list, MainActivity.this);
    }

    /**
     * Initialization control
     */
    private void initView() {
        lv = (ListView) findViewById(R.id.lv);
        multiChoiceBtn = (Button) findViewById(R.id.btn_multichoice);
        selectAllBtn = (Button) findViewById(R.id.btn_selectall);
        unSelectAllBtn = (Button) findViewById(R.id.btn_unselectall);
        txt = (TextView) findViewById(R.id.txt);

        lv.setAdapter(adapter);
    }

    /**
     * Events for setting controls
     */
    private void initEvent() {
        lv.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
                if (isMultiChoice) {
                    updateTxt();
                } else {
                    Toast.makeText(MainActivity.this, "Click." + arg2, Toast.LENGTH_SHORT).show();
                }

            }

        });

        selectAllBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                for (int i = 0; i < adapter.getCount(); i++) {
                    lv.setItemChecked(i, true);
                    updateTxt();
                }
            }
        });

        unSelectAllBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                // Clear all selected data
                lv.clearChoices();
                // Update ListView
                adapter.notifyDataSetInvalidated();
                updateTxt();
            }
        });
        multiChoiceBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View view) {
                if (isMultiChoice) {
                    // Exit multiple-choice mode
                    Toast.makeText(MainActivity.this, "Withdraw from multiple elections", Toast.LENGTH_SHORT).show();
                    multiChoiceBtn.setText("Open multiple selection");
                    // Clear all selected data
                    lv.clearChoices();
                    updateTxt();
                    // Update ListView
                    adapter.notifyDataSetInvalidated();
                    lv.postDelayed(new Runnable() {

                        @Override
                        public void run() {
                            // Change the ListView mode to the normal mode
                            lv.setChoiceMode(ListView.CHOICE_MODE_NONE);
                        }
                    }, 100);
                    isMultiChoice = false;
                } else {
                    // Switch to multiple-choice mode
                    Toast.makeText(MainActivity.this, "Switch to Multiple Selection", Toast.LENGTH_SHORT).show();
                    multiChoiceBtn.setText("Withdraw from multiple elections");
                    // Open Multiple Selection Mode
                    lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
                    isMultiChoice = true;
                }
            }
        });
    }

    /**
     * Update the content on TextView
     */
    private void updateTxt() {
        SparseBooleanArray array = lv.getCheckedItemPositions();
        if (null != array && array.size() > 0) {
            String str = "";
            // Traversing the selected result set
            for (int i = 0; i < array.size(); i++) {
                int key = array.keyAt(i);
                if (array.get(key)) {
                    str += key + ",";
                }
            }
            txt.setText("Selected" + str);
        } else {
            txt.setText("No items were selected");
        }
    }

}

As you can see, we switch between multiple-choice and normal states by calling the setChoiceMode of ListView. Clear Choices () is a method to clear all selected items. Remember to call notifyDataSetInvalidated() of adapter to update the status of ListView after clearing, because clearChoices() only clears the status of selected items, and does not update the view of items. The selected items will still exist, and we need to update them on our own initiative.
We can get a SparseBoolean Array by getCheckedItemPositions(), and we can traverse it to get the coordinates of all selected item s.
Another thing to note is that when I quit the multiple-choice mode, I was delaying setChoiceMode execution by postDelayed.

if (isMultiChoice) {
                    // Exit multiple-choice mode
                    Toast.makeText(MainActivity.this, "Withdraw from multiple elections", Toast.LENGTH_SHORT).show();
                    multiChoiceBtn.setText("Open multiple selection");
                    // Clear all selected data
                    lv.clearChoices();
                    updateTxt();
                    // Update ListView
                    adapter.notifyDataSetInvalidated();
                    lv.postDelayed(new Runnable() {

                        @Override
                        public void run() {
                            // Change the ListView mode to the normal mode
                            lv.setChoiceMode(ListView.CHOICE_MODE_NONE);
                        }
                    }, 100);
                    isMultiChoice = false;

This is because I found that if you call setChoiceMode (ListView. CHOICE_MODE_NONE) directly after notify DataSetInvalidated (), or setChecked(), the interface of ListView will not be updated. So I've used a clever way to delay the execution of this code until the interface update is complete, and this problem won't arise (if there's a better way to ask, it's a long-standing problem for me).
ps: In this mode, item's click events can also be executed properly, so this should be taken into account when dealing with click events.

Posted by tromton on Wed, 10 Apr 2019 17:15:31 -0700