web-dev-qa-db-ja.com

Androidスピナーの選択

OnItemSelectedListenerイベントハンドラーは、スピナーの選択がプログラムで変更されたときと、ユーザーがスピナーコントロールを物理的にクリックしたときの両方で呼び出されます。イベントがユーザーの選択によってトリガーされたかどうかを何らかの方法で判断することは可能ですか?

または、スピナーユーザーの選択を処理する別の方法はありますか?

23
Arutha

回避策を実行するには、最後に選択した位置を覚えておく必要があります。次に、スピナーリスナーの内部で、最後に選択した位置を新しい位置と比較します。それらが異なる場合は、イベントを処理し、最後に選択した位置を新しい位置値で更新します。それ以外の場合は、イベント処理をスキップします。

コード内のどこかでスピナーの選択位置をプログラムで変更し、リスナーにイベントを処理させたくない場合は、最後に選択した位置を設定する位置にリセットするだけです。

はい、Androidのスピナーは痛いです。痛みはその名前から始まると言ってもいいでしょう-「スピナー」。少し誤解を招きませんか?:)私たちが話している限りではまた、バグがあることにも注意する必要があります。Spinnerは(常にではない)状態を(デバイスの回転時に)復元しない場合があるため、Spinnerの状態を手動で処理するようにしてください。

49
Vit Khudenko

1年半後、問題はまだ存在し、人々を困惑させ続けているとは信じがたいです...

Arhimedの最も有用な投稿を読んだ後に思いついた回避策を共有したいと思いました(ありがとう、そしてスピナーが苦痛であることに同意します!)。これらの誤検知を回避するために私が行ってきたことは、単純なラッパークラスを使用することです。

import Android.util.Log;
import Android.view.View;
import Android.widget.AdapterView;
import Android.widget.AdapterView.OnItemSelectedListener;

public class OnItemSelectedListenerWrapper implements OnItemSelectedListener {

    private int lastPosition;
    private OnItemSelectedListener listener;

    public OnItemSelectedListenerWrapper(OnItemSelectedListener aListener) {
        lastPosition = 0;
        listener = aListener;
    }

    @Override
    public void onItemSelected(AdapterView<?> aParentView, View aView, int aPosition, long anId) {
        if (lastPosition == aPosition) {
            Log.d(getClass().getName(), "Ignoring onItemSelected for same position: " + aPosition);
        } else {
            Log.d(getClass().getName(), "Passing on onItemSelected for different position: " + aPosition);
            listener.onItemSelected(aParentView, aView, aPosition, anId);
        }
        lastPosition = aPosition;
    }

    @Override
    public void onNothingSelected(AdapterView<?> aParentView) {
        listener.onNothingSelected(aParentView);
    }
}

すでに選択されているのと同じ位置のアイテム選択イベントをトラップし(たとえば、位置0の最初の自動的にトリガーされた選択)、ラップされたリスナーに他のイベントを渡すだけです。これを使用するには、リスナーを呼び出すコード内の行を変更してラッパーを含める(そしてもちろん終了ブラケットを追加する)だけなので、次のように言うのではありません。

mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    ...
});

あなたはこれを持っているでしょう:

mySpinner.setOnItemSelectedListener(new OnItemSelectedListenerWrapper(new OnItemSelectedListener() {
    ...
}));

明らかに、テストしたら、ログ呼び出しを取り除くことができ、必要に応じて最後の位置をリセットする機能を追加できます(もちろん、宣言するのではなく、インスタンスへの参照を保持する必要があります-the-fly)アルキメデスが言ったように。

これが誰かがこの奇妙な行動に夢中になるのを助けることができることを願っています;-)

20

私は最近スピナーを使用しているときにこの状況になり、インターネットは適切な解決策を思い付きませんでした。

私のアプリケーションシナリオ:

CPU周波数を設定および表示するためのXスピナー(動的に、CPUごとに2つ、最小および最大)。これらはアプリケーションの起動時に入力され、CPUセットの現在の最大/最小周波数も取得します。スレッドはバックグラウンドで実行され、毎秒変更をチェックし、それに応じてスピナーを更新します。スピナー内の新しい周波数がユーザーによって設定された場合、新しい周波数が設定されます。

問題は、スレッドがsetSelectionにアクセスして現在の頻度を更新し、それがリスナーを呼び出し、値を変更したのがユーザーなのかスレッドなのかを知る方法がなかったことです。スレッドの場合、頻度を変更する必要がなかったので、リスナーを呼び出さないようにしました。

私は自分のニーズに完全に適合し、あなたの電話でリスナーを回避するソリューションを思いつきました:)(そしてこのソリューションはあなたに最大限のコントロールを与えると思います)

スピナーを拡張しました:

_import Android.content.Context;
import Android.widget.Spinner;

public class MySpinner extends Spinner {
    private boolean call_listener = true;

    public MySpinner(Context context) {
        super(context);
    }

    public boolean getCallListener() {
        return call_listener;
    }

    public void setCallListener(boolean b) {
        call_listener = b;
    }

    @Override
    public void setSelection(int position, boolean lswitch) {
        super.setSelection(position);
        call_listener = lswitch;
    }
}
_

