新しくインスタンス化されたスピナーでonItemSelectedが起動しないようにするにはどうすればよいですか?


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
GünayGültekin

1
簡単な解決策は、最初のアイテムをSpinner空にして、内部でonItemSelected文字列が空でないかどうかを検出することstartActivityです!
Muhammad Babar 2014

このパターンは、正常に動作stackoverflow.com/questions/13397933/...
saksham

回答:


78

私はあなたの解決策が機能することを期待していました-リスナーを設定する前にアダプターを設定した場合、選択イベントは発生しませんが。

そうは言っても、単純なブールフラグを使用すると、不正な最初の選択イベントを検出して無視できます。


15
うん、うん。それが、私が無意味な解決策によって意味したことです。もっと良い方法があるに違いないようです。ありがとう、結構です。
FauxReal

5
Dev mlのこのスレッドは、これについてより深い洞察を持っています:groups.google.com/group/android-developers/browse_thread/thread/…-残念ながら解決策はありません...
BoD

25
コンポーネントをレイアウトするプロセスは、選択リスナーを起動します。したがって、レイアウトが完了した後でリスナーを追加する必要があります。レイアウトは後のある時点で発生するようですように私はこれを行うに適した、簡単な場所を見つけることができなかったonResume()onPostResume()、通常のフックのすべてのレイアウトが起こる時までに完了しているので、。
Dan Dyer、

28
私はこのブール値のフラグから離れたままにします-将来の動作の変更がバグを引き起こす可能性があるかのように。より防弾対策としては、「現在選択されているインデックス」で変数を保持し、最初に選択された項目に初期化します。次に、選択イベントで-新しい位置と等しいかどうかを確認します-戻り、何もしません。もちろん、選択時に変数を更新します。
daniel.gindi 2013

2
これは動作しません。@casanovaによる回答が機能します。それが受け入れられる答えになるはずです。
Siddharth

379

Runnablesの使用は完全に正しくありません。

setSelection(position, false);前の初期選択で使用setOnItemSelectedListener(listener)

このようにして、アニメーションなしで選択を設定すると、on item selectedリスナーが呼び出されます。しかし、リスナーは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隠された宝石!「animate」パラメータとしてfalseを渡しても、リスナーコールバックは呼び出されません。驚くばかり!
2014

3
+1奇妙ですがエレガントな解決策:)幸いにも、とにかく既にsetSelectionを呼び出す必要がありました...
Martin T. 14

35
リスナーは、Spinner UI要素がアセンブルされたときにも起動します。そのため、OPで記述されている望ましくない動作を妨げないかどうかに関係なく起動します。これは、onCreateView()の実行中または実行前に宣言されていなくてもうまく機能しますが、それは彼らが要求したものではありません。
Rudi Kershaw 2014

6
便利ですが、OPとは異なる問題を解決します。OPは、プログラマーがsetSelectionを実行しなかった場合でも、ビューが最初に表示されたときに(残念ながら)自動的に起動する選択イベントを指します
ToolmakerSteve

2
setSelection(..)メソッドの「false」値パラメーターが私にとっての解決策でした。ty!
ダニ

195

Dan Dyerの回答を参考にOnSelectListenerして、post(Runnable)メソッドにを登録してみてください。

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

そうすることで、ついに希望の行動が起こりました。

この場合、変更されたアイテムでのみリスナーが発生することも意味します。


1
エラーメッセージが表示されます。タイプAdapterView <SpinnerAdapter>のメソッドsetOnItemSelectedListener(AdapterView.OnItemSelectedListener)は、引数(新しいRunnable(){})に適用できないのはなぜですか?
Jakob

これは本質的にRunnableとUIスレッドの間に競合状態を設定しているのではないですか?
kenny_k 2013年

6
@theFunkyEngineer -このコードは、必要があり、メインスレッド法などの1から実行するonCreate()onResume()競合状態の危険性がないと、その場合など、その素晴らしいトリック。私は通常onCreate()、レイアウトコードの直後にこのトリックを使用します。
Richard Le Mesurier 2013

