安卓下拉框选择

24

当程序改变Spinner的选择时,OnItemSelectedListener事件处理程序会被调用。当用户点击Spinner控件时,该事件处理程序也会被调用。是否有可能确定事件是由用户选择触发的呢?

或者是否有其他方法来处理Spinner的用户选择?


这对我有用,使用可运行的设置选中。 https://dev59.com/RnE85IYBdhLWcg3w43wX#13528576 - Gil
8个回答

50
为了解决这个问题,您需要记住上次选择的位置。然后在您的Spinner监听器中,将上次选择的位置与新位置进行比较。如果它们不同,则处理事件并使用新位置值更新上次选择的位置,否则跳过事件处理。
如果在代码的某个位置你要编程地更改Spinner的选定位置,并且你不希望监听器处理该事件,那么只需重置上次选择的位置为你要设置的位置即可。
是的,在Android中,Spinner很痛苦。我甚至会说从它的名字“Spinner”开始就让人感到疼痛。这难道不是有点误导吗? :) 就目前而言,你也应该知道有一个bug,Spinner无法恢复(不总是)它的状态(在设备旋转时),所以一定要手动处理Spinner的状态。

如果用户想选择位置为0的项目怎么办?就我所理解的您的解决方案会忽略这一点。 - Nicolás Carrasco-Stevenson
@NicolásCarrasco,那是6年前的事了...我根本想不起来当时的情境,但肯定对解决方案没有任何问题。也许考虑将-1作为最后选择位置的起始值。 - Vit Khudenko
如果您将值设为-1,那么当Spinner布局完成时,监听器将被调用,并将0作为参数传递。当然,-1 != 0,因此事件会触发,这个问题仍未解决。我不想暗示您的答案是错误的,但它并不完整。 - Nicolás Carrasco-Stevenson

21

很难相信一年半后这个问题仍然存在并且仍然困扰着人们...

我想分享一下我在阅读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调用,如果需要,你可以添加重置最后位置的能力(当然,你必须保留实例的引用,而不是即时声明),正如Arhimed所说。

希望这可以帮助某些人避免被这种奇怪的行为搞疯了;-)


2

过去,我曾经做过类似的事情来区分

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”,这样就不用担心方法嵌套其他可能也设置内部更改指示器的方法。


6
由于在调度相应事件时,onItemSelected方法是异步调用的,因此它不适用于Spinner。 - Arutha

2

最近我遇到了一个使用旋转器的情况,但互联网上没有合适的解决方案。

我的应用场景:

使用 X 个旋转器(动态生成,每个 CPU 对应两个旋转器,最小值和最大值)来设置和查看 CPU 频率。它们在应用程序启动时填充,并获取当前 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。True将调用侦听器,并且是默认值,这就是为什么用户交互会被识别的原因,false也将调用侦听器,但使用您想要的代码进行特殊情况的处理,例如在我的情况下:无。
希望其他人能发现这个有用,并且不必长时间寻找是否已经存在类似的内容 :)

1

针对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,则应该是这样的。

1

我也在互联网上寻找了一个好的解决方案,但没有找到满足我的需求的任何东西。因此,我编写了这个扩展程序,可以在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());
            }
        }
    }

0

我知道这可能有点晚了,但我想到了一个非常简单的解决方案。它基于Arhimed的答案,完全相同。它也很容易实现。请参考被接受的答案:

不希望onItemSelected调用


0

我进行了一些日志记录并发现它只在初始化时被调用,这很烦人。我看不出需要所有这些代码的必要性,我只创建了一个实例变量,它被初始化为一个保护值,然后在第一次调用方法后设置它。

我记录了 onItemSelected 方法被调用的时间,否则它仅会被调用一次。

我遇到了一个问题,它会创建两个相同的东西,并意识到这是因为我在自定义适配器上调用了 add() 方法,这已经引用了我正在引用并且已经在适配器之外添加到列表中的列表。当我意识到这一点并删除了 add 方法后,问题就解决了。

你们确定需要所有这些代码吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接