Android 如何防止 onItemSelected 在新实例化的 Spinner 上触发?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2562248/
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
How to keep onItemSelected from firing off on a newly instantiated Spinner?
提问by FauxReal
I've thought of some less than elegant ways to solve this, but I know I must be missing something.
我已经想到了一些不太优雅的方法来解决这个问题,但我知道我一定遗漏了一些东西。
My onItemSelected
fires off immediately without any interaction with the user, and this is undesired behavior. I wish for the UI to wait until the user selects something before it does anything.
我onItemSelected
在没有与用户任何交互的情况下立即触发,这是不受欢迎的行为。我希望 UI 在执行任何操作之前等待用户选择某些内容。
I even tried setting up the listener in the onResume()
, hoping that would help, but it doesn't.
我什至尝试在 中设置侦听器onResume()
,希望这会有所帮助,但没有。
How can I stop this from firing off before the user can touch the control?
如何在用户触摸控件之前阻止它触发?
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.
}
}
}
采纳答案by CommonsWare
I would have expected your solution to work -- I though the selection event would not fire if you set the adapter before setting up the listener.
我本来希望您的解决方案起作用 - 我虽然如果您在设置侦听器之前设置适配器,则选择事件不会触发。
That being said, a simple boolean flag would allow you to detect the rogue first selection event and ignore it.
话虽如此,一个简单的布尔标志将允许您检测流氓第一选择事件并忽略它。
回答by Brad
The use of Runnables is completely incorrect.
Runnables 的使用是完全错误的。
Use setSelection(position, false);
in the initial selection before setOnItemSelectedListener(listener)
使用setSelection(position, false);
在之前的初始选择setOnItemSelectedListener(listener)
This way you set your selection with no animation which causes the on item selected listener to be called. But the listener is null so nothing is run. Then your listener is assigned.
通过这种方式,您可以设置没有动画的选择,这会导致调用 on item selected 侦听器。但是侦听器为空,因此没有运行任何内容。然后你的听众被分配。
So follow this exact sequence:
因此,请遵循以下确切顺序:
Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
回答by casaflowa
Referring to the answer of Dan Dyer, try to register the OnSelectListener
in a post(Runnable)
method:
参考Dan Dyer的回答,尝试OnSelectListener
在一个post(Runnable)
方法中注册:
spinner.post(new Runnable() {
public void run() {
spinner.setOnItemSelectedListener(listener);
}
});
By doing that for me the wished behavior finally occurred.
通过为我这样做,我希望的行为终于发生了。
In this case it also means that the listener only fires on a changed item.
在这种情况下,这也意味着侦听器仅在更改的项目上触发。
回答by karooolek
I created a small utility method for changing Spinner
selection without notifying the user:
我创建了一个小的实用方法,用于在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);
}
});
}
});
}
It disables the listener, changes the selection, and re-enables the listener after that.
它禁用侦听器,更改选择,然后重新启用侦听器。
The trick is that calls are asynchronous to the UI thread, so you have to do it in consecutive handler posts.
诀窍是调用与 UI 线程异步,因此您必须在连续的处理程序帖子中执行此操作。
回答by Jorrit
Unfortunately it seems that the two most commonly suggested solutions to this issue, namely counting callback occurrences and posting a Runnable to set the callback at a later time can both fail when for example accessibility options are enabled. Here's a helper class that works around these issues. Further explenation is in the comment block.
不幸的是,对于这个问题,两个最常见的建议解决方案,即计算回调发生次数和发布 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(); }
}
回答by Chris
I have had LOTS of issues with the spinner firing of when I didn't want to, and all the answers here are unreliable. They work - but only sometimes. You will eventually run into scenarios where they will fail and introduce bugs into your code.
当我不想时,我遇到了很多关于微调器点火的问题,这里的所有答案都不可靠。他们工作 - 但只是有时。您最终会遇到它们会失败并将错误引入您的代码的情况。
What worked for me was to store the last selected index in a variable and evaluate it in the listener. If it is the same as the new selected index do nothing and return, else continue with the listener. Do this:
对我有用的是将最后选择的索引存储在变量中并在侦听器中对其进行评估。如果与新选择的索引相同,则什么都不做并返回,否则继续侦听器。做这个:
//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
}
Trust me when I say this, this is by far the most reliable solution. A hack, but it works!
相信我,这是迄今为止最可靠的解决方案。一个黑客,但它的工作原理!
回答by Michal
I was in similar situation, and I have a simple solution working for me.
我处于类似的情况,我有一个简单的解决方案对我有用。
It seems like methods setSelection(int position)
and setSelected(int position, boolean animate)
have different internal implementation.
它看起来像方法setSelection(int position)
并且setSelected(int position, boolean animate)
有不同的内部实现。
When you use the second method setSelected(int position, boolean animate)
with false animate flag, you get the selection without firing onItemSelected
listener.
当您使用setSelected(int position, boolean animate)
带有 false animate 标志的第二种方法时,您可以在不触发onItemSelected
侦听器的情况下获得选择。
回答by JASON G PETERSON
Just to flesh out hints at using the onTouchListener to distinguish between automatic calls to the setOnItemSelectedListener (which are part of Activity initialization, etc.) vs. calls to it triggered by actual user interaction, I did the following after trying some other suggestions here and found that it worked well with the fewest lines of code.
只是为了充实使用 onTouchListener 来区分对 setOnItemSelectedListener(这是活动初始化等的一部分)的自动调用与由实际用户交互触发的对它的调用的提示,我在这里尝试了其他一些建议后做了以下操作发现它用最少的代码行就可以很好地工作。
Just set an Boolean field for your Activity/Fragment like:
只需为您的 Activity/Fragment 设置一个布尔字段,例如:
private Boolean spinnerTouched = false;
Then just before you set your spinner's setOnItemSelectedListener, set an onTouchListener:
然后就在您设置微调器的 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;
回答by j2emanue
spinner.setSelection(Adapter.NO_SELECTION, false);
回答by fusion44
After pulling my hair out for a long time now I've created my own Spinner class. I've added a method to it which disconnects and connects the listener appropriately.
在拉我的头发很长一段时间之后,我已经创建了自己的 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);
}
}
}
Use it in your XML like this:
在您的 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" />
All you have to do is retrieve the instance of SaneSpinner after inflation and call set selection like this:
您所要做的就是在膨胀后检索 SaneSpinner 的实例并像这样调用集合选择:
mMySaneSpinner.setSelection(1, true, true);
With this, no event is fired and user interaction is not interrupted. This reduced my code complexity a lot. This should be included in stock Android since it really is a PITA.
这样,不会触发任何事件并且不会中断用户交互。这大大降低了我的代码复杂性。这应该包含在库存 Android 中,因为它确实是一个 PITA。