Android Spinner: избегайте вызовов onItemSelected во время инициализации

Я создал приложение для Android с Spinner и TextView. Я хочу отобразить выбранный элемент из раскрывающегося списка Spinner в TextView. Я реализовал Spinner в методе onCreate, поэтому, когда я запускаю программу, он показывает значение в TextView (перед выбором элемента из раскрывающегося списка).

Я хочу отображать значение в TextView только после выбора элемента из раскрывающегося списка. Как мне это сделать?

Вот мой код:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class GPACal01Activity extends Activity implements OnItemSelectedListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Spinner spinner = (Spinner) findViewById(R.id.noOfSubjects);

        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,R.array.noofsubjects_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);
    }

    public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
        TextView textView = (TextView) findViewById(R.id.textView1);
        String str = (String) parent.getItemAtPosition(pos);
        textView.setText(str);
    }

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

    }
}

person Grant    schedule 15.11.2012    source источник
comment
прядильщики всегда имеют выбранное значение по умолчанию, если вы хотите иметь счетчик без выбранного значения по умолчанию, вы должны создать свой собственный счетчик или создать собственный счетчик с пустой записью, а в методе getView () изменить видимость необработанный макет в GONE   -  person Houcine    schedule 15.11.2012
comment
Почему такая досадная ошибка все еще существует в конце 2018 года ???   -  person user-123    schedule 10.11.2018
comment
@ user-123 То есть 2020 год? ;)   -  person musooff    schedule 02.09.2020
comment
@musooff ты про 2021 год?   -  person Prasad Pawar    schedule 15.03.2021


Ответы (19)


spinner.setOnItemSelectedListener(this); // Will call onItemSelected() Listener.

Поэтому в первый раз обработайте это с любым целочисленным значением

Пример: изначально взять int check = 0;

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
   if(++check > 1) {
      TextView textView = (TextView) findViewById(R.id.textView1);
      String str = (String) parent.getItemAtPosition(pos);
      textView.setText(str);
   }
}

Вы можете сделать это с логическим значением, а также проверив текущую и предыдущую позиции. См. здесь

person Abhi    schedule 15.11.2012
comment
Потрясающий человек ... когда я впервые столкнулся с этой проблемой, я попытался реализовать собственный счетчик ... но это не сработало ... но ваше решение сработало как шарм ... Спасибо. - person Sash_KP; 26.08.2014
comment
Сомневаюсь: стоит ли использовать - TextView textView = (TextView) findViewById (R.id.textView1); или TextView textView = (TextView) arg1.findViewById (R.id.textView1) ;? - person Narendra Singh; 02.07.2015
comment
TextView textView = (TextView) findViewById (R.id.textView1); Используйте его, потому что TextView и Spinner будут в одном макете. - person Abhi; 03.07.2015
comment
Я использовал логическое значение в качестве флага .. это решение флага сработало для меня .. Спасибо @Abhi - person Shailendra Madda; 17.03.2017
comment
Где декларировать чек? Вне getView ()? Внутри viewHolder? Где? попробовал ваше решение, но у меня не работает. - person user3718908; 20.04.2018
comment
это патч, а не решение, я думаю. - person saksham; 14.06.2018
comment
К сожалению, проблема в том, что фреймворк вызывает это при инициализации Spinner с позицией 0, которая является первым элементом. - person Nabster; 26.10.2018
comment
Я столкнулся с той же проблемой. Это какая-то ошибка? также, если я выберу один и тот же элемент несколько раз, прослушиватель не будет работать с первого раза. Работает только при изменении элемента выбора. Любые комментарии, помощь? - person amitava; 01.11.2018
comment
Я пробовал это решение с той же проблемой, но это работает только с одним, например: когда у меня есть 2 элемента в раскрывающемся списке, он срабатывает, когда я выбираю любой элемент прядильщика в первый раз, когда я пытаюсь второй раз, он не работает должным образом - person Waseem; 07.12.2018
comment
Когда я применяю этот процесс с помощью koltin, мое приложение уничтожается, я работаю с счетчиком custm. - person Jorge Leonardo; 17.09.2020

Просто поместите эту строку перед набором OnItemSelectedListener

spinner.setSelection(0,false)
person Dayanand Waghmare    schedule 01.06.2016
comment
Будет лучше, если вы напишете, как это помогает и почему. - person user3533716; 14.06.2016
comment
Это помогает, но хотите как? - person AEMLoviji; 18.07.2016
comment
Это определенно лучший ответ. - person Rohit5k2; 03.11.2016
comment
@androiddeveloper в чем недостаток ?? - person Akshay; 01.06.2017
comment
Это работает, потому что вы сначала устанавливаете выбор, а затем добавляете слушателя, но слушатель не будет вызываться, потому что вы уже выбрали этот выбор раньше. Только новые выборы будут вызывать слушателя. - person android developer; 01.06.2017
comment
Это работает, потому что setSelection(int, boolean) вызывает setSelectionInt() внутри, и вам нужно установить слушателя после (а не до) этого вызова. Помните, что setSelection(int) не будет работать, потому что он вызывает setNextSelectedPositionInt() внутри, и это то, что меня сюда привело. - person Hai Zhang; 07.04.2018
comment
Это не работает, если оно объявлено во время или до onCreateView (). onItemSelected. - person Arash; 20.04.2018
comment
добавление false в setSelection() устранило мою проблему, большое спасибо! - person Boy; 03.05.2018
comment
Работает как шарм! - person Santiago; 22.02.2019
comment
Согласитесь с @Bubu, это должен быть исключительный ответ. Это правильное решение. Ответ от Abhi работает, но это просто обходной путь. - person Smeagol; 29.04.2019
comment
Отличное решение! У меня работает по крайней мере при заявлении на onViewCreated() - person Sergey Stasishin; 02.12.2019
comment
как-то мне нужно установить itemSelectedListener внутри сообщения, чтобы он работал. Не знаю почему. - person Dark Leonhart; 08.01.2020
comment
Он работал на некоторых устройствах, но, например, на В Pixel 4 XL настройка кода SelectionListener вызывается слишком быстро, поэтому мне потребовалась работа с логическим значением. - person lex; 30.03.2020
comment
@androiddeveloper Даже близко. Просто удалите флаг animate или установите его на true, и вы увидите, что ваш слушатель по-прежнему запускается независимо от того, в каком порядке вы установили свой выбор и слушателя. - person Farid; 11.07.2020
comment
@Farid Похоже, в прошлом его поведение изменилось, или что-то в этом роде. Уверен, я это проверил. В противном случае другие не проголосовали бы за него. - person android developer; 12.07.2020
comment
Да, это отличное решение. - person Ramkesh Yadav; 29.09.2020
comment
Это, безусловно, лучший ответ. - person neeohw; 26.11.2020
comment
Я до сих пор не понимаю, как логическое значение, которое должно влиять на анимацию, влияет на onItemSelectedListener ... setSelection (0) запускает прослушиватель, setSelection (0, false) - нет. Где false просто означает не анимировать ... андроид всегда поражает меня - person kristyna; 16.03.2021

Начиная с уровня API 3, вы можете использовать onUserInteraction () для Activity с логическим значением, чтобы определить, взаимодействует ли пользователь с устройством.

http://developer.android.com/reference/android/app/Activity.html#onUserInteraction()

@Override
public void onUserInteraction() {
     super.onUserInteraction();
     userIsInteracting = true;
}

В качестве поля деятельности у меня есть:

 private boolean userIsInteracting;

Наконец, мой спиннер:

      mSpinnerView.setOnItemSelectedListener(new OnItemSelectedListener() {

           @Override
           public void onItemSelected(AdapterView<?> arg0, View view, int position, long arg3) {
                spinnerAdapter.setmPreviousSelectedIndex(position);
                if (userIsInteracting) {
                     updateGUI();
                }
           }

           @Override
           public void onNothingSelected(AdapterView<?> arg0) {

           }
      });

Когда вы приходите и проходите действие, логическое значение сбрасывается на false. Работает как шарм.

person Bill Mote    schedule 31.07.2014
comment
Хороший Билл .. Я думаю, что это лучшее решение, чем то, что было принято в качестве ответа. - person Ritesh Gune; 29.04.2016
comment
где взять setmPreviousSelectedIndex?!?! - person TheQ; 24.05.2016
comment
Опечатка? :) setPreviousSelectedIndex () - person Bill Mote; 24.05.2016
comment
Это не сработает в случае вложенных фрагментов, поскольку onUserInteraction - это метод Activity. Любое другое решение? - person Chitrang; 16.09.2017
comment
Где мне установить для userIsIntecting значение false? У меня на экране несколько спиннеров - person Sikander; 22.02.2018
comment
Это работает, за исключением сценария, я не могу подобрать сам первый элемент, я хочу, если я выберу начальный элемент прядильщика (тогда я также взаимодействую), но он вообще не вызывает. - person B.shruti; 06.06.2018
comment
@Chitrang вы можете передать изменяемое логическое значение. - person Erik B; 18.08.2018
comment
@Sikander, вам нужно установить значение false? Возможно, в onCreate перед добавлением кого-либо из слушателей (я считаю, что именно в этом проблема)? - person Erik B; 18.08.2018
comment
@ErikB nvm, разобрался. Установка false внутри слушателя работает нормально. - person Sikander; 19.08.2018
comment
@ErikB Я хотел бы знать, взаимодействовал ли пользователь с фрагментом, а не с действием. Действие может иметь несколько фрагментов, поэтому он мог ранее взаимодействовать с Activity в другом фрагменте. - person Vadim Kotov; 02.08.2019

