如何使用Android Spinner作为下拉列表

时间:2011-11-02 03:06:08

标签: android spinner selection

我花了很长时间才知道Android Spinner。在几次失败的实施尝试之后,以及在阅读了许多与我自己的问题部分类似的问题之后,without satisfactory answers,以及一些完全没有任何答案的问题,例如herehere,我终于认为Android中的“微调器”并不意味着与桌面应用程序中的“下拉列表”或HTML中的选择相同。然而,我的应用程序(我猜测所有其他问题相似的海报的应用程序)需要的东西就像一个下拉框,而不是像一个微调器。

我的两个问题是我最初认为是OnItemSelectedListener的特殊性问题(我在这个网站上看到这些是单独的问题而不是一个问题):

  1. 在没有用户交互的情况下自动触发第一个列表项的初始选择。
  2. 当用户再次选择已选择的项目时,将忽略该项目。
  3. 现在我意识到,当你想到它时,在微调器上发生它是有道理的 - 它必须以选择的默认值开始,并且你只旋转它来改变它值,而不是“重新选择”一个值 - the documentation实际上说:“只有当新选择的位置与先前选择的位置不同时才会调用此回调”。我已经看到answers suggesting that you set up a flag忽略了第一个自动选择 - 如果没有别的办法,我想我可以忍受。

    但是,因为我真正想要的是一个下拉列表,其行为应该是一个下拉列表(并且正如用户可以并且应该期望的那样),我需要的是之类的一个Spinner表现得像一个下拉菜单,就像一个组合框。我不关心任何自动预选(这应该在没有触发我的听众的情况下发生),并且我想知道每个选择,即使它与之前的相同(毕竟,用户再次选择相同的项目。)

    所以... Android中有什么东西可以做到这一点,或者某些解决方法使Spinner表现得像下拉列表?如果 这个网站上有一个我没有找到的问题,并且有一个满意的答案,请告诉我(在这种情况下,我真诚地为重复这个问题而道歉)。< / p>

6 个答案:

答案 0 :(得分:8)

+1大卫的回答。但是,这是一个实现建议,不涉及从源代码复制粘贴代码(顺便说一句,看起来与David发布的in 2.3 as well完全相同):

@Override
void setSelectionInt(int position, boolean animate) {
    mOldSelectedPosition = INVALID_POSITION;
    super.setSelectionInt(position, animate);
}

这样你就可以欺骗父方法,让它每次都认为它是一个新的位置。

或者,您可以尝试在单击微调器并将其设置回onNothingSelected时将位置设置为无效。这不是很好,因为用户在对话框启动时不会看到选择了什么项目。

答案 1 :(得分:5)

好吧,我认为在大卫和菲利克斯的回答(我相信大卫帮助菲利克斯,后者反过来帮助我)的帮助下,我已经为自己的情况想出了一个解决方案。我想我会在这里将它与代码示例一起发布,以防其他人发现这种方法也很有用。它还解决了我的两个问题(不需要的自动选择和所需的重新选择触发器)。

我所做的是添加一个“请选择”虚拟项目作为我列表中的第一项(最初只是为了解决自动选择问题,以便我可以忽略它何时被选中没有用户交互),然后,当选择另一个项并且我已经处理了选择时,我只需将重置微调器到虚拟项(它被忽略)。想想看,在我决定在这个网站上发布我的问题之前我应该​​已经想到了这一点,但事后总会更明显......我发现写我的问题实际上帮我思考了什么我想实现。

显然,如果有一个虚拟物品不适合你的情况,这对你来说可能不是理想的解决方案,但是因为我想要的是当用户选择一个值时触发一个动作(并且值保持选中状态)在我的特定情况下不是必需的),这很好用。我将尝试添加一个简化的代码示例(可能无法按原样编译,我已经从我的工作代码中删除了一些内容,并在粘贴之前重命名了一些内容,但希望您能得到这个想法)。

首先,包含微调器的列表活动(在我的例子中),我们称之为MyListActivity:

public class MyListActivity extends ListActivity {

    private Spinner mySpinner;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // TODO: other code as required...

