Как отключить вызов onItemSelectedListener при настройке выбранного элемента по коду

Просто интересно, как вы решаете следующую проблему: результат рассчитывается в зависимости от выбранных элементов двух спиннеров. Чтобы обрабатывать вещи пользовательского интерфейса, то есть пользователь выбирает новый элемент в одном из счетчиков, я устанавливаю прослушиватель, используя setOnItemSelectedListener для счетчика в моем методе onCreate() действия.

Теперь: это работает, конечно, хорошо. Работа слушателя заключается в том, чтобы инициировать новый расчет результата.

Проблема: поскольку я перехватываю onPause() onResume() для сохранения/восстановления последнего состояния, у меня есть метод, который устанавливает выбранный элемент этих двух счетчиков программно, как здесь:

startSpinner.setSelection(pStart);
destSpinner.setSelection(pDest);

Эти два вызова также вызывают слушателей! Мой метод вычисления результата плюс уведомление о новом наборе результатов вызывается здесь дважды!

Глупый прямой подход для этого состоял бы в том, чтобы иметь логическую переменную отключающую все, что слушатель делает внутри, устанавливая ее перед установкой выбранных элементов и сбрасывая ее после этого. Хорошо. Но есть ли лучший метод??

Я не хочу, чтобы слушатели вызывались кодом — действиями, только действиями пользователя! :-(

Как ты делаешь это? Спасибо!


person Zordid    schedule 19.02.2010    source источник
comment
Вы можете использовать setSelection(pStart, false) для параметра анимации. Но это не работает все время.   -  person sunlover3    schedule 15.08.2017


Ответы (11)


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

Нежелательные вызовы onItemSelected

person Vedavyas Bhat    schedule 13.02.2014
comment
Большое спасибо, звучит как отличный подход! Мне придется изменить свой код, когда у меня будет время... - person Zordid; 15.03.2014
comment
Забавно, что вы также ответили на свой вопрос - так же, как и я раньше в своем вопросе .. :) - person Zordid; 15.03.2014
comment
:D да. Что еще смешнее, так это то, что я понял работу обратного вызова, увидев ваш ответ :) - person Vedavyas Bhat; 15.03.2014

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

Создайте свой слушатель для счетчика как OnTouchListener, так и OnItemSelectedListener.

public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {

    boolean userSelect = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        userSelect = true;
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (userSelect) { 
            // Your selection handling code here
            userSelect = false;
        }
    }

}

Добавьте слушателя к регистратору счетчика для обоих типов событий.

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);

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

person Andres Q.    schedule 11.02.2015
comment
Определенно лучшее решение, которое я видел для этого. Иногда мне хочется, чтобы Spinner устарел! - person jophde; 16.02.2015
comment
Если создать абстрактный класс SpinnerInteractionListener и создать абстрактный метод onSelected (AdapterView‹?› parent, View view, int pos, long id, boolean userSelected), то вы можете перехватывать все события (пользователь, инициализация и т. д.) и реагировать на них по-разному. способы. Это то, что мне было нужно. Идеальное решение, спасибо. - person Alexandr; 28.08.2015
comment
Я пробовал все, что мог, чтобы решить мою проблему, связанную с Spinner, и вот что, наконец, сделало это. Я считаю, что любой, кто когда-либо реализовывал Spinner, должен делать это именно так. API в основном сломался из коробки, это была недостающая функциональность. Большое спасибо! - person boltup_im_coding; 12.11.2016
comment
он терпит неудачу, если пользователь касается просмотра [но не изменяет данные], а затем, если приложение устанавливает счетчик, тогда прослушиватель выполняется - person Grzegorz Dev; 22.11.2016

Хорошо, теперь у меня все работает так, как я хочу.

Здесь нужно понять (и я не понял, когда писал этот вопрос...), что все в Android работает в одном потоке - потоке пользовательского интерфейса.

Значение: даже если вы устанавливаете значения Spinner здесь и там: они только обновляются (визуально) и их слушатели вызываются только после всех методов, в которых вы сейчас находитесь (например, onCreate , onResume или что-то еще) завершены.

Это позволяет следующее:

  • сохранить выбранные позиции в переменных поля. (например, currentPos1, currentPos2)
  • слушатели onItemSelectedListener() вызывают метод типа refreshMyResult() или что-то в этом роде.
  • при программной установке позиций установите счетчики и сразу после этого вызовите собственный метод обновления вручную.

Метод refreshMyResult() выглядит так:

int newPos1 = mySpinner1.getSelectedItemPosition();
int newPos2 = mySpinner2.getSelectedItemPosition();
// only do something if update is not done yet
if (newPos1 != currentPos1 || newPos2 != currentPos2) {
    currentPos1 = newPos1;
    currentPos2 = newPos2;

    // do whatever has to be done to update things!

}

Поскольку слушатели будут вызваны позже — и к тому времени запомненная позиция в currentPos уже будет обновлена ​​— ничего не произойдет и не произойдет ненужного обновления чего-либо еще. Когда пользователь выбирает новое значение в одном из счетчиков, ну и обновление будет выполнено соответствующим образом!

Вот и все! :-)

Ahh - еще одна вещь: ответ на мой вопрос: Нет. Слушатели не могут быть отключены (легко) и будут вызываться всякий раз, когда значение изменяется.

