我花了很长时间才知道Android Spinner。在几次失败的实施尝试之后,以及在阅读了许多与我自己的问题部分类似的问题之后,without satisfactory answers,以及一些完全没有任何答案的问题,例如here和here,我终于认为Android中的“微调器”并不意味着与桌面应用程序中的“下拉列表”或HTML中的选择相同。然而,我的应用程序(我猜测所有其他问题相似的海报的应用程序)需要的东西就像一个下拉框,而不是像一个微调器。
我的两个问题是我最初认为是OnItemSelectedListener的特殊性问题(我在这个网站上看到这些是单独的问题而不是一个问题):
现在我意识到,当你想到它时,在微调器上发生它是有道理的 - 它必须以选择的默认值开始,并且你只旋转它来改变它值,而不是“重新选择”一个值 - the documentation实际上说:“只有当新选择的位置与先前选择的位置不同时才会调用此回调”。我已经看到answers suggesting that you set up a flag忽略了第一个自动选择 - 如果没有别的办法,我想我可以忍受。
但是,因为我真正想要的是一个下拉列表,其行为应该是一个下拉列表(并且正如用户可以并且应该期望的那样),我需要的是之类的一个Spinner表现得像一个下拉菜单,就像一个组合框。我不关心任何自动预选(这应该在没有触发我的听众的情况下发生),并且我想知道每个选择,即使它与之前的相同(毕竟,用户再次选择相同的项目。)
所以... Android中有什么东西可以做到这一点,或者某些解决方法使Spinner表现得像下拉列表?如果 这个网站上有一个我没有找到的问题,并且有一个满意的答案,请告诉我(在这种情况下,我真诚地为重复这个问题而道歉)。< / p>
答案 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相对较新,但我希望这可以帮助现在搜索类似功能的人。