        mySpinner = (Spinner) findViewById(R.id.mySpinner);
        mySpinner.setAdapter(new MySpinnerAdapter(this));
        mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> aParentView,
                        View aView, int aPosition, long anId) {
                if (aPosition == 0) {
                    Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
                } else {
                    Log.d(getClass().getName(), "Handling selection of actual list item...");
                    // TODO: insert code to handle selection
                    resetSelection();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> anAdapterView) {
                // do nothing
            }
        });
    }

    /**
     * Reset the filter spinner selection to 0 - which is ignored in
     * onItemSelected() - so that a subsequent selection of another item is
     * triggered, regardless of whether it's the same item that was selected
     * previously.
     */
    protected void resetSelection() {
        Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
        mySpinner.setSelection(0);
    }
}

spinner适配器代码看起来像这样(如果你愿意的话,实际上可能是上面列表活动中的内部类):

public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {

    private List<MyListItem> items; // replace MyListItem with your model object type
    private Context context;

    public MySpinnerAdapter(Context aContext) {
        context = aContext;
        items = new ArrayList<MyListItem>();
        items.add(null); // add first dummy item - selection of this will be ignored
        // TODO: add other items;
    }

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

    @Override
    public Object getItem(int aPosition) {
        return items.get(aPosition);
    }

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

    @Override
    public View getView(int aPosition, View aView, ViewGroup aParent) {
        TextView text = new TextView(context);
        if (aPosition == 0) {
            text.setText("-- Please select --"); // text for first dummy item
        } else {
            text.setText(items.get(aPosition).toString());
            // or use whatever model attribute you'd like displayed instead of toString()
        }
        return text;
    }
}

我猜(还没试过),使用setSelected(false)代替setSelection(0)可以达到同样的效果,但重新设置为“请选择”非常适合我的目的。并且,“看,马,没有旗帜!” (虽然我想忽略0选择并没有那么不同。)

希望这可以帮助其他人使用类似的用例。 :-)对于其他用例,Felix的回答可能更合适(感谢Felix!)。

答案 2 :(得分:4)

看。我不知道这是否会对你有所帮助,但由于你似乎已经厌倦了寻找答案而没有取得多大成功,这个想法可能对你有所帮助,谁知道......

Spinner类派生自AbsSpinner。在这里,有这种方法:

void setSelectionInt(int position, boolean animate) {
        if (position != mOldSelectedPosition) {
            mBlockLayoutRequests = true;
            int delta  = position - mSelectedPosition;
            setNextSelectedPositionInt(position);
            layout(delta, animate);
            mBlockLayoutRequests = false;
        }
    }

这是来自1.5 source的AFAIK。也许你可以检查一下这个来源,看看Spinner / AbsSpinner是如何工作的,并且可能只是扩展那个类就足以捕获正确的方法而检查是否position != mOldSelectedPosition

我的意思是...这是一个巨大的“可能”,有很多“ifs”(想到Android版本等等),但是因为你看起来很沮丧(而且我曾多次使用Android),也许这可以给你一些“光”。我认为通过查看您之前的研究没有其他明显的答案。

祝你好运!

答案 3 :(得分:2)

如果要在同一活动中同时进行多个同时选择,则修改微调器非常有用。 如果您只希望用户进行分层选择,例如:

  

你想吃什么?

水果

  • 苹果
  • 香蕉
  • 橙子

快餐

  • 伯格斯
  • 弗里斯
  • 热狗,

那么ExpandableListView对你来说可能更好。它允许用户导航不同组的层次结构并选择子元素。这类似于让几个Spinners供用户选择 - 如果你不想同时选择,那就是。

答案 4 :(得分:1)

以下是区分任何(有意或无意)程序化和用户启动的更改的替代解决方案:

为微调器创建一个侦听器,作为OnTouchListener和OnItemSelectedListener

public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {

    boolean userSelect = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        userSelect = true;
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (userSelect) { 
            // Your selection handling code here
            userSelect = false;
        }
    }

}

将侦听器添加到微调器注册两种事件类型

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);

这不会处理用户重新选择相同项目不会触发onItemSelected方法(我没有观察到)的情况,但我想这可以通过添加一些代码来处理onTouch方法。

无论如何,Amos指出的问题让我疯狂,然后才想到这个解决方案,所以我想我会尽可能广泛地分享。有许多线程讨论这个问题,但到目前为止我只看到了另一个与此类似的解决方案:https://stackoverflow.com/a/25070696/4556980

答案 5 :(得分:0)

在我意识到PopupMenu小部件是我真正想要的之前,我已经解决了这个线程中提到的几个问题。如果没有改变Spinner功能所需的黑客和变通方法,这很容易实现。当这个线程在2011年启动时,PopupMenu相对较新,但我希望这可以帮助现在搜索类似功能的人。