Проблема с выбором списка в Android - PullRequest
9 голосов
/ 11 января 2010

Привет всем,

Прошу прощения за следующий длинный вопрос ...

У меня есть LinearLayout, который содержит ListView и некоторые другие элементы. Что касается ListView, каждая из его строк представляет собой LinearLayout, который содержит 3 вида - флажок, ImageView и TextView (слева направо - горизонтальный). Поскольку я хотел, чтобы при использовании трекбола (чтобы он выделялся цветом фона), была выбрана вся строка, я установил все три вида в строке LinearLayout как не фокусируемые, и это сработало.

Теперь у меня 2 проблемы с этим ListView. Во-первых, я хочу, чтобы всякий раз, когда я касался строки в ListView (пальцем), получал то же поведение, что и при использовании трекбола - это означает, что я хочу, чтобы строка была выделена (выделена). Сейчас происходит то, что когда я касаюсь строки, она действительно становится выделенной, но когда я отпускаю палец, выбор исчезает (очень похоже на то, что происходит в списке контактов устройства).

Второй - из меню я могу отобразить новый LinearLayout вместо того, который содержит ListView (экран другого приложения). Когда это происходит, я все еще сохраняю объект LinearLayout, который содержит ListView, потому что я хочу иметь возможность повторно отобразить его позже, не создавая его с нуля. Проблема заключается в том, что когда я повторно отключаю LinearLayout с ListView, ни одна из строк ListView не выделяется, даже если выбранная строка была выбрана, когда LinearLayout с ListView «покинул» экран.

Извините за длинный пост.

Спасибо!

Ответы [ 7 ]

19 голосов
/ 24 октября 2012

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

Итак, давайте сначала начнем с "запомнить выделение" . Проблема в том, что даже если вы знаете, что Вы можете использовать селектор xml для определения стиля выделения / нажатия / фокусировки. Но этот стиль не будет быть сохраненным после того, как пользователь щелкнул эту строку. Например, у меня есть селектор выделения xml (list_selector.xml в папке res / drawable), подобный этому (но у вас могут быть другие поля, которые необходимо выделить как цвет текста textview в строке):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/list_selector_pressed" android:state_pressed="true" />
    <item android:drawable="@drawable/list_selector_pressed" android:state_selected="true" />
</selector>    

и list_selector_pressed.xml, которые определили стиль выделения - установите цвет фона до серого цвета:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape xmlns:android="http://schemas.android.com/apk/res/android">
            <solid android:color="@color/dark_gray" />
        </shape>
    </item>
</layer-list>

Итак, как @David Hedlund предложил:

Скорее, назначьте OnItemClickListener, и пусть он сохранит идентификатор выбранного элемента в некоторой переменной.

вам нужно создать переменную экземпляра поверх вашего класса:

    private View currentSelectedView;

затем перейдите к

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
    if (currentSelectedView != null && currentSelectedView != v) {
        unhighlightCurrentRow(currentSelectedView);
    }

    currentSelectedView = v;
    highlightCurrentRow(currentSelectedView);
    //other codes 
    }

Довольно просто: мы проверяем, является ли currentSelectedView нулевым или текущим просмотренным просмотром или нет. Сначала мы отключаем любой стиль, вызывая метод unhighlightCurrentRow (currentSelectedView) - вы можете задаться вопросом, почему мы передаем переменную currentSelectedView в качестве параметра, я объясню это позже. Затем мы назначаем представление для currentSelectedView и выделяем текущую строку; так что стиль будет сохраняться после нажатия пользователем.

private void unhighlightCurrentRow(View rowView) {
    rowView.setBackgroundColor(Color.TRANSPARENT);
    TextView textView = (TextView) rowView.findViewById(R.id.menuTitle);
    textView.setTextColor(getResources().getColor(R.color.white));
}

private void highlightCurrentRow(View rowView) {
    rowView.setBackgroundColor(getResources().getColor(
            R.color.dark_gray));
    TextView textView = (TextView) rowView.findViewById(R.id.menuTitle);
    textView.setTextColor(getResources().getColor(R.color.yellow));

} 

