Android Spinner with multiple choice
Asked Answered
C

6

70

How do I create spinner which allows to choose multiple items, i.e spinner with check boxes?

Cuyp answered 16/2, 2011 at 11:11 Comment(4)
AFAIK Spinner dosen't have a multiple choice mode.Upbeat
And how you suppose to show selected values in spinnerBeauchamp
I mean how do I implement this: widget that works like spinner but shows a dialog with checkboxes and allows multiple choice.Cuyp
https://mcmap.net/q/281096/-multi-selection-spinner-in-android-without-alertdialogPotoroo
B
162

I have written custom implementation of MultiSpinner. It's looking similar to normal spinner, but it has checkboxes instead of radiobuttons. Selected values are displayed on the spinner divided by comma. All values are checked by default. Try it:

package cz.destil.settleup.gui;

public class MultiSpinner extends Spinner implements
        OnMultiChoiceClickListener, OnCancelListener {

    private List<String> items;
    private boolean[] selected;
    private String defaultText;
    private MultiSpinnerListener listener;

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

    public MultiSpinner(Context arg0, AttributeSet arg1) {
        super(arg0, arg1);
    }

    public MultiSpinner(Context arg0, AttributeSet arg1, int arg2) {
        super(arg0, arg1, arg2);
    }

    @Override
    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
        if (isChecked)
            selected[which] = true;
        else
            selected[which] = false;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        // refresh text on spinner
        StringBuffer spinnerBuffer = new StringBuffer();
        boolean someSelected = false;
        for (int i = 0; i < items.size(); i++) {
            if (selected[i] == true) {
                spinnerBuffer.append(items.get(i));
                spinnerBuffer.append(", ");
                someSelected = true;
            } 
        }
        String spinnerText;
        if (someSelected) {
            spinnerText = spinnerBuffer.toString();
            if (spinnerText.length() > 2)
                spinnerText = spinnerText.substring(0, spinnerText.length() - 2);
        } else {
            spinnerText = defaultText;
        }
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
                android.R.layout.simple_spinner_item,
                new String[] { spinnerText });
        setAdapter(adapter);
        listener.onItemsSelected(selected);
    }

    @Override
    public boolean performClick() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setMultiChoiceItems(
                items.toArray(new CharSequence[items.size()]), selected, this);
        builder.setPositiveButton(android.R.string.ok,
                new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                });
        builder.setOnCancelListener(this);
        builder.show();
        return true;
    }

    public void setItems(List<String> items, String allText,
            MultiSpinnerListener listener) {
        this.items = items;
        this.defaultText = allText;
        this.listener = listener;

        // all selected by default
        selected = new boolean[items.size()];
        for (int i = 0; i < selected.length; i++)
            selected[i] = true;

        // all text on the spinner
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
                android.R.layout.simple_spinner_item, new String[] { allText });
        setAdapter(adapter);
    }

    public interface MultiSpinnerListener {
        public void onItemsSelected(boolean[] selected);
    }
}

You use it in XML like this:

<cz.destil.settleup.gui.MultiSpinner android:id="@+id/multi_spinner" />

And you pass data to it in Java like this:

MultiSpinner multiSpinner = (MultiSpinner) findViewById(R.id.multi_spinner);
multiSpinner.setItems(items, getString(R.string.for_all), this);

Also you need to implement the listener,which will return the same length array , with true or false to show selected to unselected..

public void onItemsSelected(boolean[] selected);
Blanketyblank answered 16/5, 2011 at 19:43 Comment(20)
how do you get the selected indexes from your component? I am able to use it but how can i get the selected items? Thank youChelsiechelsy
Just expose array selected[] via public getter.Kuopio
Works great! Awesome piece of code. Thanks for posting complete listing.Biolysis
Hi! Great piece of code! What is the license associated with your class ? Can it be used in commercial code ?Columbium
@Destil forgot to add your name so you are notified of my last post.Columbium
@nobre: All code on SO is cc-wiki with attribution. See meta.stackexchange.com/questions/25956/…Kuopio
@Destil How to set SimpleCursorAdapter for MultiSpinnerLesleylesli
@Destil, I've written an alternative version of your code... I would like to hear a feedback from you :)Lacour
@Destil I know this is really old, is there any chance you have a version where the Dialog is actually a dropdown so the look & feel of a spinner would still be the same? or would this be an issue since a dropdown wouldn't have a close button?Modernism
@Destil, your code snippet is very nice! How can I implement on value change listener?Trebuchet
you have to add some description for getting value so newbie got easily by the way Wonderful work done.Microdont
@Destil I want DropDown View instead of Dialog. is it Possible?Microdont
@Trebuchet Use MultiSpinnerListenerKuopio
@ツFellinLovewithAndroidツ It is possible but my answer is about a dialog.Kuopio
stuck in same can anyone help #29446588Disenable
I had a small bug with this code. When every choice was selected, it also showed the default text, so I fixed it here: gist.github.com/asiviero/54219136dd5cd7341a15 Thanks so much @DestilOintment
How to give search functionality in that? Is there any link?Microdont
how can I get its selection?!Friederike
For people in 2020, you will most likely get error at extends Spinner. You should replace the class to: public class MultiSpinner extends androidx.appcompat.widget.AppCompatSpinner implementsShayla
Thanks. Is it possible to refresh the spinner view? Notifying the adapter doesn't work. It has to refresh because if someone picks "all" it has to uncheck the other boxes.Begone
L
14

