Android多选Spinner

70

如何创建一个允许选择多个项目的下拉列表,即带有复选框的下拉列表?


1
据我所知,Spinner 没有多选模式。 - Vikas Patidar
你应该如何在下拉列表中显示所选值呢? - ingsaurabh
我的意思是如何实现这个功能:一个类似于Spinner的小部件,但是它会显示一个带有复选框的对话框,并允许多项选择。 - user468311
https://dev59.com/dmAf5IYBdhLWcg3wizSu#47284385 - Milad Ahmadi
6个回答

162

我编写了一个自定义的MultiSpinner实现。它看起来类似于常规的spinner,但是它具有复选框而不是单选按钮。选择的值用逗号分隔显示在spinner上。所有值都默认被选中。试一下:

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);
    }
}

您可以在XML中像这样使用它:

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

你可以在Java中按照以下方式传递数据给它:

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

同时,您需要实现监听器,它将返回相同长度的数组,用true或false表示选中或未选中。

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);

2
你怎么从组件中获取所选的索引?我能够使用它,但是如何获取所选项目?谢谢。 - Thiago
2
运行得很好!这是一段很棒的代码。感谢您发布完整的清单。 - walkerk
1
你需要添加一些描述来获取值,这样新手就可以轻松理解。做得很好! - Pratik Butani
4
@Destil 我想要一个下拉式视图 (DropDown View),而不是对话框。这可行吗? - Pratik Butani
2
如何为其提供搜索功能?有链接吗? - Pratik Butani
显示剩余15条评论

14

我想展示@Destil的MultiSpinner的替代版本(感谢您的启发式代码),它允许在xml中使用"android:entries",就像一个spinner。

它最初不会显示默认文本,比如“选择一个”,但是您可以通过在构造函数中设置一个额外的ArrayAdapter来轻松获得它。

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://dev59.com/ym445IYBdhLWcg3wJ258#6022474
 */
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" />

9
据我所知,Spinner没有多选模式。相反,您可以创建一个ImageButton,在右侧设置一个可绘制的向下箭头,并在单击事件上打开一个带有多个复选框项目的Dialog

3

感谢您的帖子!很好的解决方案。我对类(方法setItems)进行了小改动,允许用户设置已选择的项目,而不是默认全部选择。

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);
}

itemValues 列表中应该包含什么内容? - eoinzy
1
假设您有一个国家列表。在itemValues中,有ID值(例如“us”,“uk”,“de”,“ch”或“1”,“2”,“3”,“4”),在items中有描述(“美国”,“英国”,“德国”,“瑞士”)。 - ᗩИᎠЯƎᗩ
我已经让多选旋转器正常工作了,但现在如何将所选的itemValues提取为逗号分隔字符串呢?'1,2,4,7' - Utrolig
如何获取所选项目? - Sheriff Said Elahl

2

您可以查看一个简单的库: MultiSelectSpinner

您只需执行以下步骤:

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());
            }
        }
    }
});
listArray1将成为您的数组。
如何使用多项选择微调器中查看完整示例。

在 Github 上发布问题 @Hasini - Pratik Butani
1
为什么需要使用KeyPairBoolData?..我们不能在你的依赖项中使用String数组吗? - KrishnaJ

0
  • Kotlin版本基于this答案。
  • 继承AppCompatSpinner而不是Spinner。
  • 添加onTouchEvent,否则点击无法被识别。
  • 进行了一些重构以提高可读性。
  • 添加了unselectAll方法。
  • 使用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()
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接