Как сохранить OnItemSelected от запуска только что созданного Spinner? - PullRequest
396 голосов
/ 01 апреля 2010

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

Мой 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.
    }
}
}

Ответы [ 33 ]

7 голосов
/ 29 июля 2015

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

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

            });

        }
    });
4 голосов
/ 17 августа 2016

Я получил очень простой ответ, 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
}
4 голосов
/ 20 апреля 2017

Это произойдет, если вы делаете выбор в коде как;

   mSpinner.setSelection(0);

Вместо приведенного выше утверждения используйте

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

Редактировать: Этот метод не работает для Mi Android версии Mi UI.

3 голосов
/ 08 февраля 2013

Я нашел гораздо более элегантное решение для этого. Он включает в себя подсчет того, сколько раз был вызван 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
}
1 голос
/ 27 октября 2014

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

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

Эта оболочка также решит проблему с вызовом слушателя во время создания макета.

Веселись, Jens.

1 голос
/ 21 июня 2013

Мой небольшой вклад - это вариация некоторых из вышеперечисленных, которая меня устраивала несколько раз.

Объявите целочисленную переменную как значение по умолчанию (или последнее использованное значение, сохраненное в настройках). Используйте spinner.setSelection (myDefault), чтобы установить это значение до регистрации слушателя. В onItemSelected проверьте, соответствует ли новое значение счетчика значению, которое вы присвоили, прежде чем выполнять какой-либо дополнительный код.

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

1 голос
/ 23 ноября 2017

Уже много ответов, вот мой.

Я расширяю 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
}
1 голос
/ 24 августа 2015

Поскольку у меня ничего не получалось, и у меня больше одного блесна в моем представлении (и ИМХО проведение карты 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) {
        }
    });
0 голосов
/ 13 апреля 2015

У меня сделано самым простым способом:

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

        }
    });

1012 * Совершено *

0 голосов
/ 18 октября 2017

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

Макет XML-файла

    <layout>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">


<Spinner
    android:id="@+id/spinner"
    android:layout_width="150dp"
    android:layout_height="wrap_content"
    android:spinnerMode="dropdown"
    android:layout_below="@id/member_img"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:background="@drawable/member_btn"
    android:padding="@dimen/activity_horizontal_margin"
    android:layout_marginStart="@dimen/activity_horizontal_margin"
    android:textColor="@color/colorAccent"
    app:position="@{0}"
    />
 </RelativeLayout>
 </layout>

app:position - это место, где вы проходите позицию для выбора.

Пользовательская привязка

  @BindingAdapter(value={ "position"}, requireAll=false)
  public static void setSpinnerAdapter(Spinner spinner, int selected) 
  {

    final int [] selectedposition= new int[1];
    selectedposition[0]=selected;


    // custom adapter or you can set default adapter
        CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
        spinner.setAdapter(customSpinnerAdapter);
            spinner.setSelection(selected,false);


    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

            String item = parent.getItemAtPosition(position).toString();
        if( position!=selectedposition[0]) {
                        selectedposition[0]=position;
            // do your stuff here
                    }
                }


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

        }
    });
}

Подробнее о пользовательской привязке данных вы можете прочитать здесь Пользовательский набор настроек Android

Примечание

  1. Не забудьте включить привязку данных в вашем файле Gradle

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
    
  2. Включите файлы макетов в <layout> теги

...