如何在通过代码设置选定项时禁用onItemSelectedListener被调用

39

想知道如何解决以下问题:根据两个Spinner所选项来计算结果。为了处理UI,即用户在一个Spinner中选择一个新项,我在活动的onCreate()方法中使用setOnItemSelectedListener安装了一个监听器。

现在:这当然可以正常工作。监听器的工作是触发对结果的新计算。

问题在于:由于我拦截了onPause()onResume()以保存/恢复上一次的状态,我编写了一个方法来编程方式地设置这两个Spinner的选定项,就像这里一样:

startSpinner.setSelection(pStart);
destSpinner.setSelection(pDest);

这两个调用也会调用监听器!我的结果计算方法以及通知新结果集的方法在此处被调用了两次!

这种情况下,一个愚蠢的直接方式是使用布尔变量禁用监听器内部执行的任何操作,在设置所选项目之前设置它,并在之后重新设置。 好吧。但有更好的方法吗?

我不希望代码行动调用监听器,只希望用户行动调用它们! :-(

你怎么做到的? 谢谢!


你可以使用 setSelection(pStart, false) 来设置 animate 参数。但这并不总是有效。 - sunlover3
13个回答

54

在我看来,一个更加简洁的解决方案,用于区分编程和用户启动的变化,是以下所示:

将您的监听器同时创建为OnTouchListener和OnItemSelectedListener,以监听Spinner的变化。

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);

这样,由于初始化或重新初始化而导致的对您的处理程序方法的任何意外调用都将被忽略。


6
我见过的绝佳解决方案。有时候我希望 Spinner 能够被废弃! - jophde
1
如果将SpinnerInteractionListener抽象类并创建抽象方法onSelected(AdapterView<?> parent, View view, int pos, long id, boolean userSelected),那么您可以捕获所有事件(用户、初始化等)并以不同的方式对其进行反应。这正是我所需要的完美解决方案,谢谢。 - Alexandr
我已经尽我所能尝试解决与Spinner相关的问题,最终找到了解决方法。我相信任何实现Spinner的人都应该这样做。API基本上是开箱即坏的,这就是缺失的功能。非常感谢! - boltup_im_coding
4
如果用户“触摸”视图但未更改数据,然后应用程序设置微调器,则会失败,然后会执行侦听器。 - Grzegorz Dev
根据我的使用情况,这些设备都有硬件键盘,并且理论上可以使用箭头键进行选择,而不是触摸屏幕。 - Literate Corvette

11

好的,我现在已经让它按照我的想法工作了。

需要理解的是(我在写那个问题时并不知道……),Android中所有的东西都在一个线程上运行 - UI线程。

也就是说:即使你在这里或那里设置Spinner的值,它们只有在你当前所在的所有方法(如onCreateonResume或其他方法)完成后才会被更新(可视化)调用它们的监听器之后

这样可以做到以下几点:

  • 将选定的位置保留在字段变量中(如currentPos1currentPos2)。
  • 监听器onItemSelectedListener()调用像refreshMyResult()等方法。
  • 在编程设置位置时,设置spinner在此之后手动调用自己的刷新方法。

refreshMyResult()方法看起来像这样:

int newPos1 = mySpinner1.getSelectedItemPosition();
int newPos2 = mySpinner2.getSelectedItemPosition();
// only do something if update is not done yet
if (newPos1 != currentPos1 || newPos2 != currentPos2) {
    currentPos1 = newPos1;
    currentPos2 = newPos2;

    // do whatever has to be done to update things!

}

由于监听器稍后将被调用,而此时currentPos中记住的位置已经更新,因此不会发生任何事情,也不会进行任何其他不必要的更新。

当用户在其中一个微调框中选择新值时,相应的更新将被执行!

就是这样! :-)

啊 - 还有一件事:我的问题的答案是:不行。 监听器不能(轻易地)被禁用,并且每当更改值时都会被调用。


2
我曾经在使用onCheckChanged时遇到类似的问题,我解决问题的方式是验证代码是否由用户输入调用,可以使用buttonView的isPressed方法。所以,恕我直言,除非我误解了问题,否则我认为你的方法可能不妥。请考虑验证它是由用户而不是您的onPause()和onResume()方法调用的。 - zkwentz
我喜欢Zordid的方法,因为无论输入来自哪里(用户输入或先前保存的),在我的应用程序中正确的做法都是除非值发生变化否则不采取任何措施。 - larham1
1
@Zach:onItemSelectedListener()内的isPressed()始终为false。根据文档,isPressed()表示视图当前是否处于按下状态。OnItemSelectedListener注册回调以在此AdapterView中选择项目时调用。我在onItemSelectedListener()内放置了一个调试消息,并发现isPressed()始终为false。我深入研究了代码,并发现isSelected()可能是要使用的正确方法。但是,不,这个方法在onLitemSelectedListener内也始终为false。因此,Zordid的方法是唯一的解决方案,并且它在我的代码中按预期工作。 - fangmobile