1
これは素晴らしいソリューションであり、間違いなくハックではありません!この種の機能は、フレームワークの奥深くで行われる方法です。Spinnerが内部でこれを行わないのは残念です。ただし、これは、アクティビティの作成後に一部のコードの実行が保証される最もクリーンな方法です。これは、アクティビティが通知しようとしたときにリスナーがまだスピナーに設定されていないため機能します。
jophde 2014年

1
これは許容できるソリューションです。ブラインドショットではありません。他の解決策は、将来、行動変化の問題を起こしやすくなります。
クルディープシンダッカ2016

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に設定してみましたが、すべてのリスナーを本来の設定に戻しましたが、何らかの理由で機能しませんでした。代わりにこの関数を試してみましたが、うまくいきました。私がなぜうまくいかなかったのかはわかりませんが、これはうまくいくので気にしません:D
JStephen

4
注:setSpinnerSelectionWithoutCallingListener2回の呼び出しをすばやく行うと、最初の呼び出しでリスナーが既にに設定されている間に2番目の呼び出しが行われるnullと、スピナーがnullリスナーにずっとスタックし続けます。私は次の修正を提案します:のif (listener == null) return;後に追加しspinner.setSelection(selection)ます。
バイオレットキリン2018

34

残念ながら、この問題に対して最も一般的に提案されている2つの解決策、つまりコールバックの発生をカウントすることと、後でコールバックを設定するために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
これが最も高い投票数の回答になるはずです。シンプルですが見事です。これにより、初期化する1行を除いて、現在のすべての実装を同じに保つことができます。古いプロジェクトのレトロフィットを非常に簡単に間違いなく行いました。その上で、スピナーが開いたときにキーボードを閉じるためのOnTouchLisenerインターフェースを実装することで、1石で2羽の鳥を殺しました。これで、すべてのスピナーが思い通りに動作します。
user3829751

美しい答え。それでも、アダプターにaddAll()を実行すると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

}

私がこれを言うとき私を信じてください、これは断然最も信頼できる解決策です。ハックですが、うまくいきます!


値を変更しようとしている場合でもこれは機能しますか?私の場合、変更リスナーをトリガーせずに実際に0であるときに値を3などに設定しようとしています。int iは、ユーザーが選択している場合にのみ異なる値を返すと言っていますか?
JStephen 2015

こんにちはJStephen、私はあなたが何を意味するのか100%わかりません。しかし、int iは、onItemSelectedがトリガーされるたびにスピナーの位置になります。問題は、実際のユーザー操作なしで、スピナーが最初にロードされるたびにonItemSelectedがトリガーされるため、この場合は望ましくない動作が発生することです。これはスピナーが最初にロードされたときのデフォルトの開始インデックスであるため、int iはこの初期点で0に等しくなります。したがって、私のソリューションは、現在選択されているアイテムが再選択されるのではなく、実際の異なるアイテムが選択されていることを確認します...これはあなたの質問に答えますか?
Chris

こんにちはクリス、ユーザーが編集できるようにデータベースから情報を取得するページがあります。ページが開いたら、スピナーにデータを入力し、その位置をデータベース内にあった値に設定します。たとえば、位置を3に設定すると、onItemSelectedは、初期値とは異なる3に設定してトリガーされます。ユーザーが実際に自分で変更した場合にのみ設定されると言っていたと思っていました。
JStephen

4
ユーザーが位置0を選択するとどうなりますか?それらは無視されます。
Yetti99

私は最後の位置の方法は良い考えではないと思います。SharedPreferencesから位置を読み込み、setSelectionを使用してスピナーを初期化します。多くの場合、SharedPrefsの値は、スピナーが作成されるときのデフォルト値と同じではないため、開始時にonItemSelectedがトリガーされます。
Arthez

26

私は同じような状況にあり、私のために働く簡単な解決策があります。

メソッドのようでsetSelection(int position)setSelected(int position, boolean animate)内部実装が異なります。

setSelected(int position, boolean animate)falseのアニメーションフラグを指定して2番目のメソッドを使用すると、onItemSelectedリスナーを起動せずに選択を取得できます。


