Android Spinner 选择
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2636098/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android Spinner selection
提问by Arutha
The OnItemSelectedListener event handler gets called both when a spinner selection is changed programmatically, and when a user physically clicks the spinner control. Is is possible to determine if an event was triggered by a user selection somehow?
OnItemSelectedListener 事件处理程序在以编程方式更改微调器选择时以及用户物理单击微调器控件时都会被调用。是否可以以某种方式确定事件是否由用户选择触发?
Or is there another way to handle spinner user selections?
或者是否有另一种方法来处理微调器用户选择?
回答by Vit Khudenko
To workaround you need to remember the last selected position. Then inside of your spinner listener compare the last selected position with the new one. If they are different, then process the event and also update the last selected position with new position value, else just skip the event processing.
要解决此问题,您需要记住上次选择的位置。然后在您的微调侦听器内部将最后选择的位置与新位置进行比较。如果它们不同,则处理事件并用新的位置值更新最后选择的位置,否则跳过事件处理。
If somewhere within the code you are going to programatically change spinner selected position and you don't want the listener to process the event, then just reset the last selected position to the one you're going to set.
如果在代码中的某处您打算以编程方式更改微调器选定的位置,并且您不希望侦听器处理该事件,那么只需将最后选定的位置重置为您要设置的位置。
Yes, Spinner in Android is painful. I'd even say pain starts from its name - "Spinner". Isn't it a bit misleading? :) As far as we're talking about it you should also be aware there's a bug - Spinner may not restore (not always) its state (on device rotation), so make sure you handle Spinner's state manually.
是的,Android 中的 Spinner 很痛苦。我什至会说痛苦是从它的名字开始的——“Spinner”。是不是有点误导?:) 就我们所讨论的而言,您还应该知道存在一个错误 - Spinner 可能不会(并非总是)恢复其状态(在设备旋转时),因此请确保您手动处理 Spinner 的状态。
回答by Amos M. Carpenter
Hard to believe that a year and a half later, the problem still exists and continues to boggle people...
很难相信,一年半过去了,这个问题依然存在,并继续令人难以置信……
Thought I'd share the workaround I came up with after reading Arhimed's most useful post (thanks, and I agree about spinners being painful!). What I've been doing to avoid these false positives is to use a simple wrapper class:
我想在阅读 Arhimed 最有用的帖子后分享我想出的解决方法(谢谢,我同意微调器很痛苦!)。为了避免这些误报,我一直在做的是使用一个简单的包装类:
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
public class OnItemSelectedListenerWrapper implements OnItemSelectedListener {
private int lastPosition;
private OnItemSelectedListener listener;
public OnItemSelectedListenerWrapper(OnItemSelectedListener aListener) {
lastPosition = 0;
listener = aListener;
}
@Override
public void onItemSelected(AdapterView<?> aParentView, View aView, int aPosition, long anId) {
if (lastPosition == aPosition) {
Log.d(getClass().getName(), "Ignoring onItemSelected for same position: " + aPosition);
} else {
Log.d(getClass().getName(), "Passing on onItemSelected for different position: " + aPosition);
listener.onItemSelected(aParentView, aView, aPosition, anId);
}
lastPosition = aPosition;
}
@Override
public void onNothingSelected(AdapterView<?> aParentView) {
listener.onNothingSelected(aParentView);
}
}
All it does is trap item selected events for the same position that was already selected (e.g. the initial automatically triggered selection for position 0), and pass on other events to the wrapped listener. To use it, all you have to do is modify the line in your code that calls the listener to include the wrapper (and add the closing bracket of course), so instead of, say:
它所做的只是为已选择的相同位置捕获 item selected 事件(例如,位置 0 的初始自动触发选择),并将其他事件传递给包装的侦听器。要使用它,您所要做的就是修改代码中调用侦听器以包含包装器的行(当然并添加右括号),因此,而不是说:
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
...
});
you'd have this:
你会有这个:
mySpinner.setOnItemSelectedListener(new OnItemSelectedListenerWrapper(new OnItemSelectedListener() {
...
}));
Obviously once you've tested it, you could get rid of the Log calls, and you could add the ability to reset the last position if required (you'd have to keep a reference to the instance, of course, rather than declaring on-the-fly) as Arhimed said.
显然,一旦您测试过它,您就可以摆脱 Log 调用,并且可以根据需要添加重置最后一个位置的功能(当然,您必须保留对实例的引用,而不是声明-the-fly)正如Arhimed所说。
Hope this can help someone from being driven crazy by this strange behaviour ;-)
希望这可以帮助有人被这种奇怪的行为逼疯;-)
回答by Jim Blackler
In the past I've done things like this to distinguish
过去我做过这样的事情来区分
internal++; // 'internal' is an integer field initialized to 0
textBox.setValue("...."); // listener should not act on this internal setting
internal--;
Then in textBox's listener
然后在 textBox 的监听器中
if (internal == 0) {
// ... Act on user change action
}
I use ++ and -- rather than setting a boolean value to 'true' so that there is no worry when methods nest other methods that might also set the internal change indicator.
我使用 ++ 和 -- 而不是将布尔值设置为“true”,以便在方法嵌套其他可能也设置内部更改指示符的方法时不用担心。
回答by showp1984
I had this situation lately when using spinners and the internet didn't came up with a suitable solution.
我最近在使用微调器时遇到了这种情况,并且互联网没有提出合适的解决方案。
My application scenario:
我的应用场景:
X spinners (dynamically, 2 for each cpu, min & max) for setting & viewing the CPU-Frequency. They are filled when the application starts and they also get the current max/min freq of the cpu set. A thread runs in the background and checks for changes every second and updates the spinners accordingly. If a new frequency inside the spinner is set by the user the new frequency is set.
X 微调器(动态地,每个 cpu 2 个,最小值和最大值)用于设置和查看 CPU 频率。它们在应用程序启动时被填充,并且它们还获得 CPU 集的当前最大/最小频率。一个线程在后台运行并每秒检查更改并相应地更新微调器。如果用户设置了微调器内的新频率,则设置新频率。
The issue was that the thread accessed setSelection to update the current frequency which in turn called my listener and I had no way of knowing if it was the user or the thread that changed the value. If it was the thread I didn't want the listener to be called since there would have been no need to change the frequency.
问题是线程访问 setSelection 以更新当前频率,该频率又调用我的侦听器,我无法知道是用户还是线程更改了值。如果是线程,我不希望调用侦听器,因为不需要更改频率。
I came up with a solution that suits my needs perfectly and works around the listener on your call :) (and I think this solution gives you maximal control)
我想出了一个完全适合我的需求的解决方案,并且可以在您的通话中围绕听众工作:)(我认为这个解决方案为您提供了最大的控制权)
I extended Spinner:
我扩展了微调器:
import android.content.Context;
import android.widget.Spinner;
public class MySpinner extends Spinner {
private boolean call_listener = true;
public MySpinner(Context context) {
super(context);
}
public boolean getCallListener() {
return call_listener;
}
public void setCallListener(boolean b) {
call_listener = b;
}
@Override
public void setSelection(int position, boolean lswitch) {
super.setSelection(position);
call_listener = lswitch;
}
}
and created my own OnItemSelectedListener:
并创建了我自己的 OnItemSelectedListener:
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
public class SpinnerOnItemSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
MySpinner spin = (MySpinner) parent.findViewById(parent.getId());
if (!spin.getCallListener()) {
Log.w("yourapptaghere", "Machine call!");
spin.setCallListener(true);
} else {
Log.w("yourapptaghere", "UserCall!");
}
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
}
If you now create a MySpinner you can use this to set the selection:
如果您现在创建一个 MySpinner,您可以使用它来设置选择:
setSelection(position, callListener);
setSelection(position, callListener);
Where callListener is either true or false. True will call the listener and is default, which is why user interactions are getting identified, false will also call the listener but uses code you want for this special case, exempli gratia in my case: Nothing.
其中 callListener 为 true 或 false。True 将调用侦听器并且是默认值,这就是用户交互被识别的原因,false 也将调用侦听器但使用您想要的代码用于这种特殊情况,在我的情况下示例:Nothing。
I hope that someone else finds this useful and is spared a long journey to look if something like this already exists :)
我希望其他人发现这很有用,并且可以免去长途跋涉来查看是否已经存在这样的东西:)
回答by Cliffus
I also looked for a good solution on the internet but didn't find any that satisfied my needs. So I've written this extension on the Spinner class so you can set a simple OnItemClickListener, which has the same behaviour as a ListView.
我也在互联网上寻找了一个很好的解决方案,但没有找到满足我需求的解决方案。所以我在 Spinner 类上编写了这个扩展,以便您可以设置一个简单的 OnItemClickListener,它与 ListView 具有相同的行为。
Only when an item gets 'selected', the onItemClickListener is called.
只有当一个项目被“选中”时,才会调用 onItemClickListener。
Have fun with it!
玩得开心!
public class MySpinner extends Spinner
{
private OnItemClickListener onItemClickListener;
public MySpinner(Context context)
{
super(context);
}
public MySpinner(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public MySpinner(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
@Override
public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener inOnItemClickListener)
{
this.onItemClickListener = inOnItemClickListener;
}
@Override
public void onClick(DialogInterface dialog, int which)
{
super.onClick(dialog, which);
if (this.onItemClickListener != null)
{
this.onItemClickListener.onItemClick(this, this.getSelectedView(), which, this.getSelectedItemId());
}
}
}
回答by Tom anMoney
Just to expand on aaamos's post above, since I don't have the 50 rep points to comment, I am creating a new answer here.
只是为了扩展上面 aaamos 的帖子,因为我没有 50 个代表点来评论,我在这里创建一个新答案。
Basically, his code works for the case when the initial Spinner selection is 0. But to generalize it, I amended his code the following way:
基本上,他的代码适用于初始 Spinner 选择为 0 的情况。但为了概括它,我按以下方式修改了他的代码:
@Override
public void setOnItemSelectedListener(final OnItemSelectedListener listener)
{
if (listener != null)
super.setOnItemSelectedListener(new OnItemSelectedListener()
{
private static final int NO_POSITION = -1;
private int lastPosition = NO_POSITION;
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if ((lastPosition != NO_POSITION) && (lastPosition != position))
listener.onItemSelected(parent, view, position, id);
lastPosition = position;
}
@Override
public void onNothingSelected(AdapterView<?> parent)
{
listener.onNothingSelected(parent);
}
});
else
super.setOnItemSelectedListener(null);
}
Basically, this code will ignore the very first firing of onItemSelected(), and then all subsequent "same position" calls.
基本上,这段代码将忽略 onItemSelected() 的第一次触发,然后是所有后续的“相同位置”调用。
Of course, the requirement here is that the selection is set programatically, but that should be the case anyhow if the default position is not 0.
当然,这里的要求是选择以编程方式设置,但如果默认位置不是 0,无论如何都应该如此。
回答by Ghoti
I did some logging and discovered that it only ever gets called on initialize, which is annoying. Can't see the need for all this code, I just created an instance variable that was initialised to a guard value, and then set it after the method had been called the first time.
我做了一些日志记录,发现它只在初始化时被调用,这很烦人。看不到所有这些代码的必要性,我只是创建了一个初始化为保护值的实例变量,然后在第一次调用该方法后设置它。
I logged when the onItemSelected method was being called it was otherwise only being called once.
我在 onItemSelected 方法被调用时记录,否则只被调用一次。
I had a problem where it was creating two of something and realised it was because I was calling add() on my custom adapter, which already had a reference to the list I was referencing and had added to outside the adapter. After I realised this and removed the add method the problem went away.
我有一个问题,它正在创建两个东西,并意识到这是因为我在我的自定义适配器上调用 add(),它已经引用了我正在引用的列表并添加到适配器外部。在我意识到这一点并删除了 add 方法之后,问题就消失了。
Are you guys sure you need all this code?
你们确定需要所有这些代码吗?
回答by Vedavyas Bhat
I know this is pretty late, but I came up with a very simple solution to this. It is based on Arhimed's answer, it's exactly the same. It's very easy to implement too. Refer the accepted answer:
我知道这已经很晚了,但我想出了一个非常简单的解决方案。它基于 Arhimed 的回答,完全相同。它也很容易实现。请参阅已接受的答案: