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

157

Я создал приложение 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

    }
}
Грант
источник
У спиннеров всегда есть выбранное значение по умолчанию. Если вы хотите иметь спиннер без выбранного значения по умолчанию, вы должны создать свой собственный спиннер или создать собственный спиннер с пустой записью, а в методе getView () измените видимость необработанный макет GONE
Houcine
5
Почему такая досадная ошибка все еще существует в конце 2018 года ???
user-123

Ответы:

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

Так что в первый раз обработайте это с любым значением Integer

Пример: изначально взять 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);
   }
}

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

Аби
источник
2
Удивительный человек ... Когда я впервые столкнулся с этой проблемой, я попытался реализовать пользовательский спиннер ... пока он не работал .. но ваше решение работало как шарм .. Спасибо.
Sash_KP
1
Где вы декларируете чек? За пределами getView ()? Внутри viewHolder? Куда? попробовал ваше решение, но у меня не работает.
user3718908
10
Я думаю, это патч, а не решение.
Сакшам
1
Я сталкиваюсь с той же проблемой. Это какая-то ошибка? также, если я выбираю один и тот же элемент повторно, слушатель не работает после первого раза. Работает только при изменении элемента выбора. Любой комментарий, помощь?
Амитава
1
Я пробовал это решение с той же проблемой, но это вызывает только один, например: когда у меня есть 2 элемента в выпадающем списке, он просыпается, когда я выбираю любой элемент счетчика 1 раз, когда я пытаюсь 2й раз, он не работает должным образом
Waseem
117

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

spinner.setSelection(0,false)
Даянанд Вагмаре
источник
7
Было бы лучше ответить, если бы вы написали, как это помогает и почему.
user3533716
1
Это помогает, но хочешь как?
AEMLoviji
14
Это работает, потому что вы сначала устанавливаете выбор, а затем добавляете слушателя, но слушатель не будет вызываться, потому что вы уже выбрали этот выбор раньше. Только новые выборы будут вызывать слушателя.
Android-разработчик
4
Это работает, потому что setSelection(int, boolean)вызывает setSelectionInt()внутри, и вам нужно установить слушателя после (а не до) вызова этого. Остерегайтесь, setSelection(int)это не сработает, потому что это звонки setNextSelectedPositionInt()внутри, и это то, что привело меня сюда.
Хай Чжан
3
Это не работает, если оно объявлено во время или перед onCreateView (). OnItemSelected будет вызываться.
Араш
62

Начиная с уровня 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. Работает как шарм.

Билл Моте
источник
3
Хороший законопроект ... Я думаю, что это лучшее решение, чем решение, принятое в качестве ответа
Ritesh Gune
откуда вы берете setmPreviousSelectedIndex?!?!
TheQ
Опечатка? :) setPreviousSelectedIndex ()
Билл Мот
4
Это не будет работать в случае фрагментов гнезда, так как onUserInteraction - метод Activity. Любое другое решение?
Chitrang
1
@ErikB НВМ, разобрался. Установка значения false внутри слушателя работает нормально.
Сикандер
20

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

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

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

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

saksham
источник
11

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

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Treesouth
источник
Хороший! Ничто не сработало для меня, что бы я ни применял раньше, но этот сработал для меня как шарм. Спасибо @TreeSouth
Дипика Лалра
11
У меня работал spinner.setSelection (position, false), используемый таким же образом. При использовании метода setSelection (position) слушатель вызывался во время инициализации.
Марио Кутлев
2
@HugoGresse Попробуйте вызвать spinner.setSelection (0, false); , Дело в том, что теперь он будет игнорировать выбор этой позиции, потому что она уже выбрана
разработчик Android
8

Мое решение:

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

        }
codareee
источник
7

Чтобы избежать вызова 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) {

    }
});
Кетан Рамани
источник
6

Вы можете сделать это следующим образом:

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

        }
    });

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

чарльстон
источник
3

Затем флаг взаимодействия с пользователем может быть установлен в значение 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
        }
    }

}

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

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Ранджит Кумар
источник
2

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

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

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.

}

Джонатан Коул
источник
2

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

private boolean inispinner;

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

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

        }
    });
Афжалур Рахман Рана
источник
2

Попробуй это

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Убирахара Эрталь
источник
1

Вы можете достичь этого, сначала установив setOnTouchListener, а затем установив onOouch в SetOnItemSelectedListener.

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

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

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
Khushal
источник
1

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

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)
        }
    }
}
Челентано
источник
0

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

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

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

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 после обновления.

RoyBS
источник
0

Код

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

            }
        });
Месси
источник
0

Для меня решение Абхи прекрасно работает до уровня 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
            }
        }

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

Янник Б
источник
0

Я поместил 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, но мне действительно не нравится мысль, что он будет вызываться всегда.

iOS_Mouse
источник