Как использовать Android Spinner как выпадающий список - PullRequest
20 голосов
/ 02 ноября 2011

Мне потребовалось много времени, чтобы обойти Android Spinner.После нескольких неудачных попыток реализации и после прочтения многих вопросов, частично похожих на мои, но без удовлетворительных ответов , а некоторые вообще без каких-либо ответов, например здесь и здесь Я наконец понял, что «спиннер» в Android не означает то же самое, что «выпадающий список» из настольных приложений или выбор в HTML.Тем не менее, то, что нужно моему приложению (и я предполагаю, что приложения всех других авторов, чьи вопросы похожи) - это то, что работает как выпадающий список, а не как блесна.

Две мои проблемыс тем, что я сначала считал особенностями OnItemSelectedListener (я видел их как отдельные вопросы на этом сайте, но не как один):

  1. Первоначальный выбор первого элемента списка запускается автоматически безвзаимодействие с пользователем.
  2. Когда элемент, который уже был выбран, снова выбирается пользователем, он игнорируется.

Теперь я понимаю, что когда вы думаете об этом, имеет смыслчтобы это произошло на прядильщике - он должен начинаться с выбранного значения по умолчанию, и вы вращаете его только для изменения этого значения, а не для «повторного выбора» значения - документация фактически говорит: «Этот обратный вызов вызывается только тогда, когда новая выбранная позиция отличается от ранее выбранной позиции».И я видел ответы, предлагающие установить флаг , чтобы игнорировать первый автоматический выбор - я думаю, я мог бы жить с этим, если нет другого пути.

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

Итак ... есть ли в Android что-то, что может это сделать, или какой-то обходной путь, чтобы Spinner вел себя как выпадающий список?Если на этом сайте есть вопрос, подобный этому, который я не нашел и на который есть удовлетворительный ответ, пожалуйста, дайте мне знать (в таком случае я искренне извиняюсь за повторение вопроса).

Ответы [ 6 ]

8 голосов
/ 02 ноября 2011

+ 1 к ответу Дэвида.Тем не менее, вот предложение по реализации, которое не включает в себя копирование кода из исходного кода (который, кстати, выглядит точно так же, как Дэвид опубликовал в 2.3, а также ):

@Override
void setSelectionInt(int position, boolean animate) {
    mOldSelectedPosition = INVALID_POSITION;
    super.setSelectionInt(position, animate);
}

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

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

5 голосов
/ 02 ноября 2011

Хорошо, я думаю, что нашел решение для моей собственной ситуации с помощью ответа Дэвида и Феликса (я думаю, Дэвид помог Феликсу, который, в свою очередь, помог моему). Я подумал опубликовать его здесь вместе с примером кода на тот случай, если кто-то найдет такой подход полезным. Это также решает обе мои проблемы (как нежелательный автоматический выбор, так и требуемый триггер повторного выбора).

Что я сделал, так это добавление фиктивного элемента ", пожалуйста, выберите " в качестве первого элемента в моем списке (изначально просто для того, чтобы обойти проблему с автоматическим выбором, чтобы я мог ее игнорировать, когда она была выбрана без взаимодействие с пользователем), а затем, когда выбран другой элемент и я обработал выделение, я просто сбрасываю счетчик на фиктивный элемент (который игнорируется). Если подумать, мне следовало бы подумать об этом давным-давно, прежде чем я решил разместить свой вопрос на этом сайте, но в ретроспективе вещи всегда более очевидны ... и я обнаружил, что написание своего вопроса действительно помогло мне подумать о том, что Я хотел достичь.

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

Во-первых, список действий (в моем случае), содержащий счетчик, назовем его MyListActivity:

public class MyListActivity extends ListActivity {

    private Spinner mySpinner;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // TODO: other code as required...