Это сработало для меня

Инициализация Spinner в Android проблематична, иногда указанная выше проблема решалась с помощью этого шаблона.

Spinner.setAdapter();
Spinner.setSelected(false);  // must
Spinner.setSelection(0,true);  //must
Spinner.setonItemSelectedListener(this);

Адаптер настройки должен быть первой частью, а onItemSelectedListener (this) будет последним при инициализации счетчика. По шаблону выше мой OnItemSelected () не вызывается во время инициализации счетчика

person saksham    schedule 23.06.2017

ха-ха ... У меня такой же вопрос. Когда initViews () делает именно так: последовательность является ключом, а слушатель - последним. Удачи !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
person Treesouth    schedule 14.03.2015
comment
Неплохо! У меня ничего не получалось, что бы я ни применял раньше, но этот сработал для меня как шарм. Спасибо @TreeSouth - person Deepika Lalra; 14.07.2015
comment
У меня работал точно так же spinner.setSelection (position, false). С помощью метода setSelection (position) слушатель был вызван во время инициализации. - person Mario Kutlev; 12.10.2015
comment
@HugoGresse Попробуйте вызвать spinner.setSelection (0, false); . Дело в том, что теперь он проигнорирует выбор этой позиции, потому что она уже выбрана - person android developer; 05.12.2015

Чтобы избежать вызова spinner.setOnItemSelectedListener () во время инициализации

spinner.setSelection(Adapter.NO_SELECTION, true); //Add this line before setting listener
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
});
person Ketan Ramani    schedule 28.11.2018

Мое решение:

protected boolean inhibit_spinner = true;


@Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                int pos, long arg3) {

            if (inhibit_spinner) {
                inhibit_spinner = false;
            }else {

            if (getDataTask != null) getDataTask.cancel(true);
            updateData();
            }

        }
person codareee    schedule 23.09.2013

Сделать это можно так:

AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            //set the text of TextView
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            yourSpinner.setOnItemSelectedListener(listener);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

Сначала я создаю слушателя и приписываю ему обратный вызов переменной; затем я создаю второго анонимного слушателя, и когда он вызывается в первый раз, это меняет слушателя =]

person Charleston    schedule 11.06.2014

создать логическое поле

private boolean inispinner;

внутри oncreate деятельности

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (!inispinner) {
                inispinner = true;
                return;
            }
            //do your work here
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });
person Afjalur Rahman Rana    schedule 28.02.2019

Затем для флага взаимодействия с пользователем можно установить значение true в методе onTouch и сбросить в onItemSelected() после обработки изменения выбора. Я предпочитаю это решение, потому что флаг взаимодействия с пользователем обрабатывается исключительно для счетчика, а не для других представлений в действии, которые могут повлиять на желаемое поведение.

В коде:

Создайте свой слушатель для счетчика:

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) { 
            userSelect = false;
            // Your selection handling code here
        }
    }

}

Добавьте слушателя к счетчику как OnItemSelectedListener и OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
person Ranjith Kumar    schedule 07.03.2017

Аналогичное простое решение, которое позволяет использовать несколько счетчиков, - это поместить AdapterView в коллекцию - в суперкласс Activity - при первом выполнении onItemSelected (...). Затем проверьте, есть ли AdapterView в коллекции, прежде чем выполнять ее. Это позволяет использовать один набор методов в суперклассе и поддерживает несколько AdapterView и, следовательно, несколько счетчиков.

Суперкласс ...

private Collection<AdapterView> AdapterViewCollection = new ArrayList<AdapterView>();

   protected boolean firstTimeThrough(AdapterView parent) {
    boolean firstTimeThrough = ! AdapterViewCollection.contains(parent);
    if (firstTimeThrough) {
       AdapterViewCollection.add(parent);
     }
    return firstTimeThrough;
   }

Подкласс ...

public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
      if (! firstTimeThrough(parent)) {
        String value = safeString(parent.getItemAtPosition(pos).toString());
        String extraMessage = EXTRA_MESSAGE;
        Intent sharedPreferencesDisplayIntent = new         Intent(SharedPreferencesSelectionActivity.this,SharedPreferencesDisplayActivity.class);
    sharedPreferencesDisplayIntent.putExtra(extraMessage,value);
    startActivity(sharedPreferencesDisplayIntent);
  }
  // don't execute the above code if its the first time through
  // do to onItemSelected being called during view initialization.

}

person Jonathan Cole    schedule 18.07.2013

Попробуй это

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
person Ubirajara Erthal    schedule 21.05.2019

Код

spinner.setOnTouchListener(new View.OnTouchListener() { 
@Override public boolean onTouch(View view, MotionEvent motionEvent) { isSpinnerTouch=true; return false; }});

