437

これを解決するためのエレガントではない方法をいくつか考えましたが、何かが欠けているに違いないことはわかっています。

MyonItemSelectedはユーザーとの対話なしですぐに起動しますが、これは望ましくない動作です。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.
    }
}
}
4

33 に答える 33

395

Runnables の使用は完全に間違っています。

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

このようにして、アニメーションなしで選択を設定すると、アイテムで選択されたリスナーが呼び出されます。ただし、リスナーは null であるため、何も実行されません。次に、リスナーが割り当てられます。

したがって、次の正確な順序に従ってください。

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
于 2013-06-27T07:19:03.797 に答える
205

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

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

私のためにそれを行うことで、最終的に望ましい動作が発生しました。

この場合、リスナーは変更された項目でのみ起動することも意味します。

于 2012-02-21T10:09:32.300 に答える
81

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

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

于 2010-04-01T17:53:21.343 に答える
55

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 スレッドに対して非同期であるため、連続したハンドラー ポストで実行する必要があることです。

于 2012-11-23T11:51:09.537 に答える
35

残念ながら、この問題に対して最も一般的に提案されている 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(); }
}
于 2013-06-15T15:40:11.583 に答える
27

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;
于 2016-02-06T14:53:20.533 に答える
26

私も同様の状況にありましたが、私には簡単な解決策がありました。

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

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

于 2012-12-07T12:05:26.590 に答える
13
spinner.setSelection(Adapter.NO_SELECTION, false);
于 2014-06-21T21:05:11.167 に答える
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) {
                }

            });

        }
    });
于 2015-07-29T15:22:57.727 に答える
3

私にとっては何も機能せず、ビューに複数のスピナーがあるため(そしてブールマップを保持している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) {
        }
    });
于 2015-08-24T17:39:20.587 に答える
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 の後に 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
}
于 2013-02-08T08:22:49.667 に答える
2

私の小さな貢献は、上記のいくつかのバリエーションであり、私に数回適しています。

整数変数をデフォルト値 (またはプリファレンスに保存された最後に使用された値) として宣言します。リスナーが登録される前に、 spinner.setSelection(myDefault) を使用してその値を設定します。onItemSelected で、追加のコードを実行する前に、新しいスピナーの値が割り当てた値と等しいかどうかを確認します。

これには、ユーザーが同じ値を再度選択した場合にコードを実行しないという追加の利点があります。

于 2013-06-21T11:09:57.340 に答える
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.
}

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

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

楽しみましょう、ジェンス。

于 2014-10-27T20:42:57.773 に答える
0

私は電話しようとします

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

setAdapter() を呼び出した後。また、アダプターの前に呼び出してみてください。

オーバーライドされた setAdapter メソッドにブール値フラグをラップして、イベントをスキップできるサブクラス化に対応するソリューションが常にあります。

于 2010-04-01T18:26:30.250 に答える
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) {

        }
    });

終わり

于 2015-04-13T09:45:12.240 に答える
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;
}
于 2015-03-17T21:55:37.440 に答える
0

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

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

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

于 2015-04-10T09:18:44.567 に答える
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;
}
于 2012-07-14T16:15:18.627 に答える
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 コールバックをトリガーせずにスピナーで項目を選択するために使用します。

于 2016-12-20T12:12:08.687 に答える
-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) {
            }
        });
于 2014-12-26T20:25:00.823 に答える
-1

これは特に洗練されたソリューションではありませんが、確実に機能することがわかりました。onResume 内から遅延スレッドにリスナーを設定するだけです。その後、自由に初期化を行うことができ、UI はレイアウトを変更し、リスナーを設定します。

Thread t = new Thread(){
            public void run() {
                try{                
                    Thread.sleep(1000);
                    getActivity().runOnUiThread(new Runnable() {                        
                        @Override
                        public void run() {
                            setSpinnerListeners();

                        }
                    });
                }
                catch(Exception e){
                    e.printStackTrace();
                }
            };
        };
        t.start();
于 2015-07-08T17:56:55.873 に答える
-1

一般的な使用のためにスピナーを設計し、データ入力だけでユーザーが選択できるようにします。利点:

1. アプリのスピナー スタイルを同じに保ちます。
2.任意の場所でスピナーを開始します。
3. リンクされたスピナーを簡単に処理できます (別のデータで ReuseSpinner を再起動します)。

私のデモの例: ReuseSpinner
は ReuseSpinner にデータを渡します:

Intent intent = new Intent(MainActivity.this, SpinnerActivity.class);
intent.putExtra(SpinnerActivity.Extra_Resource, arrayList);
startActivityForResult(intent, mRequestCode_select_country_prompt);

ユーザーの選択を取得:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == mRequestCode_select_country && resultCode == RESULT_OK){
            if(data != null){
                Map.Entry<String,String> entry = (Map.Entry<String,String>) data.getSerializableExtra(SpinnerActivity.Result_Data);
                if(entry != null){
                    Log.i(TAG, String.format("get result -> key:%s , value:%s", entry.getKey(), entry.getValue()));

                }
            }
        }
    }
于 2016-08-04T09:50:40.207 に答える