ChatGPT解决这个技术问题 Extra ChatGPT

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

我想了一些不太优雅的方法来解决这个问题,但我知道我一定遗漏了一些东西。

我的 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.
    }
}
}
你可以看看这个解决方案,它简单实用。 stackoverflow.com/a/10102356/621951
一个简单的解决方案是让 Spinner 中的第一项为空,在 onItemSelected 中您可以检测字符串是否不为空,然后 startActivity

B
Brad

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

+1 隐藏的宝石!将 false 作为“animate”参数传递不会调用侦听器回调。惊人的!
+1 奇怪但优雅的解决方案 :) 幸运的是,无论如何我已经不得不调用 setSelection ...
组装 Spinner UI 元素时,侦听器仍会触发,因此无论哪个不会阻止 OP 描述的不需要的行为,它都会触发。如果没有在 onCreateView() 期间或之前声明,这非常有用,但这不是他们要求的。
有用,但解决了与 OP 提出的不同的问题。 OP 指的是一个选择事件(不幸的是)在视图首次出现时自动触发,即使程序员没有执行 setSelection。
setSelection(..) 方法中的“false”值参数是我的解决方案。泰!
R
Richard Le Mesurier

参考 Dan Dyer 的回答,尝试在 post(Runnable) 方法中注册 OnSelectListener

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

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

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


我收到一条错误消息:AdapterView 类型中的方法 setOnItemSelectedListener(AdapterView.OnItemSelectedListener) 不适用于参数 (new Runnable(){}) 为什么会这样?
这不是本质上在 Runnable 和 UI 线程之间设置竞争条件吗?
@theFunkyEngineer - 此代码应该从主线程方法之一运行,例如 onCreate()onResume() 等。在这种情况下,这是一个绝妙的技巧,没有竞争条件的危险。我通常在布局代码之后的 onCreate() 中使用这个技巧。
这是一个很好的解决方案,绝对不是黑客!这种功能是在框架深处完成事情的方式。很遗憾 Spinner 在内部没有这样做。但是,这是保证某些代码在 Activity 创建后运行的最干净的方法。这是因为当 Activity 尝试通知他们时,侦听器尚未在 Spinner 上设置。
这是一个可接受的解决方案。不是盲拍。其他解决方案在未来更容易出现行为改变问题。
C
CommonsWare

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

话虽如此,一个简单的布尔标志将允许您检测流氓优先选择事件并忽略它。


呃,是的。这就是我所说的不优雅的解决方案。似乎必须有更好的方法。不过还是谢谢你。
Dev ml 上的这个线程对此有更深入的了解:groups.google.com/group/android-developers/browse_thread/thread/… - 不幸的是,没有给出解决方案......
布局组件的过程会触发选择侦听器。因此,您必须在布局完成后添加侦听器。我一直无法找到一个合适的、直接的地方来执行此操作,因为布局似乎发生在 onResume()onPostResume() 之后的某个时间点,因此所有正常的挂钩在布局发生时都已完成。
我会远离这个布尔标志 - 好像未来的行为改变可能会导致错误。一个更防弹的解决方案是使用“当前选定的索引”保留一个变量,初始化为第一个选定的项目。然后在选择事件上 - 检查它是否等于新位置 - 返回并且什么都不做。当然更新选择的变量。
这不起作用。 @casanova 的回答有效。这应该是公认的答案。
D
David Manpearl

我创建了一个小的实用程序方法来更改 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,然后我将它们全部设置回它们应该是的值,但由于某种原因这不起作用。而是尝试了此功能,并且有效。我不知道为什么我的不起作用,但这有效,所以我不在乎:D
注意:如果您快速调用 setSpinnerSelectionWithoutCallingListener 两次,以便在第一个已将侦听器设置为 null 时进行第二次调用,您的微调器将永远被 null 侦听器卡住。我建议进行以下修复:在 spinner.setSelection(selection) 之后添加 if (listener == null) return;
J
Jorrit

不幸的是,对于这个问题,两个最常见的建议解决方案,即计算回调发生次数和发布一个 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(); }
}

这应该是投票率最高的答案。它简单而精彩。它允许您保持所有当前实现相同,除了您初始化的那一行。绝对使改造旧项目变得非常容易。最重要的是,我通过实现 OnTouchLisener 接口在微调器打开时关闭键盘,用一块石头杀死了两只鸟。现在我所有的微调器都按照我想要的方式运行。
美丽的答案。当我 addAll() 到适配器时,它仍然触发到第 0 个元素,但我的第 0 个元素是中性(什么都不做)行为的省略号。
C
Chris

当我不想这样做时,我遇到了很多关于旋转器射击的问题,而且这里的所有答案都是不可靠的。他们工作 - 但只是有时。您最终会遇到它们将失败并在您的代码中引入错误的情况。

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

