Як утриматися відItemSelected від стрілянини за нещодавно створеним Spinner?


419

Я думав про не менш елегантні способи вирішити це, але знаю, що мені щось не вистачає.

Моє onItemSelectedвимикається негайно без будь-якої взаємодії з користувачем, і це небажана поведінка. Я хочу, щоб інтерфейс зачекав, поки користувач щось вибере, перш ніж він щось зробить.

Я навіть спробував налаштувати слухача на 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ünay Gültekin

1
Простим рішенням буде зробити перший елемент Spinnerпорожнім, а всередині onItemSelectedви зможете виявити, якщо рядок тоді не порожній startActivity!
Мухаммед Бабар

Ця схема працює правильно stackoverflow.com/questions/13397933 / ...
saksham

Відповіді:


78

Я б очікував, що ваше рішення спрацює - я хоч подія вибору не запустить, якщо ви встановите адаптер перед налаштуванням слухача.

Незважаючи на це, простий бульовий прапор дозволить вам виявити шахрайську подію першого вибору та проігнорувати її.


15
тьфу, так. Ось що я мав на увазі під неелегантним рішенням. Схоже, має бути кращий шлях. Дякую, хоча.
FauxReal

5
Цей потік у Dev ml має більш детальну інформацію про це: groups.google.com/group/android-developers/browse_thread/thread/… - На жаль, рішення не дано ...
BoD

25
Процес викладки компонентів вистрілює слухача вибору. Тому вам доведеться додати слухача після того, як буде зроблено макет. Мені не вдалося знайти підходящого, прямого місця для цього, оскільки макет здається в якийсь момент після onResume()і onPostResume(), тому всі нормальні гачки завершилися до моменту, коли трапляється макет.
Ден Дайер

28
Я б тримався подалі від цього булевого прапора - так, якби в майбутньому змінилася поведінка, це може спричинити помилку. Більш надійним рішенням буде збереження змінної з "поточним вибраним індексом", ініціалізованим до вибраного першого елемента. Потім на подію вибору - перевірте, чи дорівнює вона новій позиції - поверніться і нічого не робіть. Звичайно, оновіть змінну на вибір.
daniel.gindi

2
Це не працює. Відповідь @casanova працює. Це повинна бути прийнята відповідь.
Сіддхарт

379

Використання Runnables абсолютно некоректно.

Використовуйте setSelection(position, false);в початковому виборі ранішеsetOnItemSelectedListener(listener)

Таким чином ви встановлюєте свій вибір без анімації, що викликає виклик слухача, вибраного на елементі. Але слухач нульовий, тому нічого не працює. Потім ваш слухач призначається.

Тож дотримуйтесь цієї точної послідовності:

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

48
+1 Прихований самоцвіт! Передача помилки як параметра "anime" не викликає зворотного виклику слухача. Дивовижно!
pkk

3
+1 Дивне, але елегантне рішення :) На щастя, мені все одно довелося зателефонувати на setSelection ...
Martin T.

35
Слухач все ще запустить, коли елемент інтерфейсу Spinner буде зібраний, тож він запустить незалежно від того, що не запобігає небажаній поведінці, описаній в ОП. Це чудово працює, якщо він не декларується під час або раніше onCreateView (), але це не те, що вони просили.
Руді Кершо

6
Корисна, але вирішує іншу проблему, ніж представлена ​​ОП. OP посилається на подію вибору, яка (на жаль) автоматично спрацьовує, коли подання вперше з'являється, навіть якщо програміст не зробив setSelection .
ToolmakerSteve

2
Параметр значення "false" у методі setSelection (..) був для мене рішенням. ти!
Дани

195

Посилаючись на відповідь Дена Дайєра, спробуйте зареєструвати OnSelectListener в post(Runnable)методі:

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

Роблячи це для мене, нарешті, відбулася бажана поведінка.

У цьому випадку це також означає, що слухач працює лише на зміненому елементі.


1
Я отримую помилку: "Метод setOnItemSelectedListener (AdapterView.OnItemSelectedListener) типу AdapterView <SpinnerAdapter> не застосовується для аргументів (новий Runnable () {}).
Якоб

Хіба це по суті не налаштовує умови перегонів між Runnable та UI потоком?
kenny_k

6
@theFunkyEngineer - Цей код слід запускати з одного з основних методів потоку, наприклад onCreate(), onResume()тощо. У цьому випадку це фантастична хитрість, без небезпеки стану перегонів. Я зазвичай використовую цей трюк onCreate()лише після коду макета.
Річард Ле Месюр'є

1
Це чудове рішення і, безумовно, не хак! Цей тип функціональності - це те, як все робиться глибоко в рамках. Прикро, що Spinner цього не робить внутрішньо. Однак це найчистіший спосіб гарантувати деякий код після створення діяльності. Це працює, тому що слухач ще не встановлений на Spinner, коли активність намагається повідомити їх.
Джофд