独自のOnItemSelectedListenerを作成しました。

_import Android.util.Log;
import Android.view.View;
import Android.widget.AdapterView;
import Android.widget.AdapterView.OnItemSelectedListener;

public class SpinnerOnItemSelectedListener implements OnItemSelectedListener {
      public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
          MySpinner spin = (MySpinner) parent.findViewById(parent.getId());
          if (!spin.getCallListener()) {
              Log.w("yourapptaghere", "Machine call!");
              spin.setCallListener(true);
          } else {
              Log.w("yourapptaghere", "UserCall!");
          }
      }

      @Override
      public void onNothingSelected(AdapterView<?> arg0) {
        // TODO Auto-generated method stub
      }
}
_

MySpinnerを作成する場合は、これを使用して選択を設定できます。

setSelection(position, callListener);

CallListenerがtrueまたはfalseの場合。 Trueはリスナーを呼び出し、デフォルトです。これがユーザーインタラクションが識別される理由です。falseもリスナーを呼び出しますが、この特別な場合に必要なコードを使用します。私の場合は例:何もありません。

他の誰かがこれが役に立つと思って、このようなものがすでに存在するかどうかを調べるために長い旅を惜しまないことを願っています:)

2
showp1984

過去に私は区別するためにこのようなことをしました

internal++; // 'internal' is an integer field initialized to 0
textBox.setValue("...."); // listener should not act on this internal setting
internal--;

次に、textBoxのリスナーで

if (internal == 0) {
  // ... Act on user change action
}

ブール値を「true」に設定するのではなく、++と-を使用して、メソッドが内部変更インジケーターを設定する可能性のある他のメソッドをネストする心配がないようにします。

2
Jim Blackler

また、インターネットで良い解決策を探しましたが、私のニーズを満たすものは見つかりませんでした。そのため、この拡張機能をSpinnerクラスに記述して、ListViewと同じ動作をする単純なOnItemClickListenerを設定できるようにしました。

アイテムが「選択」された場合にのみ、onItemClickListenerが呼び出されます。

それを楽しんでください!

 public class MySpinner extends Spinner
    {
        private OnItemClickListener onItemClickListener;


        public MySpinner(Context context)
        {
            super(context);
        }

        public MySpinner(Context context, AttributeSet attrs)
        {
            super(context, attrs);
        }

        public MySpinner(Context context, AttributeSet attrs, int defStyle)
        {
            super(context, attrs, defStyle);
        }

        @Override
        public void setOnItemClickListener(Android.widget.AdapterView.OnItemClickListener inOnItemClickListener)
        {
            this.onItemClickListener = inOnItemClickListener;
        }

        @Override
        public void onClick(DialogInterface dialog, int which)
        {
            super.onClick(dialog, which);

            if (this.onItemClickListener != null)
            {
                this.onItemClickListener.onItemClick(this, this.getSelectedView(), which, this.getSelectedItemId());
            }
        }
    }
1
Cliffus

上記のaaamosの投稿を拡張するために、コメントする50の担当者ポイントがないため、ここで新しい回答を作成しています。

基本的に、彼のコードは、最初のスピナーの選択が0の場合に機能します。しかし、それを一般化するために、私は彼のコードを次のように修正しました。

@Override
public void setOnItemSelectedListener(final OnItemSelectedListener listener)
{
    if (listener != null)
        super.setOnItemSelectedListener(new OnItemSelectedListener()
        {
            private static final int NO_POSITION  = -1;

            private int lastPosition = NO_POSITION;


            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
            {
                if ((lastPosition != NO_POSITION) && (lastPosition != position))
                    listener.onItemSelected(parent, view, position, id);

                lastPosition = position;
            }


            @Override
            public void onNothingSelected(AdapterView<?> parent)
            {
                listener.onNothingSelected(parent);
            }
        });
    else
        super.setOnItemSelectedListener(null);
}

基本的に、このコードはonItemSelected()の最初の起動を無視し、その後のすべての「同じ位置」の呼び出しを無視します。

もちろん、ここでの要件は、選択がプログラムで設定されることですが、デフォルトの位置が0でない場合は、とにかくそうなるはずです。

1
Tom anMoney

ロギングを行ったところ、初期化時にのみ呼び出されることがわかりました。これは煩わしいことです。このすべてのコードの必要性がわかりません。ガード値に初期化されたインスタンス変数を作成し、メソッドが最初に呼び出された後に設定しました。

OnItemSelectedメソッドが呼び出されているときにログに記録しましたが、それ以外の場合は1回しか呼び出されていませんでした。

2つの何かを作成しているときに問題が発生しました。これは、参照しているリストへの参照が既にあり、アダプターの外部に追加されたカスタムアダプターでadd()を呼び出していたためです。これに気づき、addメソッドを削除した後、問題は解決しました。

このコードがすべて必要ですか?

0
Ghoti

私はこれがかなり遅いことを知っています、しかし私はこれに対する非常に簡単な解決策を思いつきました。それはアルキメデスの答えに基づいています、それはまったく同じです。実装も非常に簡単です。受け入れられた答えを参照してください:

不要なonItemSelected呼び出し

0
Vedavyas Bhat