ListView'da tek bir satırı nasıl güncelleyebilirim?


131

Bir var ListViewki görüntüler haber öğelerini. Bir resim, bir başlık ve bazı metinler içerirler. Görüntü ayrı bir iş parçacığına yüklenir (bir kuyruk ve tümü ile) ve görüntü indirildiğinde, şimdi notifyDataSetChanged()görüntüyü güncellemek için liste bağdaştırıcısını çağırıyorum . Bu çalışır, ancak getView()bu yana, çok sık denilen oluyor notifyDataSetChanged()aramalar getView()tüm görünür öğeleri için. Listedeki yalnızca tek bir öğeyi güncellemek istiyorum. Bunu nasıl yaparım?

Mevcut yaklaşımımda yaşadığım sorunlar:

  1. Kaydırma yavaş
  2. Listedeki tek bir yeni resim her yüklendiğinde ortaya çıkan resim üzerinde beliren bir animasyon var.

Yanıtlar:


199

Cevabı buldum, verdiğin bilgiler sayesinde Michelle. Kullanarak gerçekten doğru görünümü elde edebilirsiniz View#getChildAt(int index). İşin püf noktası, ilk görünen öğeden saymaya başlamasıdır. Aslında, yalnızca görünen öğeleri elde edebilirsiniz. Bunu ile çözersiniz ListView#getFirstVisiblePosition().

Misal:

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
kendime not: mImgView.setImageURI () liste öğesini yalnızca bir kez güncelleyecektir; bu yüzden bunun yerine mLstVwAdapter.notifyDataSetInvalidated () kullanarak daha iyiyim.
kellogs

Bu çözüm işe yarıyor. Ancak bu yalnızca yourListView.getChildAt (index) ile yapılabilir; ve görünümü değiştirin. Her iki çözüm de çalışıyor !!
AndroidDev

Yalnızca bağdaştırıcıda getView () işlevini yalnızca konum getFirstVisiblePosition () ile getLastVisiblePosition () arasındaysa çağırırsam ne olur? aynı şekilde çalışır mı? Görünümü her zamanki gibi güncelleyeceğini düşünüyorum, değil mi?
android geliştiricisi

Benim durumumda, bazı zamanlar görünüm boş, çünkü her öğe eşzamansız olarak güncelleniyor, görünüm olup olmadığını kontrol etmek daha iyi! = Null
Khawar

2
Harika çalışıyor. Beğen özelliğini instagram benzeri bir uygulamada uygulama görevini bitirmeme yardımcı oluyor. Özel bir adaptör kullanıyordum, bir liste öğesinin içindeki benzer düğmede bir yayın, arka uçtaki benzerleri ana parçadaki bir yayın alıcısıyla anlatmak için bir eşzamanlı görev başlatıyordum ve sonunda Erik'in listeyi güncellemek için cevabı.
Josh

71

Bu soru Google I / O 2010'da sorulmuştur, buradan izleyebilirsiniz:

ListView dünyası, saat 52:30

Temelde Romain Guy'ın açıkladığı şey , görüş almak için çağrıda getChildAt(int)bulunmak ListViewve (sanırım) getFirstVisiblePosition()konum ile indeks arasındaki ilişkiyi bulmak için aramaktır.

Romain örnek olarak Shelves adlı projeye de işaret ediyor, bence metodu kastediyor olabilir ShelvesActivity.updateBookCovers()ama çağrısını bulamıyorum getFirstVisiblePosition().

MUHTEŞEM GÜNCELLEMELER GELİYOR:

RecyclerView bunu yakın gelecekte düzeltecektir. Üzerinde belirttiği gibi http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , aşağıdakiler gibi, değişiklik belirtmek tam yöntemleri çağırmak mümkün olacak:

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

Ayrıca herkes RecyclerView'e dayalı yeni görünümleri kullanmak isteyecek çünkü güzel görünen animasyonlarla ödüllendirilecekler! Gelecek harika görünüyor! :-)


3
İyi bir şey! :) Belki buraya da bir boş kontrol ekleyebilirsiniz - görünüm şu anda mevcut değilse v boş olabilir. Ve tabii ki, kullanıcı ListView'ı kaydırırsa bu veriler kaybolacaktır, bu nedenle adaptördeki verileri de güncellemelidir (belki notifyDataSetChanged () 'i çağırmadan). Genel olarak tüm bu mantığı adaptör içinde tutmanın, yani ListView referansını ona aktarmanın iyi bir fikir olacağını düşünüyorum.
mreichelt

Bu benim için çalıştı, ancak - görünüm ekrandan çıktığında, değişiklik yeniden girdiğinde sıfırlanır. Sanırım, çünkü getview'de hala orijinal bilgileri alıyor. Bunu nasıl aşarım?
gloscherrybomb

6

Ben böyle yaptım:

Öğelerinizin (satırlar) daha sonra güncelleyebilmeniz için benzersiz kimlikleri olmalıdır. Liste, görünümü adaptörden alırken her görünümün etiketini ayarlayın. (Varsayılan etiket başka bir yerde kullanılıyorsa, anahtar etiketi de kullanabilirsiniz)

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

