Ответ 1
tl; dr Не устанавливайте по умолчанию Drawable в селекторе списка.
Эта проблема возникает, когда вы предоставляете селектору списка Defaultableable. Под этим я подразумеваю, что в определении селектора списка есть тег item
без требования к состоянию, что делает это item
непреднамеренно по умолчанию. Подробнее о селекторах здесь.
Код выбора вашего списка:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/item_pressed" />
<item
android:drawable="@drawable/item_selected" /> <-- this is what I'm referring to
</selector>
Исправить
Проблема, с которой вы столкнулись, вызвана тем, что селектор списков всегда рисован (даже если селектор не находится на экране). Обычно это не проблема, поскольку селектор списков прозрачен (и, следовательно, невидим). Однако, поскольку вы указали селектор списка фона по умолчанию, это означало, что всякий раз, когда переключатель селектора был на экране, он был бы видимым, что вызывало бы странное поведение, которое вы наблюдали. Вместо этого вы действительно хотите только показать этот фон, когда элемент действительно выбран.
Для этого сначала нужно удалить фон по умолчанию из селектора списков. Затем нам нужен новый способ указания выбранных элементов. Поскольку вы указали android:choiceMode="singleChoice"
в своем ListView, ListView будет обрабатывать ваши элементы списка, как список флажков. Таким образом, когда пользователь проверяет один из элементов, это активированное состояние будет установлено в true. Тем не менее, TextViews не будет показывать никаких визуальных эффектов при активации по умолчанию. Чтобы отобразить определенный фон при выборе, нам нужно использовать макет элемента списка, который может отображать активированное состояние. Один из способов сделать это - изменить фон представления элемента ListView на селектор и определить Drawable, который вы хотите использовать для активированного состояния.
Например:
Код адаптера:
ArrayAdapter adapter = ArrayAdapter.createFromResource(this,
R.array.Planets, R.layout.myitem);
myitem.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/item_background"
android:paddingTop="16dp"
android:paddingBottom="16dp"/>
item_background.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/item_selected" android:state_activated="true" />
</selector>
Или, если вы ленивы:
ArrayAdapter adapter = ArrayAdapter.createFromResource(this,
R.array.planets_array, android.R.layout.simple_list_item_activated_1);
Дальнейшее чтение
Из документации для Android не совсем очевидно, что то, что я сказал, верно, поэтому вы можете спросить, действительно ли мой ответ правдоподобен. Этот раздел посвящен тем, кто ищет надежный ответ и очень любопытно.
Чтобы понять, как работает селектор списков и как он запрограммирован, нам нужно будет погрузиться в исходный код Android. Для начала мясо логики ListView
фактически хранится в классе под названием AbsListView
(в случае, если у вас нет загруженного источника, вы можете обратиться к это). Копаясь в источнике этого класса, мы найдем несколько полезных полей/функций, относящихся к селектору:
-
mSelector
: Это Drawable селектора (тот, который вы указываете с помощьюandroid:listSelector
) -
mSelectorRect
: В этом поле определяется, где выполняется селектор, и насколько большой селектор -
mSelectedPosition
: хранит индекс выбранного элемента (это поле фактически объявлено еще глубже в классеAdapterView
) -
positionSelector(...)
: Обновления, в которых должен быть выведен селектор -
drawSelector(...)
: Рисует селектор -
trackMotionScroll(...)
: содержит логику поведения прокруткиListView
Теперь, когда у нас есть понимание среды, мы можем, наконец, понять основную логику для поведения селектора списков. Все это сводится к этим небольшим строкам кода в trackMotionScroll(...)
:
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
...
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
// if we are not in touch mode and there is a selected item then
// we do a quick check if the selected item is on screen
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
// if the selected item is on screen, we move the selector to
// where the selected item is
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
// if we are in touch mode and there is a selected item then
// we do a quick check if the selected item is on screen
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
// if the selected item is on screen, we move the selector to
// where the selected item is
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
// otherwise, if nothing is selected, hide the selector (don't draw it)
mSelectorRect.setEmpty();
}
...
}
Исходный фрагмент выше был отредактирован из оригинала для включения комментариев.
Здесь мы, наконец, находим логику, объясняющую наблюдаемое поведение: селектор списка только скрыт при mSelectorPosition == INVALID_POSITION
или, на английском языке, когда нет выбранных элементов. В противном случае он позиционируется в выбранном элементе, если элемент находится на экране, иначе никаких изменений в нем не будет.
Итак, когда вы прокручиваете ListView
, и выбранный элемент выключается, селектор списков просто остается в последнем месте, где выбранный элемент объяснял наблюдаемый селектор списка призраков.
Заключительные мысли
От работы с ListViews с момента его введения я должен сказать, что все это не очень хорошо спроектировано, и оно может быть чрезвычайно ошибкой. Я настоятельно рекомендую использовать его преемник RecyclerView
, когда вы можете.