Подтвердить что ты не робот

Как использовать Android Spinner как раскрывающийся список

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Во-первых, активность списка (в моем случае), содержащая счетчик, позвоните в 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 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 не отличается от этого.)

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

Ответ 3

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

Класс 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.

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

Я желаю вам удачи!

Ответ 4

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

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

Фрукты

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

Фаст-фуд

  • Бюргерса
  • Fries
  • Хот-доги,

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

Ответ 5

Вот альтернативное решение для различения любых (предполагаемых или непреднамеренных) программных и пользовательских изменений:

Создайте свой слушатель для счетчика как как 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.

В любом случае, проблемы, о которых говорил Амос, заставляли меня сходить с ума, прежде чем думать об этом решении, поэтому я думал, что поделюсь как можно шире. Есть много потоков, которые обсуждают это, но я видел только одно другое решение, подобное этому: fooobar.com/questions/19093/....

Ответ 6

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