より良い方法は、onItemSelectedへの余分な呼び出しを心配するのではなく、正しい選択が表示されることを確認することです。そのため、リスナーを追加する前にspinner.setSelection(selectedIndex)を呼び出すと、一貫して動作します。
2013年

1
スピナーにはsetSelected(int position、boolean animate)メソッドはありません
shift66

4
あなたが必要とする実際の通話はsetSelection(int position, boolean animate);
Brad

+1してください。これにより、コードが何度も変更されるときのより一般的な問題が解決されます。Spinnerコンテンツと選択は、ユーザーインタラクションに対してのみonItemSelectedを維持します
アラマ

4
悲しいことに、誤ったアニメーションフラグonItemSelectedがAPI23で引き続き呼び出される
mcy

23

onTouchListenerを使用してsetOnItemSelectedListenerへの自動呼び出し(アクティビティの初期化などの一部)と実際のユーザー操作によってトリガーされた呼び出しを区別するためのヒントを具体化するために、ここで他のいくつかの提案を試した後、次のことを行いました。コードの最も少ない行でうまく機能することがわかりました。

次のように、アクティビティ/フラグメントのブールフィールドを設定するだけです。

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()でも同じことを行う必要があります。そうしないと、ユーザーがキーボードで移動したときに機能しません。
ステファン・

他のすべてのソリューションは、さまざまな電話で問題があります。
Ziwei Zeng 2017

これはシンプルで完全に完璧です!余分なナンセンスは必要ありません。ロジックを覚えておいてください。ここまで下までスクロールしてよかったです。
user3833732 2018

setOnKeyListener()の代わりに、スピナーをサブクラス化して、両方のケース(タッチ/キー)で呼び出されるオーバーライドされたpreformClick()メソッドでフラグspinnerTouched = trueを設定できます。残りは同じです。
全能の

ただ、私は最近、ここに掲載DropDownPreferencesと同じバグを解決するために、この表示され言及したかった:stackoverflow.com/questions/61867118/...私は*弄ぶ天使fはTBHそれを信じることができない:D
ダニエル・ウィルソン

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

3
コードはそれ自体で説明できるかもしれませんが、少しの説明は長い道のりになります:)
nhaarman

8

長い間髪を抜いた後、自分のスピナークラスを作成しました。リスナーを適切に切断および接続するメソッドをそれに追加しました。

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が3番目の引数に本当にtrueを渡しているかどうかを再確認してください。はいの場合、ここで他の何かが間違っています。可能であれば、コードを投稿してください。
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呼び出すことでViewTreeObserver.removeGlobalOnLayoutListener、J未満のバージョンのon を削除できることに注意したいと思います。
ジャックマイスター

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(この場合は「アダプター」)が呼び出された回数のカウントが含まれます。スピナーが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の後にinItemカウンターを宣言し、onItemSelected()メソッド内で「if」条件を設定して、アダプターが呼び出された回数を確認します。あなたの場合、それは一度だけ呼び出されます:

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.
}

また、Valuesディレクトリにタグが設定されたXMLファイルが必要です。ファイルに名前を付けました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を返します。この関数には副作用があることに注意してください-タグを設定するため、同じリスナー呼び出しの2番目の呼び出しは常に戻りますfalseます。

このラッパーは、レイアウトの作成中に呼び出されるリスナーの問題も処理します。

イェンス、楽しんでください。


1

私には何もうまくいかず、ビューに複数のスピナーがあるので(ブールマップを保持しているIMHOはやりすぎです)、タグを使用してクリック数をカウントします。

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

where 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をオーバーライドすることにより、スピナーが少なくとも1回は使用されていることを確認します。新しいgetDropDownViewメソッドには、ドロップダウンメニューが少なくとも1回使用されたことを示すように設定されたブールフラグがあります。フラグが設定されるまで、リスナーへの呼び出しは無視します。

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

オンザフライでアクティビティを再作成する必要がある場合(例:テーマの変更)、単純なフラグ/カウンターは機能しません

onUserInteraction()関数を使用してユーザーアクティビティを検出します。

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


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 Custom 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.