Как я могу обновить одну строку в ListView? - PullRequest
128 голосов
/ 16 сентября 2010

У меня есть ListView, который отображает новости. Они содержат изображение, заголовок и текст. Изображение загружается в отдельном потоке (с очередью и всем), и когда изображение загружается, я теперь вызываю notifyDataSetChanged() в адаптере списка для обновления изображения. Это работает, но getView() вызывается слишком часто, поскольку notifyDataSetChanged() вызывает getView() для всех видимых элементов. Я хочу обновить только один элемент в списке. Как бы я это сделал?

Проблемы с моим текущим подходом:

  1. Медленная прокрутка
  2. У меня есть постепенная анимация на изображении, которая происходит каждый раз, когда загружается одно новое изображение в списке.

Ответы [ 10 ]

194 голосов
/ 16 сентября 2010

Я нашел ответ, благодаря вашей информации Мишель. Вы действительно можете получить правильное представление, используя View#getChildAt(int index). Подвох в том, что он начинает отсчет с первого видимого элемента. На самом деле, вы можете получить только видимые предметы. Вы решаете это с ListView#getFirstVisiblePosition().

Пример:

private void updateView(int index){
    View v = yourListView.getChildAt(index - 
        yourListView.getFirstVisiblePosition());

    if(v == null)
       return;

    TextView someText = (TextView) v.findViewById(R.id.sometextview);
    someText.setText("Hi! I updated you manually!");
}
69 голосов
/ 16 сентября 2010

Этот вопрос был задан на Google I / O 2010, вы можете посмотреть его здесь:

Мир ListView, время 52: 30

В основномРомен Гай объясняет, что нужно позвонить getChildAt(int) на ListView, чтобы получить представление, и (я думаю) позвонить getFirstVisiblePosition(), чтобы узнатькорреляция между положением и индексом.

Ромен также указывает на проект под названием Полки в качестве примера, я думаю, что он может иметь в виду метод ShelvesActivity.updateBookCovers(), но я могуНе удается найти вызов getFirstVisiblePosition().

ДОБРО ПОЖАЛОВАТЬ ОБНОВЛЕНИЯ:

RecyclerView исправит это в ближайшем будущем.Как указано в http://www.grokkingandroid.com/first-glance-androids-recyclerview/,, вы сможете вызывать методы, чтобы точно указать изменение, например:

void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)

Кроме того, каждый захочет использовать новоепредставления, основанные на RecyclerView, потому что они будут вознаграждены красивыми анимациями!Будущее выглядит потрясающе!: -)

6 голосов
/ 20 февраля 2014

Вот как я это сделал:

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

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    View view = super.getView(position, convertView, parent);
    view.setTag(getItemId(position));
    return view;
}

Для обновления проверьте каждый элемент списка, если вид с заданным идентификатором есть, он видим, поэтому мы выполняем обновление.

private void update(long id)
{

    int c = list.getChildCount();
    for (int i = 0; i < c; i++)
    {
        View view = list.getChildAt(i);
        if ((Long)view.getTag() == id)
        {
            // update view
        }
    }
}

Это на самом деле проще, чем другие методы, и лучше, когда вы имеете дело с идентификаторами, а не позициями!Также вы должны вызвать обновление для элементов, которые становятся видимыми.

3 голосов
/ 07 декабря 2016

сначала получите класс модели как глобальный, как этот объект класса модели

SampleModel golbalmodel=new SchedulerModel();

и инициализируйте его как глобальный

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

SampleModel data = (SchedulerModel) sampleList.get(position);
                golbalmodel=data;

установите измененное значение для метода объекта глобальной модели, который нужно установить, и добавьте notifyDataSetChanged его работы для меня

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Вот связанный с этим вопросс хорошими ответами.

2 голосов
/ 15 июля 2015
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;

if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
    Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
    return;
}

View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);

mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);

Для RecycleView, пожалуйста, используйте этот код

2 голосов
/ 21 декабря 2010

Ответы ясны и правильны, я добавлю идею для CursorAdapter дела здесь.

Если вы создаете подклассы CursorAdapter (или ResourceCursorAdapter, или SimpleCursorAdapter), то вы можете либо реализовать ViewBinder, либо переопределить методы bindView() и newView(), они не получат текущий индекс элемента списка в аргументах. Следовательно, когда поступают некоторые данные и вы хотите обновить соответствующие видимые элементы списка, как вы узнаете их индексы?