Güncelleme için listenin her öğesini kontrol edin, verilen kimliğe sahip bir görünüm varsa görünür olduğundan güncellemeyi gerçekleştiriyoruz.

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

Aslında diğer yöntemlerden daha kolay ve konumlarla değil kimliklerle uğraşırken daha iyi! Ayrıca görünür olan öğeler için güncellemeyi çağırmalısınız.


3

önce model sınıfını bu model sınıf nesnesi gibi global olarak alın

SampleModel golbalmodel=new SchedulerModel();

ve onu global olarak başlat

modelin geçerli satırını, onu global modele başlatarak elde edin

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

değiştirilen değeri ayarlanacak global model nesne yöntemine ayarlayın ve notifyDataSetChanged çalışmalarını benim için ekleyin

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

İşte bununla ilgili iyi cevaplarla ilgili bir soru.


Bu doğru yaklaşımdır, çünkü güncellemek istediğiniz nesneyi alırsınız, onu güncellersiniz ve ardından adaptöre bildirirsiniz ve bu, satır bilgilerini yeni verilerle değiştirecektir.
Gastón Saillén

2

Cevaplar açık ve doğru, CursorAdapterburaya dava için bir fikir ekleyeceğim .

Alt sınıflandırma yapıyorsanız CursorAdapter(veya ResourceCursorAdapter, veya SimpleCursorAdapter), o zaman ya uygulama ViewBinderya da geçersiz kılma bindView()ve newView()yöntemler elde edersiniz , bunlar bağımsız değişkenlerdeki geçerli liste öğesi dizinini almaz. Bu nedenle, bazı veriler geldiğinde ve ilgili görünür liste öğelerini güncellemek istediğinizde, indekslerini nasıl biliyorsunuz?

Çözümüm şuydu:

  • tüm oluşturulan liste öğesi görünümlerinin bir listesini saklayın, bu listeye öğeler ekleyin newView()
  • veriler geldiğinde, onları yineleyin ve hangisinin güncellenmesi gerektiğini görün - notifyDatasetChanged()hepsini yapmaktan ve yenilemekten daha iyidir

Görüntü geri dönüşümü nedeniyle, saklamam ve yinelemem gereken görüntüleme referanslarının sayısı kabaca ekranda görünen liste öğelerinin sayısına eşit olacaktır.


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 için lütfen bu kodu kullanın


Bunu recycleView'da uyguladım, çalışıyor. Ancak listeyi kaydırdığınızda tekrar değişir. herhangi bir yardım?
Fahid Nadeem

1

Erik'i sağlayan kodu kullandım, harika çalışıyor, ancak liste görünümüm için karmaşık bir özel bağdaştırıcım var ve kullanıcı arayüzünü güncelleyen kodun iki kez uygulanmasıyla karşılaştım. Adaptörlerimden getView yönteminden yeni görünümü almaya çalıştım (listview verilerini tutan dizi listesi zaten güncellendi / değiştirildi):

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

İyi çalışıyor, ancak bunun iyi bir uygulama olup olmadığını bilmiyorum. Bu nedenle, liste öğesini iki kez güncelleyen kodu uygulamama gerek yok.


1

aynen bunu kullandım

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

Göreli mizanpajı görüyorum, göreli mizanpaj içinde Metin görünümü nasıl bulunur?
Girish

1

RecyclyerView yöntemi void gibi başka bir çözüm yaptım, aynen şöyle CustomBaseAdapter sınıfı notifyItemChanged(int position)oluşturun :

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

    }
}

CustomAdapterClass içinde değişken için de bir CustomDataSetObservable sınıfı oluşturmayı unutmayın , örneğin:mDataSetObservable

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 sınıfında bir yöntem vardır notifyItemChanged(int position)ve bir satırı istediğiniz yerde güncellemek istediğinizde bu yöntemi çağırabilirsiniz (düğmeyi tıklatarak veya bu yöntemi çağırmak istediğiniz herhangi bir yerden). Ve işte!, Tek satırınız anında güncellenecek ..


0

Çözümüm: Doğruysa *, tüm listeyi yeniden çizmeden verileri ve görüntülenebilir öğeleri güncelleyin. Else notifyDataSetChanged.

Doğru - eski Veri boyutu == yeni veri boyutu ve eski veri kimlikleri ve bunların sırası == yeni veri kimlikleri ve sırası

Nasıl:

/**
 * 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;
}

ve tabi ki:

@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 için son parametreyi yoksay (Bitmap'leri ImageDrawable için geri dönüştürmem gerekip gerekmediğini belirlemek için kullanıyorum).

Yukarıda bahsedildiği gibi, 'görünür' görüntülerin toplam sayısı kabaca ekrana sığan miktardır (yönelim değişikliklerini göz ardı ederek vb.), Bu nedenle bellek açısından önemli değildir.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.