如何像下拉列表一样使用Android Spinner

我花了一段时间才把自己的脑袋围绕在Android Spinner上。 在几次失败的实现尝试之后,在阅读了许多与我自己的部分类似但没有满意的答案的问题之后 ,还有一些没有任何答案,例如在这里和这里 ,我终于明白Android中的“微调”并不意味着与桌面应用程序中的“下拉列表”相同,或者是HTML中的select。 然而,我的应用程序(我猜猜所有其他问题类似的海报的应用程序)需要的东西就像一个下拉框,而不像一个微调框。

我的两个问题与我第一次被认为是OnItemSelectedListener的idiosynchrasies(我已经在这个网站上看到这些单独的问题,但不是一个):

  1. 第一个列表项的初始select是在没有用户交互的情况下自动触发的。
  2. 当已经被select的项目被用户再次select时,它被忽略。

现在我意识到,当你思考这个问题的时候,在微调器上发生这种情况是有道理的 – 它必须从一个默认值开始select,而你只是为了改变这个值而旋转它,而不是“重新select”一个价值 – 文件实际上说:“只有当新select的位置不同于以前select的位置时才调用此callback。 我已经看到了答案,build议你设置一个标志来忽略第一个自动select – 如果没有别的办法,我想我可以忍受。

但是,因为我真正想要的是一个下拉列表,其行为应该是一个下拉列表(并且用户可以也应该期望),我需要的就像一个微调,就像一个下拉菜单,就像一个组合-框。 我不关心任何自动的预选(这应该发生而不触发我的听众),我想知道每一个select,即使它是以前的相同(毕竟,用户再次select相同的项目) 。

所以… Android中是否有这样的function呢,或者是一些解决方法让Spinner的行为像下拉列表一样? 如果在这个网站上有这样一个问题,我没有find,并有一个满意的答案,请让我知道(在这种情况下,我真诚地道歉,重复这个问题)。

Solutions Collecting From Web of "如何像下拉列表一样使用Android Spinner"

+1给David的回答。 但是,这里有一个实现build议,不涉及从源代码复制粘贴代码(顺便说一句,它看起来和David 在2.3中的贴图完全一样):

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

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

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

好吧,我想我已经在David和Felix的帮助下为自己的情况提出了一个解决scheme(我相信David帮助了Felix,而Felix帮助了我)。 我想我会把它和代码示例一起发布到这里,以防其他人发现这个方法很有用。 它也解决了我的两个问题(不需要的自动select和所需的重选触发器)。

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

显然,如果有一个虚拟物品不适合你的情况,这可能不是你理想的解决scheme,但是因为我想要的是当用户select一个值时触发一个动作(并且保持select的值不是必需的在我的具体情况),这工作得很好。 我将尝试添加一个简化的代码示例(可能不会按原样编译,我已经从我的工作代码中删除了一些内容,并在粘贴之前将其重命名,但希望您会明白)。

首先,包含微调列表的活动(在我的情况下),我们称之为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 (ie 'please select' item)."); mySpinner.setSelection(0); } } 

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

 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)来实现相同的效果,但是重新设置为“请select”适合我的目的很好。 而且,“看,马,没有旗帜!” (虽然我猜想忽略0select是不是不一样。)

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

看。 我不知道这是否会对你有帮助,但是因为你似乎厌倦了寻找一个没有太多成功的答案,这个想法可能会帮助你,谁知道…

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源的 AFAIK。 也许你可以检查这个源代码,看看Spinner / AbsSpinner是如何工作的,也许可以扩展这个类来捕获正确的方法,而不是检查position != mOldSelectedPosition

我的意思是…这是一个巨大的“也许”,有很多“ifs”(Android版本的想法等),但因为你似乎感到沮丧(我已经与Android多次),也许这可以给你有些“轻”。 我想通过查看你以前的研究,没有其他明显的答案。

祝你好运!

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

你想吃什么?

水果

  • 苹果
  • 香蕉
  • 桔子

快餐

  • 汉堡
  • 薯条
  • 热狗,

那么ExpandableListView可能对你更好。 它允许用户导航不同组的层次结构并select一个子元素。 这将有类似的几个纺纱供用户select – 如果你不希望同时select,也就是说。

以下是区分任何(有意或无意)程序化和用户发起的更改的替代解决scheme:

为微调控制器创build侦听器,既可以是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; } } } 

将侦听器添加到为两种事件types注册的微调器

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

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

无论如何,Amos指出的问题让我觉得很疯狂,然后才想到这个解决scheme,所以我想我会尽可能广泛地分享。 有很多讨论这个问题的线程,但是我只看到了另外一个类似于这个的解决scheme: https : //stackoverflow.com/a/25070696/4556980 。

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