Как использовать Android Spinner как раскрывающийся список
Мне потребовалось некоторое время, чтобы окунуться в Android Spinner. После нескольких неудачных попыток реализации и после прочтения многих вопросов, частично похожих на мои, но без удовлетворительных ответов, а некоторые без каких-либо ответов вообще, например. здесь и здесь, я наконец понял, что "spinner" в Android не должен быть таким же как "раскрывающийся список" из настольных приложений или выбрать в HTML. Однако то, что мое приложение (и я догадываюсь о приложениях всех других плакатов, чьи вопросы похожи) нуждается в том, что работает как раскрывающийся список, а не как spinner.
Мои две проблемы связаны с тем, что я сначала считал идиосинхронизацией OnItemSelectedListener (я видел их как отдельные вопросы на этом сайте, но не как один):
- Начальный выбор первого элемента списка запускается автоматически без взаимодействия с пользователем.
- Когда элемент, который уже был выбран, снова выбран пользователем, он игнорируется.
Теперь я понимаю, что, когда вы думаете об этом, имеет смысл это произойти на счетчике - он должен начинаться с выбранного значения по умолчанию, и вы вращаете его только для изменения этого значения, а не для "выберите" значение - документация на самом деле говорит: "Этот обратный вызов вызывается только тогда, когда вновь выбранная позиция отличается от ранее выбранной позиции". И я видел ответы, предлагающие установить флажок, чтобы игнорировать первый автоматический выбор - я думаю, я мог бы жить с этим, если нет другого пути.
Но так как я действительно хочу, это раскрывающийся список, который ведет себя как раскрывающийся список (и, как пользователи могут и должны ожидать), мне нужно что-то вроде Spinner, который ведет себя как раскрывающийся список, как комбинированный ящик. Меня не волнует какой-либо автоматический предварительный выбор (это должно произойти без запуска моего слушателя), и я хочу знать о каждом выборе, даже если он тот же, что и ранее (в конце концов, пользователь снова выбрал тот же элемент).
Итак... есть ли что-то в Android, которое может это сделать, или какое-то обходное решение, чтобы заставить Spinner вести себя как раскрывающийся список? Если на этом сайте есть такой вопрос, который я не нашел и который имеет удовлетворительный ответ, сообщите мне (в этом случае я искренне извиняюсь за повторение вопроса).
Ответы
Ответ 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 полезно, если вы хотите иметь несколько вариантов одновременно в одном и том же действии.
Если вы хотите, чтобы у пользователя был иерархический выбор, например:
Что вы хотите съесть?
Фрукты
Фаст-фуд
тогда 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 году, но я надеюсь, что это поможет кому-то найти аналогичную функциональность.