person Zordid    schedule 24.02.2010
comment
У меня была аналогичная проблема с использованием onCheckChanged, и я решил ее, проверив, что код вызывается пользователем, используя метод isPressed buttonView. Итак, при всем уважении, я думаю, что ваш подход может быть опрометчивым, если, конечно, я неправильно понимаю вопрос. Посмотрите, чтобы убедиться, что он был вызван пользователем, а не вашими методами onPause() и onResume(). - person zkwentz; 27.01.2011
comment
Мне нравится подход Zordid, потому что независимо от того, откуда поступил ввод (пользовательский ввод или ранее сохраненный), в моем приложении ничего не нужно делать, если значение не изменилось. - person larham1; 14.07.2011
comment
@Zach: isPressed() всегда имеет значение false внутри onItemSelectedListener(). Согласно документу, isPressed() Указывает, находится ли представление В НАСТОЯЩЕЕ ВРЕМЯ в нажатом состоянии. OnItemSelectedListener регистрирует обратный вызов, который будет вызываться, когда элемент в этом AdapterView БЫЛ ВЫБРАН. Я поместил отладочное сообщение в onItemSelectedListener() и обнаружил, что isPressed() всегда ошибочен. Я копался в коде и обнаружил, что isSelected() может быть правильным методом для использования. но НЕТ, этот метод также всегда является ложным внутри onLitemSelectedListener. Таким образом, метод Зордида — это единственное решение, и в моем коде он работает так, как ожидалось. - person fangmobile; 09.10.2012

Сначала добавьте логические значения для остановки вызова прослушивателя счетчика.

  Boolean check = false;

Затем вы добавляете прослушиватель Touch, а в элементе нажмите Listener, как показано ниже.

 holder.filters.setOnTouchListener(new View.OnTouchListener() {
               @Override
               public boolean onTouch(View v, MotionEvent event) {

                   check = true;
                   return false;
               }
           });

           holder.filters.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
           {

               @Override
               public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id)
               {
                   flag = filterids.get(position);

                   if(check)
                   {
                       check = false;
                       new Applyfilters().execute(flag,"3");
                   }else{

                   }

               }

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

Его простая работа хороша для многократной остановки вызова сервера.

person Venkatesh    schedule 30.08.2017

Вы можете очень просто вызвать метод Spinner.setSelection(int position, boolean animate) с помощью false, чтобы слушатели не реагировали на изменение.

person joe    schedule 12.07.2013
comment
У меня отлично работает (Api 16) - person Cocorico; 27.06.2014
comment
Аргумент animate определяет анимацию счетчика (необходимо ли выделение выбирать напрямую (false) или нет (true)). Это не имеет ничего общего с вызовом onItemSelected. - person Vedavyas Bhat; 23.09.2014
comment
Это неправильный ответ. У меня это не работает - Android 4.4.4. - person Ajith Memana; 21.10.2014
comment
Пробовал setSelection(spinner, false). Без изменений, запускает слушателя. Андроид 4.4.4 - person Gena Batsyan; 24.01.2015

Spinner.setSelection(int position, boolean animate) запускает прослушиватель на 4.3

person P01550n    schedule 05.09.2013
comment
Это не ответ на мой вопрос, извините. - person Zordid; 18.09.2013

Я создал библиотеку, которая помогает всем, что не нужно вызывать действие элемента onClick в Spinner. Например:

spinner.setSelection(withAction,position);

где withAction — логический флаг, используемый для вызова или не действия элемента

Ссылка на Github: https://github.com/scijoker/spinner2

person a.black13    schedule 11.10.2015

Мое решение очень простое. Сначала инициализируйте глобальную логическую переменную.

boolean shouldWork = true;

Затем используйте приведенный ниже код в методе onCreate().

Spinner spinner = findViewById(R.id.spinner);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView adapter, View v, int i, long lng) {
        if (shouldWork) {
               // Do your actions here
        }
        else
            shouldWork = true;
    }
    public void onNothingSelected(AdapterView<?> parentView)  {

    }
});

Теперь вы можете использовать метод setSelection где угодно, не вызывая метод onItemSelected() с помощью приведенного ниже кода.

shouldWork = false;
spinner.setSelection(0);
person Burak    schedule 21.09.2018

Добавьте OnItemSelectedListener для каждого счетчика после того, как вы установили любое предыдущее значение в onResume.

person Graeme Duncan    schedule 19.02.2010
comment
Проблема в том, что запущенная активность может получить новое намерение, которое должно установить счетчики на новое значение - таким образом, активность и все ее слушатели определенно установлены! Кроме того: я пытался деактивировать прослушиватель с помощью setOnItemSelectedListener(null) перед установкой значений и восстановлением прослушивателей позже: без никакого эффекта! Слушатели вызываются асинхронно! :-( - person Zordid; 20.02.2010

Когда используется Spinner.setSelection(position), он всегда активирует setOnItemSelectedListener()

Чтобы избежать двойного запуска кода, я использую это решение:

private mIsSpinnerFirstCall=true;

...
Spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        //If a new value is selected (avoid activating on setSelection())
        if(!mIsSpinnerFirstCall) {
            // Your code goes gere
        }
        mIsSpinnerFirstCall = false;
    }

    public void onNothingSelected(AdapterView<?> arg0) {
    }
});

Это решение действительно, когда вы уверены, что используется Spinner.setSelection(position). Также важно устанавливать mIsSpinnerFirstCall=true каждый раз перед использованием Spinner.setSelection(position)

person Ivo Stoyanov    schedule 22.10.2014

В моем случае, поскольку я запускаю счетчик программно, мне просто нужно добавить флаг spinnerSelected после spinner.performClick(), как показано ниже.

private var spinnerSelected = false

someView.setOnClickListener {
    spinner.performClick()
    spinnerSelected = true
}

spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
    override fun onNothingSelected(parent: AdapterView<*>?) {
        // do nothing
    }

    override fun onItemSelected(
        parent: AdapterView<*>?,
        view: View?,
        position: Int,
        id: Long
    ) {
        if (spinnerSelected) {
            //... do something

            spinnerSelected = false
        }
      }
    }
person elbert rivas    schedule 30.01.2020