Ага, вот и все. Вот как мы реализуем «запомнить выбор» для представления списка. Как вы видите, мы должны продублировать коды для стилизации как в XML, так и в Java-коде - довольно глупо: (

Далее о «установить выбор по умолчанию» . Вы можете подумать, что вы можете сделать это

listView.setAdapter(adatper)
listView.setSelection(0);
currentSelectedView = listView.getChildAt(0);
highlightCurrentRow(currentSelectedView);

в onCreate () в активности или onActivityCreated () во фрагменте.
Но если вы запустите его, вы получите исключение NullPointer и почему? потому что в настоящее время просмотр списка еще не отображается, а Android не нравится iOS, у которой есть viewWillAppear. Так что вам нужно создать мгновенную переменную, чтобы вспомнить, выполняется ли первый раз визуализация ячейки списка, и в onListItemClick снять эту переменную:

Таким образом, согласно объявлению currentSelectedView:

private Boolean firstTimeStartup = true;

затем добавьте методы: предположим, что мы хотим выделить первую строку в списке:

public class HomeAdapter extends ArrayAdapter<String> {
    int layoutResourceId;

    public HomeAdapter(Context context, int textViewResourceId,
            ArrayList<String> objects) {
        super(context, textViewResourceId, objects);
        layoutResourceId = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(
                    layoutResourceId, null);
        }

        if (firstTimeStartup && postion == 0) {
            highlightCurrentRow(convertView);
        } else {
            unhighlightCurrentRow(convertView);
        }

        TextView title = (TextView) convertView
                .findViewById(R.id.menuTitle);
        title.setText(getItem(position));
        return convertView;
    }
}

Довольно просто. Но вам нужно внести некоторые изменения в метод onListItemClick:

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

    if (firstTimeStartup) {// first time  highlight first row
        currentSelectedView = l.getChildAt(0);
    }
    firstTimeStartup = false; 
    if (currentSelectedView != null && currentSelectedView != v) {
        unhighlightCurrentRow(currentSelectedView);
    }

    currentSelectedView = v;
    highlightCurrentRow(currentSelectedView);

     //other codes
}

Вот, пожалуйста! Наслаждайтесь Android:)

5 голосов
/ 16 декабря 2011
private void setListviewSelection(final ListView list, final int pos) {
list.post(new Runnable() 
   {
    @Override
    public void run() 
      {
        list.setSelection(pos);
        View v = list.getChildAt(pos);
        if (v != null) 
        {
            v.requestFocus();
        }
    }
});
}

Выше помогает мне установить фокусировку строки в списке.

3 голосов
/ 11 января 2010
  1. Это он по умолчанию и ожидаемое поведение. Подделка под это настоятельно рекомендуется против.
  2. Это следует из 1. в некоторой степени. Выбранное состояние не является постоянным. Скорее, назначьте OnItemClickListener, и пусть он сохранит идентификатор выбранного элемента в некоторой переменной. Если вам нужно повторно выбрать элемент, когда вы вернетесь, вы можете использовать setSelection()
1 голос
/ 28 мая 2016

Для меня гораздо более чистое решение, основанное на том факте, что если вы установите android:choiceMode="singleChoice" или android:choiceMode="multipleChoice" в вашем ListView, то ListView будет пытаться сохранить проверенное состояние для одного или нескольких вариантов ListView. Дело в том, что это зависит от ячейки списка, реализующей Checkable

Итак, вы начинаете с реализации CheckableLinearLayout (или FrameLayout может быть лучше), чтобы андроид поддерживал для вас проверенное состояние и вы могли синхронизировать Drawable. Ключ здесь refreshDrawableState и onCreateDrawableState Они обновляют Drawable, так что фон будет нарисован выделенным или нет.

public class CheckableLinearLayout extends LinearLayout implements Checkable {
    boolean checked;

    public CheckableLinearLayout(Context context) {
        super(context);
    }

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setChecked(boolean checked) {
        this.checked = checked;
        refreshDrawableState();
    }

    @Override
    public boolean isChecked() {
        return checked;
    }

    @Override
    public void toggle() {
        setChecked(!isChecked());
    }

    private static final int[] CheckedStateSet = { android.R.attr.state_checked };

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CheckedStateSet);
        }
        return drawableState;
    }
}

Затем, простой selector для фона, который будет отображаться в проверенном состоянии:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="#ccc" android:state_checked="true" />
    <item android:drawable="@android:color/transparent" />
</selector>

Теперь я могу использовать CheckableLinearLayout в любом списке, в котором я хочу показать постоянное состояние выбора (множественное или единичное):

<CheckableLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/checkable_cell"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text"
        android:layout_margin="10dp"
        android:maxLines="8"
        />
</CheckableLinearLayout>

Мне не нужен специальный код в Fragment для обновления отмеченного состояния, и я могу поддерживать выбранную позицию вокруг вращения с помощью:

state.putInt("selection", listView.getCheckedItemPosition());

и

listView.setItemChecked(state.getInt("selection"), true);

аналогично, я могу использовать setItemChecked, чтобы установить изначально выбранный элемент, или использовать getCheckedItemPosition (или getCheckedItemPositions() для множественного выбора), чтобы получить текущий выбор.

1 голос
/ 25 октября 2011

Я решил эту проблему, отправив сообщение после notifyDataSetChanged() ; и вызвав setSelection(0) в моей функции handleMessage. Это Кажется некрасивым, но несколько раз помог мне, когда SDK ведет себя странно.

0 голосов
/ 03 апреля 2016

Я использую адаптер и не хочу устанавливать собственные цвета фона, но использую android: state_selected в drawable xml. SetSelection не работает для меня, но, может быть, это также потому, что мне нужен SetNotifyDataChanged, который показывает, что выбранное состояние не является постоянным.

Я также обнаружил, что состояние Selected для элемента в ListView не является постоянным, поскольку SetNotifyDataChanged приводит к обновлению макета ListView, который очищает их все. Установка элемента на Selected в GetView адаптера тоже слишком рано.

В конце концов я установил состояние «Выбрано» для представления выбранного элемента после изменения макета списка, то есть, когда запускается событие LayoutChange (в Java это, вероятно, присоединение к OnLayoutChangeListener ListView).

Чтобы сделать это действительно легко, я сохраняю вид выбранного элемента как SelectedItemView Адаптера. В обработчике событий ListView LayoutChange я просто установил для SelectedItemView.Selected адаптера значение true.

Вот код из моей Деятельности, где я устанавливаю Адаптер для ListView, а также подписываюсь на LayoutChange (или в Java присоединяю OnLayoutChangeListener)

        ringTonesListView.Adapter = ringTonesListAdapter;
        ringTonesListView.LayoutChange += (s, layoutChangeArgs) => {
            //At this stage the layout has been updated and the Selected can be set to true for the view of the selected item. This will result in android:state_selected logic to be applied as desired and styling can be completely done per layout in Resources.
            ringTonesListAdapter.SelectedItemView.Selected = true;
        };

Вот мой код для адаптера:

public class RingTonesListAdapter : BaseAdapter<RingToneItem>
{
    List<RingTone> Items { get; set; }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        View view = convertView;

        // re-use an existing view, if one is available
        // otherwise create a new one
        if (view == null)
        {
            view = Context.LayoutInflater.Inflate(Resource.Layout.AlertSoundItem, parent, false);
            view.Click += SelectRingTone;
        }

        RingTone ringTone = this[position];
        if (ringTone.Selected)
        {
            //==> Important
            //Store this view since it's the view for the Selected Item
            SelectedItemView = view;
            //Setting view.Selected to true here doesn't help either, since Selected will be cleared after.
        }

        return view;
    }

    private void SelectRingTone(object sender, EventArgs args)
    {
        View view = (View)sender;
        string title = view.FindViewById<TextView>(Resource.Id.ringToneTitle).Text;
        RingToneItem ringToneItem = Items.First(rt => rt.Title == title);
        if (!ringToneItem.Selected)
        {
            //The RingTone was not selected and is selected now
            //Deselect Old and Select new
            foreach (RingToneItem oldItem in Items.Where(rt => rt.Selected))
            {
                oldItem.Selected = false;
            }

            // Select New RingTone
            ringToneItem.Selected = true;
            //Update the ListView. 
            //This will result in removal of Selected state for all Items when the ListView updates it's layout
            NotifyDataSetChanged();
        }

        //Now play the test sound
        NotifierService.TestSound(Context, ringToneItem);
    }

    public View SelectedItemView { get; set; }
}
0 голосов
/ 17 июня 2010

Это должно помочь:

private void setListviewSelection(final ListView list, final int pos) {
    list.post(new Runnable() {
        @Override
        public void run() {
            list.setSelection(pos);
            View v = list.getChildAt(pos);
            if (v != null) {
                v.requestFocus();
            }
        }
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...