I would just like to show an alternative version of @Destil's MultiSpinner (thank you for your inspiring code) which allows to use "android:entries" in xml, just like a spinner.

It doesn't initially show a default text, like "choose one", but you can easily obtain it by setting an additional ArrayAdapter in the constructor.

MultiSpinner.java

package com.example.helloworld;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

/**
 * Inspired by: https://mcmap.net/q/277692/-android-spinner-with-multiple-choice
 */
public class MultiSpinner extends Spinner {

    private CharSequence[] entries;
    private boolean[] selected;
    private MultiSpinnerListener listener;

    public MultiSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiSpinner);
        entries = a.getTextArray(R.styleable.MultiSpinner_android_entries);
        if (entries != null) {
            selected = new boolean[entries.length]; // false-filled by default
        }
        a.recycle();
    }

    private OnMultiChoiceClickListener mOnMultiChoiceClickListener = new OnMultiChoiceClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which, boolean isChecked) {
            selected[which] = isChecked;
        }
    };

    private DialogInterface.OnClickListener mOnClickListener = new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // build new spinner text & delimiter management
            StringBuffer spinnerBuffer = new StringBuffer();
            for (int i = 0; i < entries.length; i++) {
                if (selected[i]) {
                    spinnerBuffer.append(entries[i]);
                    spinnerBuffer.append(", ");
                }
            }

            // Remove trailing comma
            if (spinnerBuffer.length() > 2) {
                spinnerBuffer.setLength(spinnerBuffer.length() - 2);
            }

            // display new text
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
                    android.R.layout.simple_spinner_item,
                    new String[] { spinnerBuffer.toString() });
            setAdapter(adapter);

            if (listener != null) {
                listener.onItemsSelected(selected);
            }

            // hide dialog
            dialog.dismiss();
        }
    };

    @Override
    public boolean performClick() {
        new AlertDialog.Builder(getContext())
                .setMultiChoiceItems(entries, selected, mOnMultiChoiceClickListener)
                .setPositiveButton(android.R.string.ok, mOnClickListener)
                .show();
        return true;
    }

    public void setMultiSpinnerListener(MultiSpinnerListener listener) {
        this.listener = listener;
    }

    public interface MultiSpinnerListener {
        public void onItemsSelected(boolean[] selected);
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MultiSpinner">
        <attr name="android:entries" />
    </declare-styleable>
</resources>

layout_main_activity.xml

<com.example.helloworld.MultiSpinner
    android:id="@+id/multispinner"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:entries="@array/multispinner_entries" />
Lacour answered 8/4, 2014 at 15:8 Comment(0)
U
9

As far as I know Spinner doesn't have a multiple choice mode. Instead you can create an ImageButton and set a drawable down arrow in the right side and on the click event you can open a Dialog having items with the multiple checkboxes.

Upbeat answered 16/2, 2011 at 12:10 Comment(0)
T
3

Thanks for the post! Great solution. I made a small change to the class (method setItems) to allow users to set already selected items instead of selecting all items to true by default.

public void setItems(
    List<String> items,
    List<String> itemValues, 
    String selectedList,
    String allText,
    MultiSpinnerListener listener) {
        this.items = items;
        this.defaultText = allText;
        this.listener = listener;

        String spinnerText = allText;

        // Set false by default
        selected = new boolean[itemValues.size()];
        for (int j = 0; j < itemValues.size(); j++)
            selected[j] = false;

        if (selectedList != null) {
            spinnerText = "";
            // Extract selected items
            String[] selectedItems = selectedList.trim().split(",");

            // Set selected items to true
            for (int i = 0; i < selectedItems.length; i++)
                for (int j = 0; j < itemValues.size(); j++)
                    if (selectedItems[i].trim().equals(itemValues.get(j))) {
                        selected[j] = true;
                        spinnerText += (spinnerText.equals("")?"":", ") + items.get(j);
                        break;
                }
    }

        // Text for the spinner
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
            android.R.layout.simple_spinner_item, new String[] { spinnerText });
        setAdapter(adapter);
}
Tinderbox answered 10/7, 2013 at 17:39 Comment(4)
What is meant to be in the list itemValues ?Titus
@Titus Suppose you have a list of Countries. In itemValues there are the ID's values (for example "us","uk","de","ch", or "1","2","3","4"), in items there are the descriptions ("USA,"United Kingdom","Germany","Switzerland").Hildegardhildegarde
I have the multi spinner working, but now how can I extract the selected itemValues into a comma sep string? '1,2,4,7'Peerless
how to get the selected item please?Friederike
M
2

