OnItemSelectedListener イベント ハンドラーは、スピナーの選択がプログラムによって変更されたときと、ユーザーがスピナー コントロールを物理的にクリックしたときに呼び出されます。イベントがユーザーの選択によって何らかの方法でトリガーされたかどうかを判断することは可能ですか?
または、スピナーのユーザー選択を処理する別の方法はありますか?
回避策を実行するには、最後に選択した位置を覚えておく必要があります。次に、スピナーリスナーの内部で、最後に選択した位置を新しい位置と比較します。それらが異なる場合は、イベントを処理し、最後に選択した位置を新しい位置値で更新します。それ以外の場合は、イベント処理をスキップします。
コード内のどこかでスピナーの選択した位置をプログラムで変更し、リスナーにイベントを処理させたくない場合は、最後に選択した位置を設定する位置にリセットするだけです。
はい、Androidのスピナーは苦痛です。痛みはその名前「スピナー」から始まるとさえ言えます。少し誤解を招きませんか?:)それについて話している限り、バグがあることにも注意する必要があります-Spinnerは(常にではない)状態を(デバイスの回転時に)復元しない可能性があるため、Spinnerの状態を手動で処理するようにしてください。
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) Arhimed が言ったように。
これが、誰かがこの奇妙な振る舞いに気が狂うのを防ぐのに役立つことを願っています;-)
過去に私は区別するためにこのようなことをしました
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」に設定するのではなく、++ と -- を使用して、メソッドが内部変更インジケーターを設定する可能性のある他のメソッドをネストするときに心配がないようにします。
最近、スピナーを使用していて、インターネットで適切な解決策が思いつかなかったときに、この状況が発生しました。
私のアプリケーションシナリオ:
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 もリスナーを呼び出しますが、この特別なケースに必要なコードを使用します。
他の誰かがこれが便利だと思って、このようなものがすでに存在するかどうかを調べる長い旅を免れることを願っています:)
上記の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でない場合は、とにかくそうなるはずです。
また、インターネットで良い解決策を探しましたが、私のニーズを満たすものは見つかりませんでした。そこで、ListView と同じ動作をする単純な OnItemClickListener を設定できるように、この拡張機能を Spinner クラスに記述しました。
項目が「選択」された場合にのみ、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());
}
}
}
ロギングを行ったところ、初期化時にのみ呼び出されることがわかりました。これは面倒です。このすべてのコードの必要性がわかりません。ガード値に初期化されたインスタンス変数を作成し、メソッドが初めて呼び出された後に設定しました。
onItemSelected メソッドが呼び出されたときにログに記録しましたが、それ以外の場合は 1 回だけ呼び出されていました。
何かの 2 つを作成しているという問題があり、参照していたリストへの参照が既にあり、アダプターの外部に追加されたカスタムアダプターで add() を呼び出していたためであることに気付きました。これに気づき、 add メソッドを削除した後、問題はなくなりました。
このコードがすべて必要ですか?
これはかなり遅いことはわかっていますが、これに対する非常に簡単な解決策を思いつきました。それは Arhimed の答えに基づいており、まったく同じです。実装も非常に簡単です。受け入れられた回答を参照してください: