Как сохранить OnItemSelected от запуска только что созданного 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
Гюнай Гюльтекин

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

Этот шаблон работает должным образом stackoverflow.com/questions/13397933/…
saksham

Ответы:


78

Я ожидал, что ваше решение сработает - хотя событие выбора не сработает, если вы установите адаптер до настройки прослушивателя.

При этом простой логический флаг позволит вам обнаружить мошенническое событие первого выбора и проигнорировать его.


15
тьфу да Вот что я имел в виду под неэкспонентным решением. Кажется, должен быть лучший путь. Однако, спасибо.
FauxReal

5
Эта тема в Dev ml более детально проинформирована об этом: groups.google.com/group/android-developers/browse_thread/thread/… - К сожалению, решения не дано ...
директоров

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 скрытый драгоценный камень! Передача false в качестве параметра «animate» не вызывает функцию обратного вызова слушателя. Потрясающие!
Пкк

3
+1 Странное, но элегантное решение :) К счастью, мне все равно пришлось вызывать setSelection ...
Мартин Т.

35
При сборке элемента Spinner UI слушатель все равно будет срабатывать, поэтому он будет срабатывать независимо от того, что не предотвращает нежелательное поведение, описанное OP. Это прекрасно работает, если не объявлено во время или перед onCreateView (), но это не то, что они просили.
Руди Кершоу

6
Полезно, но решает проблему, отличную от представленной OP. OP относится к событию выбора, которое (к сожалению) автоматически запускается при первом появлении представления, даже если программист не сделал setSelection .
ToolmakerSteve

2
Параметр «ложное» значение в методе setSelection (..) был решением для меня. ти!
Дани

195

Ссылаясь на ответ Дэна Дайера, попробуйте зарегистрировать OnSelectListenerсвой post(Runnable)метод:

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

Сделав это для меня, наконец-то произошло желаемое поведение.

В этом случае это также означает, что слушатель запускает только измененный предмет.


1
Я получаю сообщение об ошибке: метод setOnItemSelectedListener (AdapterView.OnItemSelectedListener) в типе AdapterView <SpinnerAdapter> не применим для аргументов (new Runnable () {}), почему это так?
Якоб

Разве это не устанавливает гонку между Runnable и UI Thread?
kenny_k

6
@theFunkyEngineer - этот код должен запускаться из одного из методов основного потока, например onCreate(), onResume()и т. д. В этом случае это фантастический трюк, без опасности состояния гонки. Я обычно использую этот трюк onCreate()сразу после кода макета.
Ричард Ле Мезурье

1
Это отличное решение и определенно не взломать! Такая функциональность - это то, как все делается глубоко в рамках. Обидно, что Спиннер не делает этого внутренне. Тем не менее, это самый чистый способ иметь гарантированный запуск кода после создания Activity. Это работает, потому что слушатель еще не настроен на Spinner, когда Activity пытается уведомить их.
jophde

1
Это приемлемое решение . не слепой выстрел. другие решения более склонны к проблеме изменения поведения в будущем.
Кулдип Сингх Дакка

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

Это отключает слушателя, изменяет выбор и повторно включает слушателя после этого.

Хитрость в том, что вызовы асинхронны с потоком пользовательского интерфейса, поэтому вы должны делать это в последовательных сообщениях обработчиков.


Потрясающие. У меня было несколько спиннеров, и я пытался установить для всех их слушателей значение NULL, прежде чем устанавливать их значения, а затем я вернул им все значения, которые они должны были быть, но по какой-то причине это не сработало. попробовал эту функцию вместо этого, и это сработало. Я не знаю, почему мой не работал, но это работает, поэтому мне все равно: 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, чтобы закрыть клавиатуру при открытии счетчика. Теперь все мои счетчики ведут себя именно так, как я хочу.
user3829751

Прекрасный ответ. Это все еще вызывает 0-й элемент, когда я добавляюAll () к адаптеру, но мой 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, я не уверен на 100%, что вы имеете в виду. Но int i будет позицией счетчика при каждом запуске onItemSelected. Проблема состоит в том, что onItemSelected запускается всякий раз, когда спиннер загружается впервые, без какого-либо реального взаимодействия с пользователем, что приводит к нежелательному поведению в этом случае. int i в этой начальной точке будет равно 0, так как это начальный индекс по умолчанию при первой загрузке счетчика. Таким образом, мое решение проверяет, чтобы убедиться, что выбран фактический другой элемент вместо того, чтобы текущий выбранный элемент был повторно выбран ... Это отвечает на ваш вопрос?
Крис

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

4
Что если пользователь выберет позицию 0? Они будут проигнорированы.
Yetti99

Я не думаю, что последний путь - хорошая идея. Я запускаю счетчики, загружая позицию из SharedPreferences и используя setSelection. Очень часто значения в SharedPrefs не совпадают со значениями по умолчанию при создании счетчиков, поэтому onItemSelected будет срабатывать при запуске.
Arthez

26

Я был в подобной ситуации, и у меня есть простое решение для меня.

Вроде бы методы setSelection(int position)и setSelected(int position, boolean animate)имеют разную внутреннюю реализацию.

Когда вы используете второй метод setSelected(int position, boolean animate)с ложным флагом анимации, вы получаете выбор без запуска onItemSelectedслушателя.


Лучший подход - не беспокоиться о дополнительных вызовах onItemSelected, но убедиться, что он показывает правильный выбор. Таким образом, вызов spinner.setSelection (selectedIndex) перед добавлением слушателя заставил меня работать стабильно.
andude

1
нет метода setSelected (int position, boolean animate) для spinner
shift66

4
Фактический звонок, который вам нужен,setSelection(int position, boolean animate);
Брэд

+1 для вас. Это решает более общую проблему, когда код модифицируется больше раз. Содержимое и выбор Spinner, сохраняющие onItemSelected только для взаимодействия с пользователем
alrama

4
к сожалению, флаг ложной анимации все еще вызывает onItemSelectedAPI23
mcy

23

Просто чтобы прояснить намеки на использование 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;

1
Это прекрасно работает, и начиная с Android 6+, это единственный метод, который работает. НО, вы также должны сделать то же самое с setOnKeyListener (), или он не работает, когда пользователь перемещается с помощью клавиатуры.
Стефан

Прекрасно работает, все другие решения имеют проблемы с другими телефонами.
Цивэй Цзэн

Это просто и абсолютно идеально! Никакой дополнительной ерунды не нужно, просто помните о логике. Я рад, что прокрутил весь путь до сюда!
user3833732

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

Я просто хотел упомянуть, что, похоже, это устраняет ту же ошибку с DropDownPreferences, которую я недавно разместил здесь: stackoverflow.com/questions/61867118/… Я не могу поверить, что это так: D
Даниэль Уилсон

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, поскольку это действительно PITA.


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версии on ниже J, вызвав ViewTreeObserver.removeGlobalOnLayoutListenerэтот метод, который устарел и имеет имя, аналогичное методу, используемому в этом ответе.
Джек Мейстер,

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

Объявите счетчик int после onCreate, а затем внутри метода onItemSelected () поместите условие «if», чтобы проверить, сколько раз был вызван 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

После того, как у меня возникла та же проблема, я пришел к этому решению, используя теги Идея этого проста: всякий раз, когда прядильщик изменяется программно, убедитесь, что тег отражает выбранную позицию. Затем в слушателе вы проверяете, соответствует ли выбранная позиция метке. Если это так, выбор счетчика был изменен программно.

Ниже мой новый класс "Spinner Proxy":

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, если и только если пользователь изменил счетчик. Обратите внимание, что у этой функции есть побочный эффект - она ​​устанавливает тег, поэтому второй вызов в том же вызове слушателя всегда будет возвращать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) {

        }
    });

Выполнено


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