Как я могу обновить одну строку в ListView?



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



проблемы у меня с моим нынешним подход таков:




  1. медленная прокрутка

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

616   10  

10 ответов:

Я нашел ответ, благодаря вашей информации, Мишель. Вы действительно можете получить правильный вид с помощью 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!");
}

этот вопрос был задан в 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, потому что они будут вознаграждены красиво выглядящие анимации! Будущее выглядит потрясающе! : -)

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

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

@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
        }
    }
}

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

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

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

мой обходной путь был:

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

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

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 пожалуйста, используйте этот код

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

SampleModel golbalmodel=new SchedulerModel();

и инициализировать его в global

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

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

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

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

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

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

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());
}

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

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

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);
    }

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

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

Как:

/**
 * 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).

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

Я придумал другое решение, как 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 class для 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), и вы можете вызвать этот метод, когда вы хотите обновить строку везде вы хотите (от нажатия кнопки или в любом месте вы хотите вызвать этот метод). И вуаля!, ваша единственная строка будет обновляться мгновенно..

Comments

    Ничего не найдено.