Мой обходной путь:

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

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

1 голос
/ 02 октября 2016

Я разработал другое решение, например RecyclyerView метод void notifyItemChanged(int position), создайте CustomBaseAdapter класс, например, такой:

public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
    private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();

    public boolean hasStableIds() {
        return false;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    public void notifyItemChanged(int position) {
        mDataSetObservable.notifyItemChanged(position);
    }

    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }

    public boolean isEmpty() {
        return getCount() == 0;
    } {

    }
}

Не забудьтесоздайте CustomDataSetObservable класс тоже для mDataSetObservable переменной в CustomAdapterClass , например:

public class CustomDataSetObservable extends Observable<DataSetObserver> {

    public void notifyChanged() {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }

    public void notifyItemChanged(int position) {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            mObservers.get(position).onChanged();
        }
    }
}

в классе CustomBaseAdapter существует методnotifyItemChanged(int position), и вы можете вызывать этот метод, когда хотите обновить строку, где вы хотите (от нажатия кнопки или в любом месте, где вы хотите вызвать этот метод).И вуаля!, Ваша единственная строка будет обновляться мгновенно ..

1 голос
/ 13 апреля 2016

именно я использовал это

private void updateSetTopState(int index) {
        View v = listview.getChildAt(index -
                listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());

        if(v == null)
            return;

        TextView aa = (TextView) v.findViewById(R.id.aa);
        aa.setVisibility(View.VISIBLE);
    }
1 голос
/ 25 марта 2013

Я использовал код, предоставленный Эриком, прекрасно работает, но у меня есть сложный настраиваемый адаптер для моего списка, и я столкнулся с двойной реализацией кода, который обновляет пользовательский интерфейс. Я попытался получить новое представление от метода getView моих адаптеров (массив данных, содержащий данные списка, уже обновлен / изменен):

View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
    cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
    cell.startAnimation(animationLeftIn());
}

Это работает хорошо, но я не знаю, хорошая ли это практика. Поэтому мне не нужно реализовывать код, который обновляет элемент списка два раза.

0 голосов
/ 24 июля 2013

Мое решение: Если это правильно *, обновите данные и просматриваемые элементы, не перерисовывая весь список. Иначе notifyDataSetChanged.

Правильно - размер старых данных == новый размер данных, старые идентификаторы данных и их порядок == новые идентификаторы данных и порядок

Как:

/**
 * A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
 * is one-to-one and on.
 * 
 */
    private static class UniqueValueSparseArray extends SparseArray<View> {
    private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();

    @Override
    public void put(int key, View value) {
        final Integer previousKey = m_valueToKey.put(value,key);
        if(null != previousKey) {
            remove(previousKey);//re-mapping
        }
        super.put(key, value);
    }
}

@Override
public void setData(final List<? extends DBObject> data) {
    // TODO Implement 'smarter' logic, for replacing just part of the data?
    if (data == m_data) return;
    List<? extends DBObject> oldData = m_data;
    m_data = null == data ? Collections.EMPTY_LIST : data;
    if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
    else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}


/**
 * See if we can update the data within existing layout, without re-drawing the list.
 * @param oldData
 * @param newData
 * @return
 */
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
    /**
     * Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
     * items.
     */
    final int oldDataSize = oldData.size();
    if (oldDataSize != newData.size()) return false;
    DBObject newObj;

    int nVisibleViews = m_visibleViews.size();
    if(nVisibleViews == 0) return false;

    for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
        newObj = newData.get(position);
        if (oldData.get(position).getId() != newObj.getId()) return false;
        // iterate over visible objects and see if this ID is there.
        final View view = m_visibleViews.get(position);
        if (null != view) {
            // this position has a visible view, let's update it!
            bindView(position, view, false);
            nVisibleViews--;
        }
    }

    return true;
}

и, конечно:

@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
    final View result = createViewFromResource(position, convertView, parent);
    m_visibleViews.put(position, result);

    return result;
}

Игнорировать последний параметр для bindView (я использую его, чтобы определить, нужно ли мне повторно использовать растровые изображения для ImageDrawable).

Как упомянуто выше, общее количество «видимых» представлений примерно равно количеству, которое умещается на экране (игнорируя изменения ориентации и т. Д.), Поэтому не имеет значения для памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...