6
我有一个更简单、更好的解决方案。由于我在初始化后仍然需要刷新旋转器,这是一种更通用的方法。 请参考被接受的答案:不想要的onItemSelected调用

非常感谢,听起来是一个很好的方法!我得抽时间修改我的代码... - Zordid
很有趣,你也回答了自己的问题 - 就像我之前在我的问题中所做的一样.. :) - Zordid
1
哈哈,更有趣的是,在看了你的答案后,我理解了回调函数的工作原理 :) - Vedavyas Bhat

3

首先,添加布尔值以停止旋转器监听器的调用

  Boolean check = false;

然后你添加触摸监听器和项目点击监听器,像下面的代码一样

 holder.filters.setOnTouchListener(new View.OnTouchListener() {
               @Override
               public boolean onTouch(View v, MotionEvent event) {

                   check = true;
                   return false;
               }
           });

           holder.filters.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
           {

               @Override
               public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id)
               {
                   flag = filterids.get(position);

                   if(check)
                   {
                       check = false;
                       new Applyfilters().execute(flag,"3");
                   }else{

                   }

               }

               @Override
               public void onNothingSelected(AdapterView<?> arg0)
               {
                   // TODO Auto-generated method stub
               }
           });

这个简单的方法可以有效防止服务器被多次调用。


2
我创建了一个库,可以帮助所有人,无需在Spinner中调用item onClick操作。
例如:
spinner.setSelection(withAction,position);

其中withAction是一个布尔标志,用于调用或不调用项目操作

Github链接: https://github.com/scijoker/spinner2


2

我的解决方案非常简单。首先初始化一个全局布尔变量。

boolean shouldWork = true;

然后在您的onCreate()方法中使用以下代码。
Spinner spinner = findViewById(R.id.spinner);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView adapter, View v, int i, long lng) {
        if (shouldWork) {
               // Do your actions here
        }
        else
            shouldWork = true;
    }
    public void onNothingSelected(AdapterView<?> parentView)  {

    }
});

现在,您可以通过以下代码在任何地方使用setSelection方法而不必调用onItemSelected()方法。
shouldWork = false;
spinner.setSelection(0);

2
非常简单,您可以调用Spinner.setSelection(int position, boolean animate)方法,并使用false参数,这样监听器就不会对更改做出反应。

1
在我的设备上运行良好(Api 16) - Cocorico
4
动画参数决定了旋转器的动画方式(选择是否应该直接被选中(false)或不选中(true))。它与onItemSelected调用无关。 - Vedavyas Bhat
1
这不是正确的答案。这在我的Android 4.4.4上无法工作。 - Ajith M A
1
尝试使用setSelection(spinner, false)。没有变化,触发了监听器。Android 4.4.4 - Gena Batsyan

2

Spinner.setSelection(int position, boolean animate) 在4.3版本上会触发监听器。


这不是我的问题的答案,抱歉。 - Zordid

1
当使用Spinner.setSelection(position)时,它总是会激活setOnItemSelectedListener()。
为了避免代码被触发两次,我使用以下解决方案:
private mIsSpinnerFirstCall=true;

...
Spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        //If a new value is selected (avoid activating on setSelection())
        if(!mIsSpinnerFirstCall) {
            // Your code goes gere
        }
        mIsSpinnerFirstCall = false;
    }

    public void onNothingSelected(AdapterView<?> arg0) {
    }
});

当您确定使用Spinner.setSelection(position)时,此解决方案有效。此外,在使用Spinner.setSelection(position)之前每次设置mIsSpinnerFirstCall=true很重要。


1
在每个下拉框中设置OnItemSelectedListener时,请确保在onResume中设置了任何先前的值。

2
问题在于正在运行的活动可能会得到一个新的意图,该意图应将微调器设置为新值 - 因此,活动及其所有侦听器肯定会被设置!还有:我尝试在设置值并稍后恢复侦听器之前使用setOnItemSelectedListener(null)来停用侦听器:没有任何效果!侦听器是异步调用的! :-( - Zordid

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