        mySpinner = (Spinner) findViewById(R.id.mySpinner);
        mySpinner.setAdapter(new MySpinnerAdapter(this));
        mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> aParentView,
                        View aView, int aPosition, long anId) {
                if (aPosition == 0) {
                    Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
                } else {
                    Log.d(getClass().getName(), "Handling selection of actual list item...");
                    // TODO: insert code to handle selection
                    resetSelection();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> anAdapterView) {
                // do nothing
            }
        });
    }

    /**
     * Reset the filter spinner selection to 0 - which is ignored in
     * onItemSelected() - so that a subsequent selection of another item is
     * triggered, regardless of whether it's the same item that was selected
     * previously.
     */
    protected void resetSelection() {
        Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
        mySpinner.setSelection(0);
    }
}

И код адаптера счетчика может выглядеть примерно так (фактически, если хотите, это может быть внутренний класс в приведенном выше списке действий):

public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {

    private List<MyListItem> items; // replace MyListItem with your model object type
    private Context context;

    public MySpinnerAdapter(Context aContext) {
        context = aContext;
        items = new ArrayList<MyListItem>();
        items.add(null); // add first dummy item - selection of this will be ignored
        // TODO: add other items;
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public Object getItem(int aPosition) {
        return items.get(aPosition);
    }

    @Override
    public long getItemId(int aPosition) {
        return aPosition;
    }

    @Override
    public View getView(int aPosition, View aView, ViewGroup aParent) {
        TextView text = new TextView(context);
        if (aPosition == 0) {
            text.setText("-- Please select --"); // text for first dummy item
        } else {
            text.setText(items.get(aPosition).toString());
            // or use whatever model attribute you'd like displayed instead of toString()
        }
        return text;
    }
}

Я думаю (не пробовал этого) того же эффекта можно было бы достичь, используя setSelected(false) вместо setSelection(0), но повторная установка "пожалуйста, выберите" подходит моим целям. И "смотри, Ма, нет флага!" (Хотя я думаю, игнорирование 0 выборов не так уж и отличается.)

Надеюсь, это может помочь кому-то еще с аналогичным вариантом использования. :-) Для других случаев использования ответ Феликса может быть более подходящим (спасибо Феликсу!).

4 голосов
/ 02 ноября 2011

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

Класс Spinner является производным отAbsSpinner.Внутри этого есть следующий метод:

void setSelectionInt(int position, boolean animate) {
        if (position != mOldSelectedPosition) {
            mBlockLayoutRequests = true;
            int delta  = position - mSelectedPosition;
            setNextSelectedPositionInt(position);
            layout(delta, animate);
            mBlockLayoutRequests = false;
        }
    }

Это AFAIK взято из 1.5 источника .Возможно, вы могли бы проверить этот источник, посмотреть, как работает Spinner / AbsSpinner, и, возможно, расширить этот класс настолько, чтобы поймать правильный метод, а не проверить, если position != mOldSelectedPosition.

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

Желаю вам удачи!

2 голосов
/ 21 августа 2012

Изменение Spinner полезно, если вы хотите иметь несколько вариантов выбора одновременно в одном и том же упражнении.Если вы хотите, чтобы у пользователя был только иерархический выбор, например:

Что вы хотите съесть?

Фрукты

  • Яблоки
  • Бананы
  • Апельсины

Фаст-фуд

  • Бургеры
  • Картофель фри
  • Горячиесобаки,

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

1 голос
/ 12 февраля 2015

Вот альтернативное решение для различия между любыми (преднамеренными или непреднамеренными) программными и инициированными пользователем изменениями:

Создайте прослушиватель для прядильщика как 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);

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

В любом случае, проблемы, на которые указывал Амос, сводили меня с ума, прежде чем подумать об этом решении, поэтому я подумал:буду делиться как можно шире.Есть много обсуждений, которые обсуждают это, но я видел только одно другое решение, похожее на это: https://stackoverflow.com/a/25070696/4556980.

0 голосов
/ 20 августа 2014

Я работал над несколькими проблемами, упомянутыми в этой теме, прежде чем понял, что виджет PopupMenu - это то, что я действительно хотел. Это было легко реализовать без хаков и обходных путей, необходимых для изменения функциональности Spinner. PopupMenu был относительно новым, когда этот поток был запущен в 2011 году, но я надеюсь, что это поможет кому-то, ищущему подобную функциональность сейчас.

...