1
Це прийнятне рішення . не сліпий постріл. інші рішення більш схильні до проблеми зміни поведінки в майбутньому.
Kuldeep Singh Dhaka

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

Це вимикає слухача, змінює виділення та знову вмикає слухача після цього.

Хитрість полягає в тому, що виклики є асинхронними для потоку інтерфейсу користувача, тому це потрібно робити в послідовних публікаціях обробників.


Дивовижно. У мене було кілька спінерів і намагався встановити всі їхні слухачі до нуля, перш ніж встановлювати їх значення, потім я повернув їх до того, яким вони повинні були бути, але чомусь це не вийшло. спробував цю функцію, і вона спрацювала. Я не знаю, чому моя не працювала, але це працює, тому мені все одно: D
JStephen

4
Зверніть увагу: якщо ви зателефонували setSpinnerSelectionWithoutCallingListenerдвічі швидко, щоб другий дзвінок був зроблений, коли перший вже встановив слухача null, ваш спінер буде застряг із nullслухачем назавжди. Я пропоную наступне виправлення: додати if (listener == null) return;після spinner.setSelection(selection).
Фіолетова жирафа

34

На жаль, здається, що два найпоширеніші рішення цієї проблеми, а саме підрахунок подій зворотних викликів та розміщення 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
Це має бути найвища відповідь. Це просто, але геніально. Це дозволяє зберегти всю поточну реалізацію однаковою, за винятком одного рядка, де ви ініціалізуєтесь. Однозначно зробити старі проекти, що підлягають ретро, ​​досить легко. Крім того, одним каменем я вбив двох птахів, застосувавши інтерфейс OnTouchLisener, щоб закрити клавіатуру, коли спінер відкриється. Тепер усі мої прядильники поводяться саме так, як я хочу.
користувач3829751

Прекрасна відповідь. Це все ще спрацьовує на 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

}

Повірте мені, коли я це кажу, це, безумовно, найнадійніше рішення. Хак, але це працює!


Це навіть спрацює, якщо ви намагаєтесь змінити значення? У моєму випадку я намагаюся встановити значення на зразок 3, коли воно фактично 0, не викликаючи слухачів змін. Ви говорите, що int i повертає інше значення лише тоді, коли користувач вибирає його?
JStephen

Привіт JStephen, я не впевнений на 100%, що ти маєш на увазі. Але int i буде позицією спінера, коли буде запущено onItemSelected. Проблема полягає в тому, що onItemSelected спрацьовує кожного разу, коли спінер вперше завантажується, без будь-якої реальної взаємодії з користувачем, що призводить до небажаної поведінки в цьому випадку. int i буде дорівнює 0 в цій початковій точці, оскільки це початковий індекс за замовчуванням при першому завантаженні спінера. Тож моє рішення перевіряє, чи не вибрано фактичний інший елемент замість обраного на даний момент вибраного елемента ... Чи відповідає це на ваше запитання?
Кріс

Привіт Кріс, у мене є сторінка, яка витягує інформацію з бази даних для редагування користувачем. коли сторінка відкривається, я заповнюю спінери, а потім встановлюю їхні позиції значенням, які були в базі даних. Отже, якщо я встановив їх положення, наприклад, 3, це призводить до запуску onItemSelected з i, встановленого в 3, що відрізняється від початкового. Я думав, ти говориш, що я встановлюється лише в тому випадку, якщо користувач насправді змінив його сам.
JStephen

4
Що робити, якщо користувач вибере позицію 0? Вони будуть ігноровані.
Yetti99

Я не думаю, що останній спосіб позиції - це гарна ідея. Я запускаю спінери, завантажуючи положення з SharedPreferences та використовуючи setSelection. Дуже часто значення в SharedPrefs не збігаються зі значеннями за замовчуванням, коли створюються спінери, тому onItemSelected буде ініційовано при ініціації.
Артез

26

Я опинився в подібній ситуації, і у мене просте рішення працює.

Схоже, методи setSelection(int position)іsetSelected(int position, boolean animate) мають різну внутрішню реалізацію.

Коли ви використовуєте другий метод setSelected(int position, boolean animate)з помилковим прапором анімації, ви отримуєте вибір, не звільняючи onItemSelectedслухача.


Кращий підхід полягає в тому, щоб не турбуватися про зайві дзвінки в onItemSelected, а переконатися, що він відображає правильний вибір. Отже, виклик spinner.setSelection (selectedIndex) перед додаванням слухача змусив це працювати стабільно для мене.
andude

1
немає обраного методу (позиція int, булева анімація) для
спінера

4
Фактичний дзвінок, який вам потрібен,setSelection(int position, boolean animate);
Бред

+1 для вас. Це вирішує більш загальну проблему, коли код змінюється більше разів, зміст Spinner та вибір, що підтримується, вибирається лише для взаємодії з користувачем
alrama

4
На жаль помилковий прапор анімації все ще дзвонить onItemSelectedв API23
1717 року

23

Тільки для того, щоб чітко визначити натяки на використання onTouchListener, щоб розрізняти автоматичні дзвінки до setOnItemSelectedListener (які є частиною ініціалізації активності тощо) та виклики до нього, ініційовані фактичною взаємодією з користувачем, я зробив наступне, спробувавши деякі інші пропозиції тут і виявив, що він добре працює з найменшими рядками коду.

Просто встановіть булеве поле для вашої діяльності / фрагмента, як:

private Boolean spinnerTouched = false;

Тоді перед тим, як встановити набір спінераOnItemSelectedListener, встановіть 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

Це просто і абсолютно ідеально! Ніяких зайвих дурниць не потрібно, просто пам’ятайте про логіку. Я радий, що я прокрутив всю дорогу донизу!
користувач3833732

Замість setOnKeyListener (), ви можете підкласифікувати спінер і встановити прапор spinnerTouched = true у методі, що перекривається preformClick (), який викликається в обох випадках (touch / key). Відпочинок той самий.
Всемогутній

Просто хотілося б згадати, що це, як видається, вирішує ту саму помилку з DropDownPreferences, яку я нещодавно опублікував тут: stackoverflow.com/questions/61867118/… Я не можу f * cking вважаю це tbh: D
Daniel Wilson

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

3
Код може говорити сам за себе, але трохи пояснення йде дуже довго :)
nhaarman

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

Використовуйте його у своєму 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);

При цьому жодна подія не запускається і взаємодія користувача не переривається. Це значно зменшило складність мого коду. Це повинно бути включено в склад Android, оскільки це справді ПІТА.


1
Це не працює для мене, воно все ще спрацьовує onItemSelected.
Артез

Arthez, будь ласка, перевірте, чи дійсно ви передаєте вірність третьому аргументу. Якщо так, тут щось не так. Якщо можливо, опублікуйте свій код.
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 - це найчистіше рішення конкретного питання ОП. Хочу зазначити, що ви можете видалити ViewTreeObserver.OnGlobalLayoutListenerверсії нижче J, зателефонувавши ViewTreeObserver.removeGlobalOnLayoutListener, що застаріло і має схожу назву з методом, який використовується у цій відповіді.
Джек Майстер

7

Це станеться, якщо ви робите вибір у коді як;

   mSpinner.setSelection(0);

Замість вищевказаного твердження використовуйте

   mSpinner.setSelection(0,false);//just simply do not animate it.

Редагувати: Цей спосіб не працює для інтерфейсу Mi Mi версія Android.


2
Це безумовно вирішило проблему для мене. Я читав документацію про віджет Spinner .. абсолютно складно зрозуміти різницю: setSelection (int позиція, булева анімація) -> Перейти безпосередньо до певного елемента в даних адаптера. setSelection (позиція int) -> Встановлює вибраний в даний момент елемент.
Мет

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

Оголосіть лічильник int після методу onCreate, а потім всередині методу onItemSelected () поставіть умову "якщо", щоб перевірити, скільки разів викликався atapter. У вашому випадку ви його зателефонували лише раз:

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

Вам також знадобиться 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()поверне справжню, якщо і лише в тому випадку, якщо користувач змінив спінер. Зауважте, що ця функція має побічний ефект - вона встановить тег, тому другий виклик у тому ж виклику слухача завжди повернеться false.

Ця обгортка також буде вирішувати проблему із викликається слухачем під час створення макета.

Повеселіться, Єнс.


1

Оскільки для мене нічого не працювало, і я маю на увазі більше 1 спінера (а IMHO, що тримає карту bool, є надмірним), я використовую тег для підрахунку кліків:

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

де 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

Це теж не елегантне рішення. Насправді це швидше Рубе-Гольдберг, але, здається, працює. Я переконуюсь, що спінер використовується щонайменше один раз, розширивши адаптер масиву та змінивши його getDropDownView. У новому методі getDropDownView у мене є булевий прапор, який встановлено, щоб відображати спадне меню було використано принаймні один раз. Я ігнорую дзвінки до слухача, поки не встановлено прапор.

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

        }
    });

Зроблено


Це цікаве рішення. Можна використати більше пояснень. В основному це навмисне ігнорування першої подіїЕлектрики. Вони можуть працювати добре в деяких випадках, але не в інших, наприклад, коли включені параметри доступності (див. Пояснення Жорріта) .
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 . Я створив власну прив'язку, щоб переконатися, що слухач не викликається, поки вибраний елемент не буде змінено, навіть якщо користувач вибирає одну і ту ж позицію знову і знову, подія не запускається.

Макет файлу 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 призначені для користувача сетер

ПРИМІТКА

  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) Також буде вдячне пояснення того, що робить ваш код. Не всі фрагменти коду розуміються одразу під час читання коду.
Майк Кох
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.