如何防止onItemSelected在新实例化的Spinner上触发?


419

我想到了一些不太优雅的方法来解决此问题,但我知道我一定会丢失一些东西。

onItemSelected会立即解雇,而无需与用户进行任何交互,这是不受欢迎的行为。我希望UI能够等到用户选择某些内容后再执行任何操作。

我什至尝试在中设置侦听器onResume(),希望这样做会有帮助,但没有帮助。

在用户可以触摸控件之前,如何停止触发它?

public class CMSHome extends Activity { 

private Spinner spinner;

@Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Heres my spinner ///////////////////////////////////////////
    spinner = (Spinner) findViewById(R.id.spinner);
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    };

public void onResume() {
    super.onResume();
    spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}

    public class MyOnItemSelectedListener implements OnItemSelectedListener {

    public void onItemSelected(AdapterView<?> parent,
        View view, int pos, long id) {

     Intent i = new Intent(CMSHome.this, ListProjects.class);
     i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
        startActivity(i);

        Toast.makeText(parent.getContext(), "The pm is " +
          parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
    }

    public void onNothingSelected(AdapterView parent) {
      // Do nothing.
    }
}
}

2
您可以看一下此解决方案,它既简单又实用。stackoverflow.com/a/10102356/621951
居纳伊居尔泰金

1
一个简单的解决方案是将第一个项目设置为Spinner空,然后在其中onItemSelected可以检测String是否不为空startActivity
穆罕默德·巴巴尔

Answers:


78

我希望您的解决方案能够正常工作-尽管如果您在设置侦听器之前设置适配器,则选择事件不会触发。

就是说,一个简单的布尔标志将使您能够检测到恶意的第一个选择事件并忽略它。


15
恩,是的。那就是我所说的优雅解决方案。似乎必须有更好的方法。不过谢谢你
FauxReal

5
开发人员ml上的该线程对此有更深入的了解:groups.google.com/group/android-developers/browse_thread/thread/…-不幸的是没有给出解决方案...
BoD 2010年

25
布置组件的过程将触发选择侦听器。因此,您必须在布局完成添加侦听器。我一直找不到合适的直接位置,因为布局似乎在onResume()和之后的某个时间发生onPostResume(),因此所有正常的挂钩都在布局发生时完成。
Dan Dyer 2010年

28
我会远离此布尔值标志-好像将来的行为更改可能会导致错误。一种更防弹的解决方案是保留一个带有“当前所选索引”的变量,该变量被初始化为所选的第一项。然后在选择事件中-检查它是否等于新位置-返回并什么也不做。当然在选择时更新变量。
daniel.gindi

2
这是行不通的。@casanova的作品有效。那应该是公认的答案。
Siddharth 2014年

379

使用Runnables是完全不正确的。

使用setSelection(position, false);在之前的初始选择setOnItemSelectedListener(listener)

这样,您可以在没有动画的情况下设置选择,这不会导致选中项目上的侦听器被调用。但是侦听器为null,因此没有任何运行。然后分配您的听众。

因此,请遵循以下确切顺序:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);

48
+1隐藏的宝石!将false传递为“ animate”参数不会调用侦听器回调。太棒了!
2014年

3
+1怪异但优雅的解决方案:)幸运的是,无论如何,我已经不得不致电setSelection ...
Martin T.

35
组装Spinner UI元素时,侦听器仍将触发,因此无论哪种方法都不能阻止OP描述的不良行为,侦听器都将触发。如果未在onCreateView()之前或之前声明,则效果很好,但这不是他们要求的。
鲁迪·克肖

6
有用,但解决的问题不同于OP提出的问题。OP表示一个选择事件,不幸的是,即使程序员没有执行setSelection,该事件也会在视图首次出现时自动触发。
ToolmakerSteve

2
setSelection(..)方法中的“ false”值参数对我来说是解决方案。ty!
Dani

194

参考Dan Dyer的答案,尝试OnSelectListenerpost(Runnable)方法中注册:

spinner.post(new Runnable() {
    public void run() {
        spinner.setOnItemSelectedListener(listener);
    }
});

通过这样做,我希望的行为终于发生了。

在这种情况下,这还意味着侦听器仅在更改的项目上触发。


1
我收到一条错误消息:AdapterView <SpinnerAdapter>类型的方法setOnItemSelectedListener(AdapterView.OnItemSelectedListener)不适用于自变量(新的Runnable(){}),为什么?
雅各布2012年

这实际上不是在Runnable和UI线程之间建立竞争条件吗?
kenny_k 2013年

6
@theFunkyEngineer-此代码应从主线程方法之一运行,例如onCreate()onResume()等等。在这种情况下,这是一个绝妙的技巧,没有竞争条件的危险。我通常onCreate()在布局代码之后使用此技巧。
理查德·勒·马修里尔

1
这是一个很好的解决方案,绝对不是黑客!这种功能是在框架深处完成工作的方式。遗憾的是Spinner不在内部执行此操作。但是,这是保证在创建Activity后可以运行某些代码的最简洁的方法。之所以可行,是因为当“活动”尝试通知时,尚未在Spinner上设置侦听器。
jophde 2014年

1
这是可以接受的解决方案。不是盲目的。其他解决方案将来更容易出现行为更改问题。
库尔德·辛格·达卡,

50

我创建了一个小的实用程序方法来更改Spinner选择而不通知用户:

private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
    final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
    spinner.setOnItemSelectedListener(null);
    spinner.post(new Runnable() {
        @Override
        public void run() {
            spinner.setSelection(selection);
            spinner.post(new Runnable() {
                @Override
                public void run() {
                    spinner.setOnItemSelectedListener(l);
                }
            });
        }
    });
}

它禁用侦听器,更改选择,然后在此之后重新启用侦听器。

诀窍是调用与UI线程异步,因此您必须在连续的处理程序中进行操作。


太棒了 我有多个微调器,并尝试在设置它们的值之前将其所有侦听器设置为null,然后将它们全部恢复为应设置的值,但由于某些原因而无法正常工作。尝试了这个功能,它起作用了。我不知道为什么我的
没用

4
请注意:如果您setSpinnerSelectionWithoutCallingListener快速拨打两次,以便在第一个已经将监听null器设置为的情况下拨打第二个电话,则您的微调器将null永远被监听器卡住。我提出以下解决方案:在if (listener == null) return;之后添加spinner.setSelection(selection)
紫罗兰色长颈鹿

34

不幸的是,似乎在此问题上最常用的两个建议解决方案,即计算回调发生次数和稍后发布Runnable以设置回调,都可能在例如启用辅助功能选项时均失败。这是一个解决这些问题的帮助程序类。进一步的说明在注释块中。

import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;

/**
 * Spinner Helper class that works around some common issues 
 * with the stock Android Spinner
 * 
 * A Spinner will normally call it's OnItemSelectedListener
 * when you use setSelection(...) in your initialization code. 
 * This is usually unwanted behavior, and a common work-around 
 * is to use spinner.post(...) with a Runnable to assign the 
 * OnItemSelectedListener after layout.
 * 
 * If you do not call setSelection(...) manually, the callback
 * may be called with the first item in the adapter you have 
 * set. The common work-around for that is to count callbacks.
 * 
 * While these workarounds usually *seem* to work, the callback
 * may still be called repeatedly for other reasons while the 
 * selection hasn't actually changed. This will happen for 
 * example, if the user has accessibility options enabled - 
 * which is more common than you might think as several apps 
 * use this for different purposes, like detecting which 
 * notifications are active.
 * 
 * Ideally, your OnItemSelectedListener callback should be
 * coded defensively so that no problem would occur even
 * if the callback was called repeatedly with the same values
 * without any user interaction, so no workarounds are needed.
 * 
 * This class does that for you. It keeps track of the values
 * you have set with the setSelection(...) methods, and 
 * proxies the OnItemSelectedListener callback so your callback
 * only gets called if the selected item's position differs 
 * from the one you have set by code, or the first item if you
 * did not set it.
 * 
 * This also means that if the user actually clicks the item
 * that was previously selected by code (or the first item
 * if you didn't set a selection by code), the callback will 
 * not fire.
 * 
 * To implement, replace current occurrences of:
 * 
 *     Spinner spinner = 
 *         (Spinner)findViewById(R.id.xxx);
 *     
 * with:
 * 
 *     SpinnerHelper spinner = 
 *         new SpinnerHelper(findViewById(R.id.xxx))
 *         
 * SpinnerHelper proxies the (my) most used calls to Spinner
 * but not all of them. Should a method not be available, use: 
 * 
 *      spinner.getSpinner().someMethod(...)
 *
 * Or just add the proxy method yourself :)
 * 
 * (Quickly) Tested on devices from 2.3.6 through 4.2.2
 * 
 * @author Jorrit "Chainfire" Jongma
 * @license WTFPL (do whatever you want with this, nobody cares)
 */
public class SpinnerHelper implements OnItemSelectedListener {
    private final Spinner spinner;

    private int lastPosition = -1;
    private OnItemSelectedListener proxiedItemSelectedListener = null;  

    public SpinnerHelper(Object spinner) {
         this.spinner = (spinner != null) ? (Spinner)spinner : null;        
    }

    public Spinner getSpinner() {
        return spinner;
    }

    public void setSelection(int position) { 
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position);     
    }

    public void setSelection(int position, boolean animate) {
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position, animate);        
    }

    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        proxiedItemSelectedListener = listener;
        spinner.setOnItemSelectedListener(listener == null ? null : this);
    }   

    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position != lastPosition) {
            lastPosition = position;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onItemSelected(
                        parent, view, position, id
                );
            }
        }
    }

    public void onNothingSelected(AdapterView<?> parent) {
        if (-1 != lastPosition) {
            lastPosition = -1;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onNothingSelected(
                        parent
                );
            }
        }
    }

    public void setAdapter(SpinnerAdapter adapter) {
        if (adapter.getCount() > 0) {
            lastPosition = 0;
        }
        spinner.setAdapter(adapter);
    }

    public SpinnerAdapter getAdapter() { return spinner.getAdapter(); } 
    public int getCount() { return spinner.getCount(); }    
    public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }   
    public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
    public Object getSelectedItem() { return spinner.getSelectedItem(); }
    public long getSelectedItemId() { return spinner.getSelectedItemId(); }
    public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
    public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
    public boolean isEnabled() { return spinner.isEnabled(); }
}

3
这应该是投票率最高的答案。简单而辉煌。它允许您将所有当前实现保持相同,除了您初始化的那一行。绝对很容易对旧项目进行改造。最重要的是,我通过实现OnTouchLisener接口关闭了微调器打开时关闭键盘,用一块石头杀死了两只鸟。现在我所有的微调器都完全按照我想要的方式工作。
user3829751 2015年

漂亮的答案。当我向适配器添加all()时,它仍然会触发第0个元素,但第0个元素是中性(不执行任何操作)的省略号。
jwehrle

31

当我不想启动时,我有很多关于微调器触发的问题,这里的所有答案都不可靠。它们有效-但仅在某些时候。您最终将遇到它们将失败的情况,并将错误引入您的代码中。

对我有用的是将最后选择的索引存储在变量中,并在侦听器中对其求值。如果它与新选择的索引相同,则不执行任何操作并返回,否则继续侦听器。做这个:

//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;

//then evaluate it in your listener
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {

  if(mLastSpinnerPosition == i){
        return; //do nothing
  }

  mLastSpinnerPosition = i;
  //do the rest of your code now

}

相信我,这是迄今为止最可靠的解决方案。骇客,但行得通!


如果您尝试更改值,这甚至可以工作吗?在我的情况下,我试图将值设置为类似3的值(实际上为0)而不触发更改侦听器。您是说int i仅在用户选择它时返回一个不同的值吗?
JStephen 2015年

嗨,JStephen,我不确定您是什么意思。但是只要触发onItemSelected,我就将成为微调框的位置。问题在于,每当首次加载微调器时都会触发onItemSelected,而没有任何实际的用户交互,在这种情况下会导致不良行为。int i在此初始点将等于0,因为这是微调器第一次加载时的默认起始索引。因此,我的解决方案将进行检查以确保选择了实际的其他项目,而不是重新选择了当前选择的项目...这是否回答了您的问题?
克里斯

克里斯,您好,我有一个页面可以从数据库中提取信息供用户编辑。当页面打开时,我将填充微调器,然后将其位置设置为数据库中的值。因此,例如,如果我将其位置设置为3,这会导致onItemSelected触发,而我将其设置为3,这与初始设置不同。我以为您是在说我只有在用户自己实际更改后才能设置。
JStephen

4
如果用户选择位置0,该怎么办?他们将被忽略。
Yetti99'9

我不认为最后的位置方法是个好主意。我通过从SharedPreferences加载位置并使用setSelection来启动微调器。创建微调器时,SharedPrefs中的值通常与默认值不同,因此onItemSelected将在启动时触发。
Arthez

26

我处于类似情况,并且有一个简单的解决方案为我工作。

似乎是方法,setSelection(int position)并且setSelected(int position, boolean animate)具有不同的内部实现。

当您使用setSelected(int position, boolean animate)带有错误动画标志的第二种方法时,无需触发onItemSelected侦听器即可获得选择。


更好的方法是不必担心对onItemSelected的额外调用,而要确保它显示正确的选择。因此,在添加侦听器之前调用spinner.setSelection(selectedIndex)使其始终如一地工作。
andude

1
没有用于旋转器的setSelected(int position,boolean animate)方法
shift66 2013年

4
您需要拨打的电话是setSelection(int position, boolean animate);
Brad

为您+1。当代码修改次数更多时,这解决了一个更普遍的问题。微调器内容和选择仅将onItemSelected保留用于用户交互
alrama

4
可悲的是,错误的动画标志仍然onItemSelected在API23中调用
mcy

23

为了充实使用​​onTouchListener的提示,以区分对setOnItemSelectedListener的自动调用(是Activity初始化的一部分,等等)与由实际用户交互触发的对其的调用,我在这里尝试了一些其他建议之后做了以下操作:发现它可以用最少的代码行很好地工作。

只需为您的“活动/片段”设置一个布尔字段,例如:

private Boolean spinnerTouched = false;

然后在设置微调器的setOnItemSelectedListener之前,设置一个onTouchListener:

    spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            spinnerTouched = true;
            return false;
        }
    });

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    ...
         if (spinnerTouched){
         //Do the stuff you only want triggered by real user interaction.
        }
        spinnerTouched = false;

1
效果很好,而且自Android 6+起,这是唯一可行的方法。但是,您还必须对setOnKeyListener()进行相同的操作,否则当用户使用键盘进行导航时,它将不起作用。
斯特凡(Stéphane)

效果很好,所有其他解决方案在不同的手机上都存在一些问题。
曾伟曾

这是简单而绝对完美的!无需多余的废话,只需牢记逻辑即可。我很高兴我一直向下滚动到这里!
user3833732 '18

除了setOnKeyListener()之外,您还可以继承Spinner的子类,并在覆盖的preformClick()方法中设置标志spinnerTouched = true,这两种情况都将调用该方法(touch / key)。休息是一样的。
全能的

只是想提一下,这似乎解决了我最近在这里发布的DropDownPreferences的相同错误:stackoverflow.com/questions/61867118 / ...我无法相信它tbh:D
Daniel Wilson

13
spinner.setSelection(Adapter.NO_SELECTION, false);

3
该代码可能说明一切,但是有一点点解释很长的路要走:)
nhaarman 2014年

8

长时间拉扯头发后,现在我创建了自己的Spinner类。我向它添加了一个方法,该方法可以适当地断开并连接侦听器。

public class SaneSpinner extends Spinner {
    public SaneSpinner(Context context) {
        super(context);
    }

    public SaneSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
    public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
        OnItemSelectedListener l = getOnItemSelectedListener();
        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(null);
        }

        super.setSelection(position, animate);

        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(l);
        }
    }
}

像这样在您的XML中使用它:

<my.package.name.SaneSpinner
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/mySaneSpinner"
    android:entries="@array/supportedCurrenciesFullName"
    android:layout_weight="2" />

您所需要做的就是在通货膨胀后检索SaneSpinner的实例,并选择呼叫集,如下所示:

mMySaneSpinner.setSelection(1, true, true);

这样,不会触发任何事件,并且不会中断用户交互。这大大降低了我的代码复杂度。由于它确实是PITA,因此应该包含在库存的Android中。


1
这对我不起作用,它仍然会触发onItemSelected。
Arthez

Arthez请仔细检查您是否真的将第三个论点传递给了真。如果是,这里还有其他问题。如果可能,请发布您的代码。
Fusion44

8

如果您推迟添加侦听器,直到布局完成,那么布局阶段就不会出现不需要的事件:

spinner.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once works for JELLY_BEAN and later
            spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            // add the listener
            spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                    // check if pos has changed
                    // then do your work
                }

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

            });

        }
    });

这是可行的,并且IMO是解决OP特定问题的最干净的解决方案。我想指出,您可以ViewTreeObserver.OnGlobalLayoutListener通过调用删除J下面的on版本ViewTreeObserver.removeGlobalOnLayoutListener,该版本已弃用,并且与该答案使用的方法名称相似。
杰克·梅斯特

7

如果您在代码中进行选择,则会发生这种情况;

   mSpinner.setSelection(0);

代替上述声明使用

   mSpinner.setSelection(0,false);//just simply do not animate it.

编辑:此方法不适用于Mi Android版本Mi UI。


2
这绝对为我解决了问题。我阅读了有关Spinner小部件的文档。理解它们之间的差异绝对是棘手的:setSelection(int position,boolean animate)->直接跳转到适配器数据中的特定项目。setSelection(int position)->设置当前选择的项目。
马特

5

我得到一个非常简单的答案,100%肯定它有效:

boolean Touched=false; // this a a global variable

public void changetouchvalue()
{
   Touched=true;
}

// this code is written just before onItemSelectedListener

 spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            changetouchvalue();
            return false;
        }
    });

//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code

if(Touched)
{
 // the code u want to do in touch event
}

3

我已经找到了更优雅的解决方案。它涉及计数已调用ArrayAdapter(在您的情况下为“ adapter”)的次数。假设您有1个微调框,然后致电:

int iCountAdapterCalls = 0;

ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

在onCreate之后声明一个int计数器,然后在onItemSelected()方法内放置一个“ if”条件来检查已调用atapter的次数。在您的情况下,您只调用了一次,所以:

if(iCountAdapterCalls < 1)
{
  iCountAdapterCalls++;
  //This section executes in onCreate, during the initialization
}
else
{
  //This section corresponds to user clicks, after the initialization
}

2

我的小贡献是对上述内容的一些修改,使我感到很满意。

声明一个整数变量作为默认值(或上次使用的值保存在首选项中)。在注册侦听器之前,使用spinner.setSelection(myDefault)设置该值。在onItemSelected中,检查新的微调器值是否等于您在运行任何其他代码之前分配的值。

如果用户再次选择相同的值,则具有不运行代码的附加优点。


1

遇到同样的问题后,我使用标签来解决这个问题。其背后的想法很简单:只要以编程方式更改微调器,就应确保标签反映所选位置。然后在侦听器中检查所选位置是否等于标签。如果是,则以编程方式更改了微调框选择。

下面是我的新“旋转代理”类:

package com.samplepackage;

import com.samplepackage.R;
import android.widget.Spinner;

public class SpinnerFixed {

    private Spinner mSpinner;

    public SpinnerFixed(View spinner) {
         mSpinner = (Spinner)spinner;
         mSpinner.setTag(R.id.spinner_pos, -2);
    }

    public boolean isUiTriggered() {
         int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
         int pos = mSpinner.getSelectedItemPosition();
         mSpinner.setTag(R.id.spinner_pos, pos);
         return (tag != -2 && tag != pos);
    }

    public void setSelection(int position) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position);
    }

    public void setSelection(int position, boolean animate) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position, animate);
    }

    // If you need to proxy more methods, use "Generate Delegate Methods"
    // from the context menu in Eclipse.
}

您还将需要一个XML文件,该文件的目录中有标签设置Values。我为文件命名spinner_tag.xml,但这取决于您。看起来像这样:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
  <item name="spinner_pos" type="id" />
</resources>

现在更换

Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);

在你的代码中

SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));

并使您的处理程序看起来像这样:

myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (myspinner.isUiTriggered()) {
            // Code you want to execute only on UI selects of the spinner
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
});

isUiTriggered()当且仅当用户已更改微调器时,该函数才会返回true。请注意,此功能有副作用-它将设置标签,因此同一监听器调用中的第二个调用将始终返回false

该包装器还将处理布局创建期间调用侦听器的问题。

玩得开心,詹斯。


1

由于对我无济于事,而且我的视线中有1个以上的微调器(而恕我直言,拿着bool地图实在是太过分了),因此我使用标记来计算点击次数:

spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            Integer selections = (Integer) parent.getTag();
            if (selections > 0) {
                // real selection
            }
            parent.setTag(++selections); // (or even just '1')
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });

1

已经有很多答案了,这是我的。

我扩展AppCompatSpinner并添加了一种方法pgmSetSelection(int pos),该方法允许以编程方式进行选择设置,而无需触发选择回调。我已经使用RxJava对此进行了编码,以便选择事件通过传递Observable

package com.controlj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;

import io.reactivex.Observable;

/**
 * Created by clyde on 22/11/17.
 */

public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
    private int lastSelection = INVALID_POSITION;


    public void pgmSetSelection(int i) {
        lastSelection = i;
        setSelection(i);
    }

    /**
     * Observe item selections within this spinner. Events will not be delivered if they were triggered
     * by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
     *
     * @return an Observable delivering selection events
     */
    public Observable<Integer> observeSelections() {
        return Observable.create(emitter -> {
            setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                    if(i != lastSelection) {
                        lastSelection = i;
                        emitter.onNext(i);
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapterView) {
                    onItemSelected(adapterView, null, INVALID_POSITION, 0);
                }
            });
        });
    }

    public FilteredSpinner(Context context) {
        super(context);
    }

    public FilteredSpinner(Context context, int mode) {
        super(context, mode);
    }

    public FilteredSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
        super(context, attrs, defStyleAttr, mode);
    }
}

用法示例,例如在onCreateView()中调用Fragment

    mySpinner = view.findViewById(R.id.history);
    mySpinner.observeSelections()
        .subscribe(this::setSelection);

setSelection()封闭视图中,哪里是一个看起来像这样的方法,该方法既可以通过的用户选择事件Observable也可以通过调用,也可以通过编程方式从其他位置调用,因此,处理选择的逻辑对于这两种选择方法都是通用的。

private void setSelection(int position) {
    if(adapter.isEmpty())
        position = INVALID_POSITION;
    else if(position >= adapter.getCount())
        position = adapter.getCount() - 1;
    MyData result = null;
    mySpinner.pgmSetSelection(position);
    if(position != INVALID_POSITION) {
        result = adapter.getItem(position);
    }
    display(result);  // show the selected item somewhere
}

0

我会尝试打电话

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

在调用setAdapter()之后。还可以尝试在适配器之前进行呼叫。

子类始终提供解决方案,您可以在其中将布尔标志包装到重写的setAdapter方法上以跳过事件。


0

带有布尔标志或计数器的解决方案对我没有帮助,因为在方向更改期间onItemSelected()会导致标志或计数器“飞越”。

我进行了分类,android.widget.Spinner并做了一些细微的补充。相关部分如下。这个解决方案对我有用。

private void setHandleOnItemSelected()
{
  final StackTraceElement [] elements = Thread.currentThread().getStackTrace();

  for (int index = 1; index < elements.length; index++)
  {
     handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$

     if (handleOnItemSelected)
     {
        break;
     }
  }
}

@Override
public void setSelection(int position, boolean animate)
{
  super.setSelection(position, animate);

  setHandleOnItemSelected();
}

@Override
public void setSelection(int position)
{
  super.setSelection(position);

  setHandleOnItemSelected();
}

public boolean shouldHandleOnItemSelected()
{
  return handleOnItemSelected;
}

0

这也不是一个优雅的解决方案。实际上,它确实是Rube-Goldberg,但它似乎有效。通过扩展阵列适配器并覆盖其getDropDownView,我确保微调器已至少使用一次。在新的getDropDownView方法中,我具有一个布尔标志,该标志设置为显示下拉菜单至少已使用一次。在设置该标志之前,我将忽略对侦听器的调用。

MainActivity.onCreate():

ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);

ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...

ArAd  abAdapt = new ArAd (this
   , android.R.layout.simple_list_item_1
   , android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);

覆盖数组适配器:

private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
    private ArAd(Activity a
            , int layoutId, int resId, ArrayList<String> list) {
        super(a, layoutId, resId, list);
        viewed = false;
    }
    @Override
    public View getDropDownView(int position, View convertView,
            ViewGroup parent) {
        viewed = true;
        return super.getDropDownView(position, convertView, parent);
    }
}

修改后的侦听器:

@Override
public boolean onNavigationItemSelected(
   int itemPosition, long itemId) {
   if (viewed) {
     ...
   }
   return false;
}


0

我用最简单的方法完成了:

private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;

onCreate();

spinner = (Spinner) findViewById(R.id.spinner);

listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {

            Log.i("H - Spinner selected position", position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    };

 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            spinner.setOnItemSelectedListener(listener);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

完成了


这是一个有趣的解决方案。可以使用更多的解释。基本上,它有意忽略第一个onItemSelected事件。在某些情况下,它们可能工作得很好,但在其他情况下,例如在启用了辅助功能选项时,效果就不好(请参阅Jorrit的说明)
jk7

0
if () {        
       spinner.setSelection(0);// No reaction to create spinner !!!
     } else {
        spinner.setSelection(intPosition);
     }


spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

         if (position > 0) {
           // real selection
         }

      }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

     }
});

0

那是我最终的,易于使用的解决方案:

public class ManualSelectedSpinner extends Spinner {
    //get a reference for the internal listener
    private OnItemSelectedListener mListener;

    public ManualSelectedSpinner(Context context) {
        super(context);
    }

    public ManualSelectedSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        mListener = listener;
        super.setOnItemSelectedListener(listener);
    }

    public void setSelectionWithoutInformListener(int position){
        super.setOnItemSelectedListener(null);
        super.setSelection(position);
        super.setOnItemSelectedListener(mListener);
    }

    public void setSelectionWithoutInformListener(int position, boolean animate){
        super.setOnItemSelectedListener(null);
        super.setSelection(position, animate);
        super.setOnItemSelectedListener(mListener);
    }
}

将默认值setSelection(...)用于默认行为,或者将其setSelectionWithoutInformListener(...)用于选择微调器中的项目,而无需触发OnItemSelectedListener回调。


0

我需要mSpinner在ViewHolder中使用,因此标志mOldPosition是在匿名内部类中设置的。

mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            int mOldPosition = mSpinner.getSelectedItemPosition();

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
                if (mOldPosition != position) {
                    mOldPosition = position;
                    //Do something
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
                //Do something
            }
        });

0

我将在创建onClickListener对象的过程中存储初始索引。

   int thisInitialIndex = 0;//change as needed

   myspinner.setSelection(thisInitialIndex);

   myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      int initIndex = thisInitialIndex;

      @Override
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
         if (id != initIndex) { //if selectedIndex is the same as initial value
            // your real onselecteditemchange event
         }
      }

      @Override
      public void onNothingSelected(AdapterView<?> parent) {
      }
  });

0

我的解决方案使用onTouchListener但不限制其使用。它会onTouchListener在需要安装的地方创建包装器onItemSelectedListener

public class Spinner extends android.widget.Spinner {
    /* ...constructors... */

    private OnTouchListener onTouchListener;
    private OnItemSelectedListener onItemSelectedListener;

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        onItemSelectedListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    @Override
    public void setOnTouchListener(OnTouchListener listener) {
        onTouchListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
        return onItemSelectedListener != null ? new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
                return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
            }
        } : onTouchListener;
    }
}

0

我可能在发布后回答得太晚了,但是我设法使用Android数据绑定库Android Databinding实现了这一点 。我创建了一个自定义绑定,以确保在更改选定的项目之前不调用侦听器,这样即使用户一次又一次地选择相同位置也不会触发事件。

布局xml文件

    <layout>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">


<Spinner
    android:id="@+id/spinner"
    android:layout_width="150dp"
    android:layout_height="wrap_content"
    android:spinnerMode="dropdown"
    android:layout_below="@id/member_img"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:background="@drawable/member_btn"
    android:padding="@dimen/activity_horizontal_margin"
    android:layout_marginStart="@dimen/activity_horizontal_margin"
    android:textColor="@color/colorAccent"
    app:position="@{0}"
    />
 </RelativeLayout>
 </layout>

app:position 是您要通过的位置。

自定义绑定

  @BindingAdapter(value={ "position"}, requireAll=false)
  public static void setSpinnerAdapter(Spinner spinner, int selected) 
  {

    final int [] selectedposition= new int[1];
    selectedposition[0]=selected;


    // custom adapter or you can set default adapter
        CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
        spinner.setAdapter(customSpinnerAdapter);
            spinner.setSelection(selected,false);


    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

            String item = parent.getItemAtPosition(position).toString();
        if( position!=selectedposition[0]) {
                        selectedposition[0]=position;
            // do your stuff here
                    }
                }


        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });
}

你可以阅读更多有关自定义数据绑定这里的Android定制的setter

注意

  1. 不要忘记在Gradle文件中启用数据绑定

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. <layout>标签中包含布局文件


-1
mYear.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View arg1, int item, long arg3) {
                if (mYearSpinnerAdapter.isEnabled(item)) {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

2
1)请正确格式化您的代码。2)对您的代码在做什么的解释也将不胜感激。读取代码时,并非所有代码片段都能立即理解。
Mike Koch 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.