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

ListView не обновляется после фильтрации

У меня есть ListView (с setTextFilterEnabled (true)) и пользовательский адаптер (расширяет ArrayAdapter), который я обновляю из основного потока пользовательского интерфейса всякий раз, когда новый элемент добавлен/вставлен. Сначала все работает нормально - новые предметы отображаются в списке сразу. Однако это останавливает момент, когда я пытаюсь отфильтровать список.

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

Любые идеи, которые вызывают это и как лучше всего решить эту проблему?

4b9b3361

Ответ 1

Я прошел через исходный код ArrayAdapter, и похоже, что это было написано так, чтобы вести себя таким образом.

ArrayAdapter имеет два списка для начала: mObjects и mOriginalValues. mObjects - это основной набор данных, с которым будет работать адаптер. Принимая функцию add(), например:

public void add(T object) {
    if (mOriginalValues != null) {
        synchronized (mLock) {
            mOriginalValues.add(object);
            if (mNotifyOnChange) notifyDataSetChanged();
        }
    } else {
        mObjects.add(object);
        if (mNotifyOnChange) notifyDataSetChanged();
    }
}

mOriginalValues ​​изначально имеет значение null, поэтому все операции (добавить, вставить, удалить, очистить) по умолчанию предназначены для mObjects. Все это нормально до тех пор, пока вы не решите включить фильтрацию в списке и фактически выполните его. Фильтрация в первый раз инициализирует mOriginalValues ​​любыми mObjects:

private class ArrayFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence prefix) {
        FilterResults results = new FilterResults();

        if (mOriginalValues == null) {
            synchronized (mLock) {
                mOriginalValues = new ArrayList<T>(mObjects);
                //mOriginalValues is no longer null
            }
        }

        if (prefix == null || prefix.length() == 0) {
            synchronized (mLock) {
                ArrayList<T> list = new ArrayList<T>(mOriginalValues);
                results.values = list;
                results.count = list.size();
            }
        } else {
            //filtering work happens here and a new filtered set is stored in newValues
            results.values = newValues;
            results.count = newValues.size();
        }

        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        //noinspection unchecked
        mObjects = (List<T>) results.values;
        if (results.count > 0) {
            notifyDataSetChanged();
        } else {
            notifyDataSetInvalidated();
        }
    }
}

mOriginalValues ​​теперь имеет копию исходных значений/элементов, поэтому адаптер может выполнять свою работу и отображать отфильтрованный список через mObjects без потери предварительно отфильтрованных данных.

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

if (prefix == null || prefix.length() == 0) {
            synchronized (mLock) {
                ArrayList<T> list = new ArrayList<T>(mOriginalValues);
                results.values = list;
                results.count = list.size();
            }
        }

mOriginalValues, который мы модифицировали, так как наш первый фильтр (хотя мы не могли его увидеть на экране) хранится в другом списке и копируется в mObjects, наконец, отображая сделанные изменения. Тем не менее с этого момента все будет так: все операции будут выполняться на mOriginalValues, и изменения будут появляться только после фильтрации.

Что касается решения, то на данный момент я пришел либо (1), чтобы поставить логический флаг, который сообщает операциям адаптера, если есть текущая фильтрация или нет - если фильтрация завершена, тогда скопируйте над содержимым mOriginalValues ​​для mObjects или (2) просто вызвать объект Filter Filter и передать пустую строку *.getFilter(). filter (""), чтобы заставить фильтр после каждой операции [также предложенный BennySkogberg].

Было бы весьма признательно, если кто-нибудь сможет пролить свет на эту проблему или подтвердить, что я только что сделал. Спасибо!

Ответ 2

Если я хорошо понимаю вашу проблему - вызываете ли вы метод notifyDataSetChanged()? Это заставляет listview перерисовываться.

listAdapter.notifyDataSetChanged();

просто используйте это, когда вы что-то делаете (например: lazy-load of images), чтобы обновить список.

Ответ 3

Проблема сообщается как дефект с июля 2010 года. Ознакомьтесь с моим comment # 5

Ответ 4

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

public ArrayList<String> listArray = new ArrayList<>();
public ArrayList<String> tempListArray = new ArrayList<>();
public ArrayAdapter<String> tempAdapter;
public String currentFilter;

in onCreate:

// Fill my ORIGINAL listArray;
listArray.add("Cookies are delicious.");

// After adding everything to listArray, I add everything to tempListArray
for (String element : listArray){
    tempListArray.add(element);
}

// Setting up the Adapter...
tempAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, tempListArray);
myListView.setAdapter(tempAdapter);

Фильтрация:

// Defining filter functions
public void customFilterList(String filter){
        tempListArray.clear();
        for (String item : listArray){
            if (item.toLowerCase().contains(filter.toLowerCase())){
                tempListArray.add(item);
            }
        }
        currentFilter = filter;
        tempAdapter.notifyDataSetChanged();
    }
    public void updateList(){
        customFilterList(currentFilter);
    }

Я использовал его для реализации функции поиска в моем списке.

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

Вместо notifyDataSetChanged вы должны вызвать updateList().

Вместо myListView.getFilter(). filter ('filter text') вы вызываете customFilterList ('filter text')

На данный момент это работает только с одним ListView. Может быть - с небольшой работой - вы могли бы реализовать это как пользовательский адаптер массива.