ListView не обновляется после фильтрации - PullRequest
14 голосов
/ 05 августа 2010

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

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

Есть идеи, что является причиной этого и как лучше всего решить проблему?

Ответы [ 5 ]

16 голосов
/ 06 августа 2010

Я просмотрел фактический исходный код 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 ​​изначально равен нулю, поэтому все операции (добавление, вставка, удаление, очистка) по умолчанию нацелены на объекты mObject. Это нормально, пока вы не решите включить фильтрацию в списке и фактически выполнить ее. Фильтрация впервые инициализирует 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 ​​есть копия исходных значений / элементов, поэтому адаптер может выполнять свою работу и отображать отфильтрованный список через объекты mObject без потери предварительно отфильтрованных данных.

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

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 адаптера и передать пустую строку * .getFilter (). filter ("") для принудительной фильтрации фильтра после каждой операции [как также предложено BennySkogberg].

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

0 голосов
/ 11 июня 2018

Я боролся с той же проблемой. Затем я нашел этот пост в StackOverflow.

В опубликованном ответе @MattDavis входящий массив адресов назначен двум различным спискам.

 public PromotionListAdapter(Activity a, ArrayList<HashMap<String, String>> d) 
{
    activity = a;
    inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    imageLoader = new ImageLoader(activity.getApplicationContext());

    //To start, set both data sources to the incoming data
    originalData = d;
    filteredData = d;
}

оригинальные данные и отфильтрованные данные.

originalData - это место хранения исходного массива, который был передан, а отфильтрованные данные - это набор данных, который используется при вызове getCount и getItem.

В моем собственном ArrayAdapter я продолжал возвращать исходные данные при вызове getItem. Поэтому ListView обычно вызывает getCount () и видит, что количество элементов соответствует моим исходным данным (не отфильтрованным данным), а getItem получит запрошенный индексированный элемент из исходных данных, поэтому полностью игнорирует новый отфильтрованный набор данных.

Вот почему казалось, что вызов notifyDatasetChanged не обновляет ListView, хотя фактически он просто обновляется из исходного массива, а не из отфильтрованного набора.

Надеюсь, это поможет прояснить эту проблему для кого-то еще в будущем.

Пожалуйста, не стесняйтесь поправлять меня, если я ошибаюсь, поскольку я все еще учусь каждый день!

0 голосов
/ 07 июня 2017

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

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

в 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.Возможно - немного поработав - вы могли бы реализовать это как пользовательский адаптер массива.

0 голосов
/ 20 декабря 2012

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

0 голосов
/ 05 августа 2010

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

listAdapter.notifyDataSetChanged();

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

...