setSelected() работает с ошибками в ListView

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

Я вызываю view.setSelected() в OnItemClickListener адаптера, и он прекрасно применяет другой фон к выбранному элементу в соответствии с моими настройками. Но когда я выбираю элемент, из-за которого в ListView не хватает места (не важно, как именно), и полоса прокрутки появляется (или исчезает) внутри ListView - Android забывает мой выбор, и применяется стиль по умолчанию. Та же ошибка возникает при повороте экрана - элемент отменяет выбор. Поэтому я думаю, что «отмена выбора» происходит во время вызова адаптера getView().

Интересно, что мое событие onClick вызывает отправку запроса json в фоновую службу, а также получение и декодирование ответа json, поэтому между щелчком элемента и изменением содержимого действия требуется некоторое время. Вот как это выглядит:

  1. Я щелкаю элемент ListView. Он меняет фон на «выбранный цвет».
  2. Несколько мгновений жду.
  3. Содержание активности меняется в соответствии с ответом службы. Полоса прокрутки появляется внутри ListView. Фон элемента меняется на «цвет по умолчанию» (элемент не выбран).

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

Попытка вызвать setSelected() внутри getView() адаптера не влияет на ошибку. Элемент по-прежнему не выбран. Пробовал задавать фон элемента вручную в getView() - стало интереснее: элементы, вызывающие появление полосы прокрутки, стали работать корректно, а элементы, не вызывающие появление полосы прокрутки (на самом деле это означает, что они не вызывают вызов getView()) перестал работать!

Весь код достаточно сложен, поэтому я выложу лишь некоторые важные фрагменты. Вот мой OnItemClickListener:

private AdapterView.OnItemClickListener onCategoryClickListener =
        new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, final View view, final int position,
            long id) {
        categoriesAdapter.setSelectedPosition(position);
        view.setSelected(true);
        // More code here
    }
};

и вот фрагмент кода моего адаптера:

private int selectedPosition;
private boolean selectable = true;

public void setSelectedPosition(int position) {
    this.selectedPosition = position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    TextView label = (TextView) View.inflate(context, textViewResourceId, null);
    label.setText(getName(values.get(position)));
    if(selectable) {
        label.setBackgroundResource(R.drawable.list_selector);
        if(position == selectedPosition) {
            label.setSelected(true);  // This does not work. Why?
            label.setBackgroundColor(  // This gives strange results
                context.getResources().getColor(R.color.list_item_selected_color));
        } else {
            // Similar code here, but for deselecting items.
        }
    }
    return label;
}

и вот мой селектор:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@color/list_item_default_color"
        android:state_selected="false" />
    <item
        android:drawable="@color/list_item_selected_color"
        android:state_selected="true"/>
</selector>

Я много искал, как заставить его работать, но ничего не помогает. Вот некоторые вещи, которые я пробовал:

  • Запуск view.setSelected() внутри view.post()
  • Запуск list.setSelection() - для чего этот метод? это ничего не делает!
  • Инициализация TextView более точная, проверка, является ли convertView значением null. Дает плохие, очень плохие результаты - вызывает перемешивание элементов ListView (без какого-либо влияния на их статус выбора).
  • Не использовал ViewHolder, потому что у меня нет сложных макетов для Item, у меня есть простой TextView.

person Alexander Konotop    schedule 06.12.2014    source источник


Ответы (2)


Для API 11+:

1) Установите вид списка на один выбор:

<ListView
        android:choiceMode="singleChoice" />

2) Установите фон корневого элемента макета вашего элемента в свой селектор:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@drawable/selector">

3) Замените android:state_selected на android:state_activated:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/list_item_selected_color"
        android:state_activated="true"/>
    <item android:drawable="@color/list_item_default_color"/>
</selector>

4) Выделите элемент с помощью listView.setItemChecked(index, true);

Примечание. Чтобы прояснить состояние представления, особенно state_activated, вы можете проверить это сообщение; это интересно.

person tachyonflux    schedule 06.12.2014
comment
К сожалению, я использую API-7, и у него нет state_activated. - person Alexander Konotop; 07.12.2014
comment
Тогда я думаю, что вы правы в том, что ваш путь - единственный путь - person tachyonflux; 08.12.2014

Я немного запутался с цветами. После исправления некоторых глупых ошибок мой выбор работает надежно, устанавливая цвет фона непосредственно из OnItemClickListener и getView:

фиксированный OnItemClickListener:

public void onItemClick(AdapterView<?> parent, final View view, final int position,
        long id) {
    categoriesAdapter.setSelectedPosition(position);
    for (int j = 0; j < parent.getChildCount(); j++) {
        parent.getChildAt(j).setSelected(false);
        parent.getChildAt(j).setBackgroundColor(getContext().getResources().getColor(
            R.color.list_item_default_color));
    }
    view.setBackgroundColor(getContext().getResources().getColor(
        R.color.list_item_selected_color));

    // More code here
}

фиксированный адаптер:

private Integer selectedPosition;

public void setSelectedPosition(int position) {
    this.selectedPosition = position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    TextView label = (TextView) View.inflate(context, textViewResourceId, null);
    label.setText(getName(values.get(position)));
    if(selectedPosition != null) {
        if(position == selectedPosition) {
            label.setBackgroundColor(context.getResources()
                .getColor(R.color.list_item_selected_color));
        } else {
            label.setBackgroundColor(context.getResources()
                .getColor(R.color.list_item_default_color));
        }
    }
    return label;
}

Таким образом, способ сделать это с помощью <selector> не работает, потому что Android не может надежно переключать состояние виджета. Это правильно или у кого-то работает "<selector> способ"?

person Alexander Konotop    schedule 06.12.2014