Як я можу оновити один рядок у ListView?


131

У мене є ListViewякий відображає новини. Вони містять зображення, заголовок та текст. Зображення завантажується в окрему нитку (із чергою та всіма), і коли зображення завантажується, я зараз закликаю notifyDataSetChanged()адаптер списку, щоб оновити зображення. Це працює, але дзвонять getView()занадто часто, оскільки notifyDataSetChanged()дзвінки getView()для всіх видимих ​​елементів. Я хочу оновити лише один елемент у списку. Як би я це зробив?

Проблеми, які виникають у мене з сучасним підходом, є:

  1. Прокрутка відбувається повільно
  2. У мене є анімація зникає на зображенні, яка відбувається щоразу, коли завантажується одне нове зображення зі списку.

Відповіді:


199

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

2
зауважте до себе: mImgView.setImageURI () оновить елемент списку, але лише один раз; тому я краще використовувати замість mLstVwAdapter.notifyDataSetInvalidated ().
kellogs

Це рішення працює. Але це можна зробити лише за допомогою yourListView.getChildAt (індекс); і змінити подання. Обидва рішення працюють !!
AndroidDev

Що буде, якщо я просто зателефоную getView () на адаптер, тільки якщо позиція знаходиться між getFirstVisiblePosition () та getLastVisiblePosition ()? це працювало б так само? Я думаю, що це оновить вигляд, як завжди, правда?
андроїд розробник

У моєму випадку певний час перегляд є нульовим, тому що кожен елемент оновлюється асинхронно, краще перевірити, чи перегляд! = Null
Khawar

2
Чудово працює. Це допомагає мені закінчити завдання щодо реалізації функції Like в додатку, подібному до instagram. Я використовував користувальницький адаптер, трансляцію у подібній кнопці всередині елемента списку, запускав асинтакт, щоб розповісти про вихідну програму про подібне з приймачем трансляції на батьківському фрагменті, і нарешті відповідь Еріка оновити список.
Джош

71

Це запитання було задано в 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, оскільки вони будуть нагороджені красиво виглядаючими анімаціями! Майбутнє виглядає приголомшливо! :-)


3
Гарна річ! :) Можливо, ви можете також додати нульову перевірку тут - v може бути нульовою, якщо представлення недоступне в даний момент. І звичайно, ці дані не зникнуть, якщо користувач прокручує ListView, тому слід також оновити дані в адаптері (можливо, без виклику notifyDataSetChanged ()). Взагалі я думаю, що було б непогано зберігати всю цю логіку в адаптері, тобто передавати посилання ListView на неї.
mreichelt

Це працювало для мене, за винятком випадків, коли перегляд вийшов із екрана, коли він знову входить, зміни скидаються. Я здогадуюсь, тому що в getview він все ще отримує оригінальну інформацію. Як мені це обійти?
gloscherrybomb

6

Ось як я це зробив:

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

@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

отримати клас моделі спочатку таким же глобальним, як об’єкт цього класу моделей

SampleModel golbalmodel=new SchedulerModel();

і ініціалізувати його на глобальному рівні

отримати поточний рядок подання за моделлю, ініціалізуючи його до глобальної моделі

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

встановити змінене значення методу об'єкта глобальної моделі, який буде встановлено, і додайте notifyDataSetChanged його роботи для мене

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Ось відповідне питання з цього питання з хорошими відповідями.


Це правильний підхід, оскільки ви отримуєте об'єкт, який ви хочете оновити, оновити його, а потім повідомити адаптер, і це змінить інформацію про рядки з новими даними.
Gastón Saillén

2

Відповіді чіткі та правильні, тут я додам ідею для CursorAdapterвипадку.

Якщо ви підкласифікуєте CursorAdapter(або ResourceCursorAdapter, або SimpleCursorAdapter), то ви переходите до реалізації ViewBinderабо переопределення bindView()та newView()методів, вони не отримують поточний індекс елемента списку в аргументах. Тому, коли деякі дані надходять і ви хочете оновити відповідні видимі елементи списку, як ви знаєте їх індекси?

Моє вирішення було:

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

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


2
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 використовуйте цей код


Я реалізував це на recycleView, він працює. Але коли ви прокручуєте список, він знову змінюється. будь-яка допомога?
Фахід Надей

1

Я використав код, який надав Ерік, працює чудово, але у мене є складний спеціальний адаптер для мого перегляду списку, і я стикався з двократною реалізацією коду, що оновлює інтерфейс користувача. Я намагався отримати новий вигляд з моїх адаптерів 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());
}

Це добре працює, але я не знаю, чи це хороша практика. Тому мені не потрібно реалізовувати код, який оновлює пункт списку два рази.


1

саме я цим користувався

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

Я отримую View як відносний макет, як знайти Textview всередині відносного макета?
Гіріш

1

Я створив інше рішення, як- от метод 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), і ви можете викликати цей метод, коли ви хочете оновити рядок, куди ви хочете (від натискання кнопки або де завгодно, щоб викликати цей метод). І вуаля !, ваш єдиний ряд одразу оновиться ..


0

Моє рішення: Якщо це правильно *, оновіть дані та видимі елементи, не перемальовуючи весь список. Інше сповіститиDataSetChanged.

Правильно - 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 (я використовую його, щоб визначити, чи потрібно мені переробляти растрові зображення для ImageDravable).

Як вже було сказано вище, загальна кількість "видимих" переглядів - це приблизно кількість, яка розміщується на екрані (ігнорування змін орієнтації тощо), тому немає великої пам'яті.


0

На додаток до цього рішення ( https://stackoverflow.com/a/3727813/5218712 ) просто хочу додати, що воно має працювати лише в тому випадку, якщо listView.getChildCount() == yourDataList.size(); всередині ListView може бути додатковий перегляд.

Приклад того, як дочірні елементи заповнюються:  listView.mДитячий масив

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.