holder.spinner_from.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int slot_position, long l) {
                if(isSpinnerTouch)
                {
                    Log.d("spinner_from", "spinner_from");
                    spinnerItemClickListener.onSpinnerItemClickListener(position, slot_position, AppConstant.FROM_SLOT_ONCLICK_CODE);
                }
                else {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
person Messi    schedule 17.10.2019

Вы можете добиться этого с помощью сначала setOnTouchListener, а затем setOnItemSelectedListener в onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
person vviieett    schedule 19.11.2015
comment
Мне это нравится. Несмотря на то, что он создает нового слушателя каждый раз, когда пользователь касается представления. Поэтому я предпочитаю кэшировать первый созданный слушатель и повторно использовать его. - person Vlad; 13.11.2018

Это сработало для меня:

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
person Khushal    schedule 27.09.2019

Основываясь на ответе Абхи, я сделал этого простого слушателя

class SpinnerListener constructor(private val onItemSelected: (position: Int) -> Unit) : AdapterView.OnItemSelectedListener {

    private var selectionCount = 0

    override fun onNothingSelected(parent: AdapterView<*>?) {
        //no op
    }

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        if (selectionCount++ > 1) {
           onItemSelected(position)
        }
    }
}
person AdrianoCelentano    schedule 06.10.2019

Была такая же проблема, и это работает для меня:

У меня есть 2 счетчика, и я обновляю их во время инициализации и во время взаимодействия с другими элементами управления или после получения данных с сервера.

Вот мой шаблон:

public class MyClass extends <Activity/Fragment/Whatever> implements Spinner.OnItemSelectedListener {

    private void removeSpinnersListeners() {
        spn1.setOnItemSelectedListener(null);
        spn2.setOnItemSelectedListener(null);
    }

    private void setSpinnersListeners() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spn1.setOnItemSelectedListener(MyClass.this);
                spn2.setOnItemSelectedListener(MyClass.this);
            }
        }, 1);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        // Your code here
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

Когда класс запускается, используйте setSpinnersListeners () вместо прямой установки слушателя.

Runnable предотвратит запуск счетчика onItemSelected сразу после того, как вы установите их значения.

Если вам нужно обновить счетчик (после вызова сервера и т. Д.), Используйте removeSpinnersListeners () прямо перед строками обновления и setSpinnersListeners () сразу после строк обновления. Это предотвратит запуск onItemSelected после обновления.

person RoyBS    schedule 22.10.2015

Для меня решение Abhi отлично работает до уровня Api 27.

Но кажется, что с уровня Api 28 и выше onItemSelected () не вызывается, когда установлен слушатель, что означает, что onItemSelected () никогда не вызывается.

Поэтому я добавил короткий оператор if для проверки уровня Api:

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {

            if(Build.VERSION.SDK_INT >= 28){ //onItemSelected() doesn't seem to be called when listener is set on Api 28+
                check = 1;
            }

            if(++check > 1) {
                //Do your action here
            }
        }

Я думаю, что это довольно странно, и я не уверен, что у других тоже есть эта проблема, но в моем случае это сработало.

person Jannik B    schedule 05.03.2020

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

Мой код Kotlin выглядит примерно так:

private var mySpinnerHasBeenTapped = false

private fun initializeMySpinner() {

    my_hint_text_view.setOnClickListener {
        mySpinnerHasBeenTapped = true //turn flag to true
        my_spinner.performClick() //call spinner click
    }

    //Basic spinner setup stuff
    val myList = listOf("Leonardo", "Michelangelo", "Rafael", "Donatello")
    val dataAdapter: ArrayAdapter<String> = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, myList)
    my_spinner.adapter = dataAdapter

    my_spinner.onItemSelectedListener = object : OnItemSelectedListener {

        override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {

            if (mySpinnerHasBeenTapped) { //code below will only run after the user has clicked
                my_hint_text_view.visibility = View.GONE //once an item has been selected, hide the textView
                //Perform action here
            }
        }

        override fun onNothingSelected(parent: AdapterView<*>?) {
            //Do nothing
        }
    }
}

Файл макета выглядит примерно так, при этом важная часть состоит в том, что Spinner и TextView имеют одинаковую ширину, высоту и поля:

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true" />

            <TextView
                android:id="@+id/my_hint_text_view"
                android:layout_width="match_parent"
                android:layout_height="35dp"                
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true"
                android:gravity="center_vertical"
                android:text="*Select A Turtle"
                android:textColor="@color/green_ooze"
                android:textSize="16sp" />

        </FrameLayout>

Я уверен, что другие решения работают там, где вы игнорируете первый вызов onItemSelected, но мне действительно не нравится идея предполагать, что он всегда будет вызываться.

person iOS_Mouse    schedule 30.06.2020