如何在新实例化的Spinner上保持onItemSelected?

Fau*_*eal 405 android spinner android-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.
    }
}
}
Run Code Online (Sandbox Code Playgroud)

Bra*_*rad 365

Runnables的使用完全不正确.

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

这样,您可以在没有动画的情况下设置选择,从而调用on项目选定的侦听器.但是侦听器为null,因此无法运行.然后分配你的听众.

所以按照这个确切的顺序:

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

  • +1隐藏的宝石!将false作为"animate"参数传递不会调用侦听器回调.真棒! (45认同)
  • 当Spinner UI元素被组装时,侦听器仍然会触发,因此无论哪个不阻止OP描述的不需要的行为,它都会触发.如果没有在onCreateView()期间或之前声明,这可以很好地工作,但这不是他们要求的. (33认同)
  • 有用,但解决了与OP不同的问题.OP指的是一个选择事件(不幸的是)在视图首次出现时自动触发*即使程序员没有执行setSelection*. (6认同)
  • +1奇怪但优雅的解决方案:)幸运的是,我已经不得不调用setSelection了... (3认同)
  • setSelection(..)方法中的"false"值参数是我的解决方案.TY! (2认同)
  • 为setSelection(位置); 不起作用,你必须使用setSelection(position,false); 使它工作 (2认同)

小智 191

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

spinner.post(new Runnable() {
    public void run() {
        spinner.setOnItemSelectedListener(listener);
    }
});
Run Code Online (Sandbox Code Playgroud)

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

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

  • @theFunkyEngineer - 这个代码*应该从一个主要的线程方法运行,例如`onCreate()`,`onResume()`等.在这种情况下,它是一个奇妙的技巧,没有竞争条件的危险.我通常在布局代码之后的`onCreate()`中使用这个技巧. (5认同)

Com*_*are 75

我希望你的解决方案能够正常工作 - 如果你在设置监听器之前设置了适配器,那么选择事件就不会激活.

话虽这么说,一个简单的布尔标志将允许您检测恶意第一选择事件并忽略它.

  • 我会远离这个布尔标志 - 好像将来行为发生变化可能会导致错误.更加防弹的解决方案是使用"当前选定的索引"保留变量,初始化为所选的第一个项目.然后在选择事件 - 检查它是否等于新位置 - 返回并且什么都不做.当然在选择时更新变量. (27认同)
  • 布局组件的过程激发了选择监听器.因此,您必须在布局完成后添加侦听器*.我一直无法找到一个合适的,直截了当的地方,因为布局似乎在`onResume()`和`onPostResume()`之后的某个时刻发生,所以所有正常的挂钩都在布局发生时完成. (24认同)
  • 呃,是的 这是一个不优雅的解决方案我的意思.似乎必须有更好的方法.谢谢你. (14认同)
  • Dev ml上的这个帖子对此有更深入的了解:http://groups.google.com/group/android-developers/browse_thread/thread/d93ce1ef583a2a29 - 遗憾的是没有给出解决方案...... (5认同)
  • 这不起作用.回答@casanova的作品.那应该是公认的答案. (2认同)

小智 47

我创建了一个小实用程序方法来更改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);
                }
            });
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

它会禁用侦听器,更改选择,然后重新启用侦听器.

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

  • 值得注意的是:如果你快速调用`setSpinnerSelectionWithoutCallingListener`两次,那么第二次调用是在第一次调用已经将侦听器设置为`null`时,你的微调器将永远停留在一个'null`监听器上.我提出了以下修复:在`spinner.setSelection(selection)`之后添加`if(listener == null)return;`. (4认同)

Jor*_*rit 33

不幸的是,似乎这个问题的两个最常见的建议解决方案,即计算回调事件和发布Runnable以便稍后设置回调,例如在启用可访问性选项时都会失败.这是一个解决这些问题的助手类.进一步的explenation在注释块中.

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(); }
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是最高投票的答案.它简单而又精彩.它允许您保持所有当前实现相同,除了初始化的一行.绝对适合旧式项目,非常容易.最重要的是,我通过实现OnTouchLisener接口在spinner打开时关闭键盘,一举两得.现在,我的所有旋转器都按照我想要的方式运行. (3认同)

Chr*_*ris 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

}
Run Code Online (Sandbox Code Playgroud)

相信我这样说,这是迄今为止最可靠的解决方案.一个黑客,但它的工作原理!

  • 如果用户选择位置0怎么办?他们会被忽略. (3认同)

Mic*_*hal 25

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

它看起来像方法setSelection(int position)并且setSelected(int position, boolean animate)具有不同的内部实现.

当您使用setSelected(int position, boolean animate)带有false animate标志的第二种方法时,您可以在不触发onItemSelected侦听器的情况下进行选择.

  • 你需要的实际调用是`setSelection(int position,boolean animate);` (4认同)
  • 遗憾的是,假动画标志仍然在API23中调用`onItemSelected` (4认同)

JAS*_*SON 21

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

只需为您的Activity/Fragment设置一个布尔字段,如:

private Boolean spinnerTouched = false;
Run Code Online (Sandbox Code Playgroud)

然后在设置微调器的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;
Run Code Online (Sandbox Code Playgroud)


j2e*_*nue 13

spinner.setSelection(Adapter.NO_SELECTION, false);
Run Code Online (Sandbox Code Playgroud)

  • 代码可能不言而喻,但有一点解释很长的路要走:) (2认同)

fus*_*n44 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样在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" />
Run Code Online (Sandbox Code Playgroud)

您需要做的就是在充气后调用SaneSpinner的实例并调用集合选择,如下所示:

mMySaneSpinner.setSelection(1, true, true);
Run Code Online (Sandbox Code Playgroud)

这样,就不会触发任何事件,也不会中断用户交互.这大大降低了我的代码复杂性.这应该包含在现货Android中,因为它确实是PITA.


red*_*der 7

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

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) {
                }

            });

        }
    });
Run Code Online (Sandbox Code Playgroud)


小智 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
}
Run Code Online (Sandbox Code Playgroud)


Uza*_*air 5

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

   mSpinner.setSelection(0);
Run Code Online (Sandbox Code Playgroud)

代替上述声明使用

   mSpinner.setSelection(0,false);//just simply do not animate it.
Run Code Online (Sandbox Code Playgroud)

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

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