You can check a simple library MultiSelectSpinner

You can simply do the following steps:

multiSelectSpinnerWithSearch.setItems(listArray1, new MultiSpinnerListener() {
    @Override
    public void onItemsSelected(List<KeyPairBoolData> items) {
        for (int i = 0; i < items.size(); i++) {
            if (items.get(i).isSelected()) {
                Log.i(TAG, i + " : " + items.get(i).getName() + " : " + items.get(i).isSelected());
            }
        }
    }
});

The listArray1 will be your array.

Check the full example here in How-To

Microdont answered 28/8, 2020 at 7:5 Comment(2)
Post issue on github @HasiniMicrodont
Why it is require to use KeyPairBoolData?.. Can't we use String array in your dependency?Graphite
M
1
  • Kotlin version based on this answer.
  • Extends AppCompatSpinner instead of Spinner.
  • Added onTouchEvent otherwise the click was not being recognized.
  • Made some refactoring to improve readability.
  • Added unselectAll method.
  • Fix trailing comma using joinToString.
class MultiSelectionSpinner @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) :
    androidx.appcompat.widget.AppCompatSpinner(context, attrs, defStyleAttr),
    DialogInterface.OnMultiChoiceClickListener,
    DialogInterface.OnCancelListener {

    private var items: List<String> = arrayListOf()
    lateinit var selected: BooleanArray
    private var defaultText: String? = null

    override fun onClick(dialog: DialogInterface, which: Int, isChecked: Boolean) {
        selected[which] = isChecked
    }

    override fun onCancel(dialog: DialogInterface?) {
        updateSpinnerText()
    }

    /**
     * Refresh text on spinner when closing the dialog.
     */
    private fun updateSpinnerText() {
        val spinnerText: String? = if (selected.any { it }) {
            getSelectedItemsText()
        } else {
            defaultText
        }

        spinnerText?.let {
            setSpinnerText(it)
        }
    }

    /**
     * Get a comma separated string for the selected items.
     */
    private fun getSelectedItemsText(): String {
        val selectedItems = items.filterIndexed { index, _ -> 
            selected[index] 
        }
        
        return selectedItems.joinToString(", ")
    }

    /**
     * Achieve that setting just one element to the adapter.
     */
    private fun setSpinnerText(spinnerText: String) {
        val adapter = ArrayAdapter(
            context,
            android.R.layout.simple_spinner_item,
            arrayOf(spinnerText)
        )

        setAdapter(adapter)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        super.onTouchEvent(event)

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                return true
            }

            MotionEvent.ACTION_UP -> {
                // For this particular app we want the main work to happen
                // on ACTION_UP rather than ACTION_DOWN. So this is where
                // we will call performClick().
                performClick()
                return true
            }
        }

        return false
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun performClick(): Boolean {
        val builder = AlertDialog.Builder(context)

        builder.setMultiChoiceItems(
            items.toTypedArray(),
            selected,
            this
        )

        builder.setPositiveButton(android.R.string.ok) {
          dialog, _ -> dialog.cancel()
        }

        builder.setOnCancelListener(this)

        builder.show()

        return true
    }

    fun setItems(
        items: List<String>,
        defaultText: String?
    ) {
        this.items = items
        this.defaultText = defaultText

        // By default none of the items will be selected
        selected = BooleanArray(items.size) { false }

        // Text to show initially, before selecting items
        defaultText?.let {
            setSpinnerText(it)
        }
    }

    fun unselectAll() {
        this.selected = BooleanArray(selected.size) { false }
        updateSpinnerText()
    }
}
Malta answered 14/10, 2023 at 22:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.