OnItemSelectedListener 事件处理程序在以编程方式更改微调器选择时以及用户物理单击微调器控件时都会被调用。是否可以确定事件是否由用户选择以某种方式触发?
还是有另一种处理微调器用户选择的方法?
要解决此问题,您需要记住最后选择的位置。然后在您的微调器侦听器内部将最后选择的位置与新位置进行比较。如果它们不同,则处理事件并用新的位置值更新最后选择的位置,否则跳过事件处理。
如果在代码中的某处您要以编程方式更改微调器选择的位置并且您不希望侦听器处理该事件,那么只需将最后选择的位置重置为您要设置的位置。
是的,Android 中的 Spinner 很痛苦。我什至会说痛苦从它的名字开始——“Spinner”。是不是有点误导?:) 就我们所说的而言,您还应该知道存在一个错误 - Spinner 可能不会(并非总是)恢复其状态(在设备旋转时),因此请确保您手动处理 Spinner 的状态。
很难相信,一年半之后,问题依然存在,并继续困扰着人们……
我想我会在阅读 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);
}
}
它所做的只是为已选择的相同位置捕获项目选择事件(例如,位置 0 的初始自动触发选择),并将其他事件传递给包装的侦听器。要使用它,您所要做的就是修改代码中调用侦听器的行以包含包装器(当然还要添加右括号),而不是说:
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
...
});
你会有这个:
mySpinner.setOnItemSelectedListener(new OnItemSelectedListenerWrapper(new OnItemSelectedListener() {
...
}));
显然,一旦您对其进行了测试,您就可以摆脱 Log 调用,并且如果需要,您可以添加重置最后一个位置的功能(当然,您必须保留对实例的引用,而不是声明-the-fly) 正如 Arhimed 所说。
希望这可以帮助某人避免被这种奇怪的行为逼疯;-)
过去我做过这样的事情来区分
internal++; // 'internal' is an integer field initialized to 0
textBox.setValue("...."); // listener should not act on this internal setting
internal--;
然后在 textBox 的监听器中
if (internal == 0) {
// ... Act on user change action
}
我使用 ++ 和 -- 而不是将布尔值设置为“true”,这样当方法嵌套其他可能也设置内部更改指示器的方法时就不用担心了。
我最近在使用微调器时遇到了这种情况,并且互联网没有提出合适的解决方案。
我的应用场景:
用于设置和查看 CPU 频率的 X 微调器(动态,每个 cpu 2 个,最小值和最大值)。它们在应用程序启动时被填充,并且它们还获得 cpu 集的当前最大/最小频率。一个线程在后台运行,每秒检查一次更改并相应地更新微调器。如果用户设置了微调器内的新频率,则设置新频率。
问题是线程访问 setSelection 以更新当前频率,这反过来又调用了我的侦听器,我无法知道是用户还是线程更改了值。如果是线程,我不希望调用侦听器,因为不需要更改频率。
我想出了一个非常适合我需要的解决方案,并且可以在您的通话中围绕听众工作:)(我认为这个解决方案可以让您最大限度地控制)
我扩展了 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;
}
}
并创建了我自己的 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
}
}
如果您现在创建一个 MySpinner,您可以使用它来设置选择:
setSelection(position, callListener);
其中 callListener 为真或假。True 将调用侦听器并且是默认值,这就是识别用户交互的原因,false 也将调用侦听器,但使用此特殊情况所需的代码,在我的情况下,例如:Nothing。
我希望其他人发现这很有用,并且可以省去漫长的旅程来查看是否已经存在这样的东西:)
我还在互联网上寻找了一个好的解决方案,但没有找到任何满足我需求的解决方案。所以我在 Spinner 类上编写了这个扩展,这样你就可以设置一个简单的 OnItemClickListener,它的行为与 ListView 相同。
只有当一个项目被“选中”时,才会调用 onItemClickListener。
玩得开心!
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());
}
}
}
只是为了扩展上面的 aaamos 帖子,因为我没有 50 个代表点可以发表评论,所以我在这里创建一个新答案。
基本上,他的代码适用于初始 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);
}
基本上,这段代码将忽略 onItemSelected() 的第一次触发,然后是所有后续的“相同位置”调用。
当然,这里的要求是以编程方式设置选择,但是如果默认位置不是 0,无论如何都应该是这种情况。
我做了一些日志记录,发现它只会在初始化时被调用,这很烦人。看不到需要所有这些代码,我只是创建了一个初始化为保护值的实例变量,然后在第一次调用该方法后设置它。
我在调用 onItemSelected 方法时记录了它,否则它只被调用一次。
我遇到了一个问题,它正在创建两个东西并意识到这是因为我在我的自定义适配器上调用 add(),它已经引用了我正在引用的列表并添加到适配器外部。在我意识到这一点并删除了 add 方法后,问题就消失了。
你们确定需要所有这些代码吗?
我知道这已经很晚了,但我想出了一个非常简单的解决方案。它基于Arhimed的回答,完全一样。它也很容易实现。参考接受的答案: