我想了一些不太优雅的方法来解决这个问题,但我知道我一定遗漏了一些东西。
我的 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.
}
}
}
Spinner
中的第一项为空,在 onItemSelected
中您可以检测字符串是否不为空,然后 startActivity
!
Runnables 的使用是完全不正确的。
在 setOnItemSelectedListener(listener)
之前的初始选择中使用 setSelection(position, false);
通过这种方式,您可以在没有动画的情况下设置您的选择,这会导致调用 on item selected 侦听器。但是侦听器为空,因此没有运行任何内容。然后分配您的听众。
所以遵循这个确切的顺序:
Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
参考 Dan Dyer 的回答,尝试在 post(Runnable)
方法中注册 OnSelectListener
:
spinner.post(new Runnable() {
public void run() {
spinner.setOnItemSelectedListener(listener);
}
});
通过为我这样做,最终发生了希望的行为。
在这种情况下,这也意味着侦听器仅在更改的项目上触发。
onCreate()
、onResume()
等。在这种情况下,这是一个绝妙的技巧,没有竞争条件的危险。我通常在布局代码之后的 onCreate()
中使用这个技巧。
我本来希望您的解决方案能够正常工作-尽管如果您在设置侦听器之前设置适配器,则不会触发选择事件。
话虽如此,一个简单的布尔标志将允许您检测流氓优先选择事件并忽略它。
onResume()
和 onPostResume()
之后的某个时间点,因此所有正常的挂钩在布局发生时都已完成。
我创建了一个小的实用程序方法来更改 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 线程是异步的,因此您必须在连续的处理程序帖子中执行此操作。
setSpinnerSelectionWithoutCallingListener
两次,以便在第一个已将侦听器设置为 null
时进行第二次调用,您的微调器将永远被 null
侦听器卡住。我建议进行以下修复:在 spinner.setSelection(selection)
之后添加 if (listener == null) return;
。
不幸的是,对于这个问题,两个最常见的建议解决方案,即计算回调发生次数和发布一个 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(); }
}
当我不想这样做时,我遇到了很多关于旋转器射击的问题,而且这里的所有答案都是不可靠的。他们工作 - 但只是有时。您最终会遇到它们将失败并在您的代码中引入错误的情况。
对我有用的是将最后选择的索引存储在变量中并在侦听器中对其进行评估。如果它与新选择的索引相同,则不执行任何操作并返回,否则继续侦听器。做这个:
//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
}
当我这么说的时候相信我,这是迄今为止最可靠的解决方案。一个黑客,但它的工作原理!
只是为了充实使用 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;
我处于类似情况,我有一个简单的解决方案适合我。
似乎方法 setSelection(int position)
和 setSelected(int position, boolean animate)
具有不同的内部实现。
当您使用带有 false animate 标志的第二种方法 setSelected(int position, boolean animate)
时,您无需触发 onItemSelected
侦听器即可获得选择。
setSelection(int position, boolean animate);
onItemSelected
spinner.setSelection(Adapter.NO_SELECTION, false);
如果您在代码中进行选择,则会发生这种情况;
mSpinner.setSelection(0);
而不是上面的语句使用
mSpinner.setSelection(0,false);//只是简单地不为它设置动画。
编辑:此方法不适用于 Mi Android 版 Mi UI。
在拉了很长时间之后,我创建了自己的 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);
这样,不会触发任何事件,也不会中断用户交互。这大大降低了我的代码复杂性。这应该包含在库存 Android 中,因为它确实是 PITA。
如果您推迟添加侦听器直到布局完成,则布局阶段不会出现不需要的事件:
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) {
}
});
}
});
ViewTreeObserver.removeGlobalOnLayoutListener
来删除 J 以下版本上的 ViewTreeObserver.OnGlobalLayoutListener
,它已被弃用,并且与此答案使用的方法具有相似的名称。
我得到了一个非常简单的答案,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
}
我找到了更优雅的解决方案。它涉及计算调用了多少次 ArrayAdapter(在您的情况下为“适配器”)。假设您有 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”条件来检查适配器被调用了多少次。在您的情况下,您只需调用一次:
if(iCountAdapterCalls < 1)
{
iCountAdapterCalls++;
//This section executes in onCreate, during the initialization
}
else
{
//This section corresponds to user clicks, after the initialization
}
由于没有什么对我有用,而且我认为我有超过 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) {
}
});
我的小贡献是对上述一些内容的变体,它适合我几次。
将整数变量声明为默认值(或保存在首选项中的最后使用值)。使用 spinner.setSelection(myDefault) 在注册侦听器之前设置该值。在 onItemSelected 检查新的微调器值是否等于您在运行任何其他代码之前分配的值。
如果用户再次选择相同的值,这具有不运行代码的额外优势。
在遇到同样的问题后,我使用标签来解决这个问题。它背后的想法很简单:每当以编程方式更改微调器时,请确保标记反映所选位置。然后在侦听器中检查所选位置是否等于标签。如果是这样,则微调器选择已以编程方式更改。
下面是我的新“微调器代理”类:
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
。
此包装器还将处理在布局创建期间调用侦听器的问题。
玩得开心,詹斯。
已经有很多答案了,这是我的。
我扩展了 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);
}
}
其用法示例,在 Fragment
中的 onCreateView()
中调用,例如:
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
}
我会试着打电话
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
在你调用 setAdapter() 之后。也尝试在适配器之前调用。
您始终可以使用子类化解决方案,您可以在其中将布尔标志包装到覆盖的 setAdapter 方法以跳过事件。
带有布尔标志或计数器的解决方案对我没有帮助,因为在方向更改期间 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;
}
这也不是一个优雅的解决方案。事实上,它更像是 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;
}
如果您需要即时重新创建活动,例如:更改主题,简单的标志/计数器将不起作用
使用 onUserInteraction() 函数检测用户活动,
参考:https://stackoverflow.com/a/25070696/4772917
我用最简单的方法完成了:
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) {
}
});
完毕
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) {
}
});
这是我最终且易于使用的解决方案:
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 回调。
我需要在 ViewHolder 中使用 mSpinner
,因此在匿名内部类中设置了标志 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
}
});
我会在创建 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) {
}
});
我的解决方案使用 onTouchListener
,但不限制其使用。如有必要,它会在设置 onItemSelectedListener
处为 onTouchListener
创建一个包装器。
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;
}
}
我可能在帖子中回答得太晚了,但是我设法使用 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 Custom Setter
笔记
不要忘记在你的 Gradle 文件中启用数据绑定 android { .... dataBinding { enabled = true } } 在
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) {
}
});