//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,我不是 100% 确定你的意思。但每当 onItemSelected 被触发时,int i 将是微调器的位置。问题是,每当首次加载微调器时都会触发 onItemSelected,而没有任何实际的用户交互,在这种情况下会导致不需要的行为。 int i 在此初始点将等于 0,因为这是首次加载微调器时的默认起始索引。因此,我的解决方案会检查以确保选择了实际不同的项目,而不是重新选择当前选择的项目...这是否回答了您的问题?
嗨,克里斯,我有一个从数据库中提取信息供用户编辑的页面。当页面打开时,我填充微调器,然后将它们的位置设置为数据库中的值。因此,例如,如果我将它们的位置设置为 3,这会导致 onItemSelected 在 i 设置为 3 时触发,这与初始值不同。我在想你是说我只有在用户自己真正改变它的情况下才设置。
如果用户选择位置 0 怎么办?他们会被忽略。
我不认为最后的位置方式是个好主意。我通过从 SharedPreferences 加载位置并使用 setSelection 来初始化微调器。创建微调器时,SharedPrefs 中的值通常与默认值不同,因此 onItemSelected 将在启动时触发。
J
JASON G PETERSON

只是为了充实使用 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;

这很好用,从 Android 6+ 开始,这是唯一有效的方法。但是,您还必须对 setOnKeyListener() 执行相同操作,否则当用户使用键盘导航时它不起作用。
效果很好,所有其他解决方案在不同的手机上都有一些问题。
这很简单而且绝对完美!不需要多余的废话,只要记住逻辑即可。我很高兴我一直向下滚动到这里!
除了 setOnKeyListener(),您可以子类化 spinner 并在覆盖的 preformClick() 方法中设置标志 spinnerTouched = true,这在两种情况下都会调用(触摸/键)。休息也是一样。
只是想提一下,这似乎解决了与我最近在这里发布的 DropDownPreferences 相同的错误:stackoverflow.com/questions/61867118/… 我不能相信它tbh:D
M
Michal

我处于类似情况,我有一个简单的解决方案适合我。

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

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


更好的方法是不用担心对 onItemSelected 的额外调用,而是确保它显示正确的选择。因此,在添加侦听器之前调用 spinner.setSelection(selectedIndex) 使它对我来说始终如一。
微调器没有 setSelected(int position, boolean animate) 方法
您需要的实际调用是 setSelection(int position, boolean animate);
为你+1。当代码修改更多次时,这解决了一个更普遍的问题 Spinner 内容和选择保持 onItemSelected 仅用于用户交互
可悲的是,错误的动画标志仍然在 API23 中调用 onItemSelected
j
j2emanue
spinner.setSelection(Adapter.NO_SELECTION, false);

代码可能不言自明,但稍微解释一下就会有很长的路要走:)
U
Uzair

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

mSpinner.setSelection(0);

而不是上面的语句使用

mSpinner.setSelection(0,false);//只是简单地不为它设置动画。

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


这绝对解决了我的问题。我阅读了有关 Spinner 小部件的文档。要理解其中的区别绝对很棘手: setSelection(int position, boolean animate) -> 直接跳转到适配器数据中的特定项目。 setSelection(int position) -> 设置当前选中的项目。
f
fusion44

在拉了很长时间之后,我创建了自己的 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。


这对我不起作用,它仍然会触发 onItemSelected。
Arthez 请仔细检查您是否真的将 true 传递给第三个参数。如果是的话,这里还有其他问题。如果可能,请发布您的代码。
r
redocoder

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

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.removeGlobalOnLayoutListener 来删除 J 以下版本上的 ViewTreeObserver.OnGlobalLayoutListener,它已被弃用,并且与此答案使用的方法具有相似的名称。
J
Jeslin Jacob

我得到了一个非常简单的答案,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
}

g
g00dy

我找到了更优雅的解决方案。它涉及计算调用了多少次 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
}

S
SagiLow

由于没有什么对我有用,而且我认为我有超过 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) {
        }
    });

D
David Walton

我的小贡献是对上述一些内容的变体,它适合我几次。

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

如果用户再次选择相同的值,这具有不运行代码的额外优势。


J
Jens

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

下面是我的新“微调器代理”类:

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

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

玩得开心,詹斯。


C
Clyde

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

我扩展了 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
}

P
Pentium10

我会试着打电话

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

在你调用 setAdapter() 之后。也尝试在适配器之前调用。

您始终可以使用子类化解决方案,您可以在其中将布尔标志包装到覆盖的 setAdapter 方法以跳过事件。


R
RobinBobin

带有布尔标志或计数器的解决方案对我没有帮助,因为在方向更改期间 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;
}

s
steven smith

这也不是一个优雅的解决方案。事实上,它更像是 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;
}

C
Community

如果您需要即时重新创建活动,例如:更改主题,简单的标志/计数器将不起作用

使用 onUserInteraction() 函数检测用户活动,

参考:https://stackoverflow.com/a/25070696/4772917


H
Hiren Patel

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

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 事件。它们在某些情况下可能会很好地工作,但在其他情况下则不然,例如启用辅助功能选项时 (see Jorrit's explanation)
G
Gennady Kozlov
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) {

     }
});

M
MatPag

这是我最终且易于使用的解决方案:

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 回调。


F
Francis Bacon

我需要在 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
            }
        });

R
Ray Lionfang

我会在创建 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) {
      }
  });

D
Dem0n13

我的解决方案使用 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;
    }
}

N
N.Moudgil

我可能在帖子中回答得太晚了,但是我设法使用 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 } } 在 标签中包含你的布局文件


Y
Yotam Omer
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) {
            }
        });

1)请正确格式化您的代码。 2)您的代码正在做什么的解释也将不胜感激。阅读代码时,并非所有代码片段都能立即被理解。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