Bagaimana saya bisa memperbarui satu baris dalam ListView?

131

Saya memiliki ListViewyang menampilkan item berita. Mereka mengandung gambar, judul dan beberapa teks. Gambar dimuat dalam utas terpisah (dengan antrian dan semua) dan ketika gambar diunduh, saya sekarang memanggil notifyDataSetChanged()daftar adaptor untuk memperbarui gambar. Ini berfungsi, tetapi getView()dipanggil terlalu sering, karena notifyDataSetChanged()panggilan getView()untuk semua item yang terlihat. Saya ingin memperbarui hanya satu item dalam daftar. Bagaimana saya melakukan ini?

Masalah yang saya miliki dengan pendekatan saya saat ini adalah:

  1. Menggulir lambat
  2. Saya memiliki animasi fade-in pada gambar yang terjadi setiap kali satu gambar baru dalam daftar dimuat.
Erik
sumber

Jawaban:

199

Saya menemukan jawabannya, terima kasih atas informasi Anda Michelle. Anda memang bisa mendapatkan tampilan yang benar menggunakan View#getChildAt(int index). Tangkapannya adalah ia mulai menghitung dari item pertama yang terlihat. Bahkan, Anda hanya bisa mendapatkan item yang terlihat. Anda menyelesaikan ini dengan ListView#getFirstVisiblePosition().

Contoh:

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!");
}
Erik
sumber
2
catatan untuk diri sendiri: mImgView.setImageURI () akan memperbarui item daftar tetapi hanya sekali; jadi saya lebih baik menggunakan mLstVwAdapter.notifyDataSetInvalidated () sebagai gantinya.
Kellog
Solusi ini berfungsi. Tetapi ini hanya dapat dilakukan dengan yourListView.getChildAt (indeks); dan ubah tampilan. Kedua solusi ini bekerja !!
AndroidDev
apa yang akan terjadi jika saya panggil getView () pada adaptor hanya jika posisinya antara getFirstVisiblePosition () dan getLastVisiblePosition ()? apakah akan bekerja sama? saya pikir ini akan memperbarui tampilan seperti biasa, kan?
pengembang android
Dalam kasus saya, beberapa kali tampilan adalah nol, karena setiap item diperbarui secara tidak sinkron, lebih baik untuk memeriksa apakah tampilan! = Null
Khawar
2
Bagus sekali. Ini membantu saya menyelesaikan tugas menerapkan fitur Suka di aplikasi mirip-instagram. Saya menggunakan adaptor khusus, siaran dalam tombol suka di dalam elemen daftar, meluncurkan asynctask untuk memberi tahu backend tentang sejenisnya dengan penerima siaran pada fragmen induk, dan akhirnya jawaban Erik untuk memperbarui daftar.
Josh
71

Pertanyaan ini telah diajukan di Google I / O 2010, Anda dapat melihatnya di sini:

Dunia ListView, waktu 52:30

Pada dasarnya apa yang Romain Guy menjelaskan adalah untuk memanggil getChildAt(int)pada ListViewuntuk mendapatkan pandangan dan (saya pikir) callgetFirstVisiblePosition() untuk mengetahui hubungan antara posisi dan indeks.

Romain juga menunjuk ke proyek yang disebut Shelves sebagai contoh, saya pikir dia mungkin bermaksud metode ShelvesActivity.updateBookCovers(), tetapi saya tidak dapat menemukan panggilan getFirstVisiblePosition().

PEMBARUAN LUAR BIASA DATANG:

RecyclerView akan memperbaikinya dalam waktu dekat. Seperti yang ditunjukkan pada http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , Anda akan dapat memanggil metode untuk menentukan perubahan dengan tepat, seperti:

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

Juga, semua orang akan ingin menggunakan tampilan baru berdasarkan RecyclerView karena mereka akan dihargai dengan animasi yang terlihat bagus! Masa depan terlihat luar biasa! :-)

mreichelt
sumber
3
Hal baik! :) Mungkin Anda dapat menambahkan cek nol di sini juga - v mungkin nol jika tampilan tidak tersedia saat ini. Dan tentu saja data ini akan hilang jika pengguna menggulir ListView, jadi orang juga harus memperbarui data dalam adaptor (mungkin tanpa memanggil notifyDataSetChanged ()). Secara umum saya pikir itu akan menjadi ide yang baik untuk menyimpan semua logika ini di dalam adaptor, yaitu meneruskan referensi ListView untuk itu.
mreichelt
Ini bekerja untuk saya, kecuali - ketika tampilan meninggalkan layar, ketika masuk kembali perubahan diatur ulang. Saya kira karena dalam getview masih menerima informasi asli. Bagaimana saya mengatasi ini?
gloscherrybomb
6

Beginilah cara saya melakukannya:

Item Anda (baris) harus memiliki id unik sehingga Anda dapat memperbaruinya nanti. Atur tag setiap tampilan saat daftar mendapatkan tampilan dari adaptor. (Anda juga dapat menggunakan tag kunci jika tag default digunakan di tempat lain)

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

Untuk pembaruan, periksa setiap elemen daftar, jika ada tampilan dengan id yang diberikan, itu terlihat sehingga kami melakukan pembaruan.

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

Ini sebenarnya lebih mudah daripada metode lain dan lebih baik ketika Anda berurusan dengan id bukan posisi! Anda juga harus menghubungi pembaruan untuk item yang terlihat.

Ali
sumber
3

dapatkan kelas model sebagai global seperti objek kelas model ini

SampleModel golbalmodel=new SchedulerModel();

dan inisialisasi ke global

dapatkan baris tampilan saat ini oleh model dengan menginisialisasi untuk model global

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

atur nilai yang diubah ke metode objek model global yang akan ditetapkan dan tambahkan notifyDataSetChanged kerjanya untuk saya

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Berikut ini adalah pertanyaan terkait dengan jawaban yang bagus.

koteswara DK
sumber
Ini adalah pendekatan yang tepat, karena Anda mendapatkan objek yang ingin Anda perbarui, perbarui dan kemudian beri tahu adaptor, dan itu akan mengubah informasi baris dengan data baru.
Gastón Saillén
2

Jawabannya jelas dan benar, saya akan menambahkan ide untuk CursorAdapterkasus di sini.

Jika Anda subklasifikasi CursorAdapter(atau ResourceCursorAdapter, atau SimpleCursorAdapter), maka Anda bisa menerapkan ViewBinderatau menimpa bindView()dan newView()metode, ini tidak menerima indeks item daftar saat ini dalam argumen. Karena itu, ketika beberapa data tiba dan Anda ingin memperbarui item daftar yang terlihat relevan, bagaimana Anda tahu indeksnya?

Solusi saya adalah:

  • simpan daftar semua tampilan daftar item yang dibuat, tambahkan item ke daftar ini dari newView()
  • ketika data tiba, lakukan iterasi dan lihat mana yang perlu diperbarui - lebih baik daripada melakukan notifyDatasetChanged()dan menyegarkan semuanya

Karena melihat daur ulang, jumlah referensi tampilan yang harus saya simpan dan iterate akan kira-kira sama dengan jumlah item daftar yang terlihat di layar.

Pēteris Caune
sumber
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);

Untuk RecycleView silakan gunakan kode ini

Libin Thomas
sumber
saya menerapkan ini pada recycleView, ini berfungsi. Tetapi ketika Anda menggulir daftar itu berubah lagi. ada bantuan?
Fahid Nadeem
1

Saya menggunakan kode yang menyediakan Erik, bekerja dengan baik, tetapi saya memiliki adaptor khusus yang kompleks untuk tampilan daftar saya dan saya dihadapkan dengan dua kali implementasi kode yang memperbarui UI. Saya telah mencoba untuk mendapatkan tampilan baru dari metode getView adapter saya (daftar array yang menyimpan data listview semuanya telah diperbarui / diubah):

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

Ini bekerja dengan baik, tetapi saya tidak tahu apakah ini praktik yang baik. Jadi saya tidak perlu mengimplementasikan kode yang memperbarui daftar item dua kali.

Primoz990
sumber
1

tepatnya saya menggunakan ini

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);
    }
koki
sumber
Saya mendapatkan Tampilan sebagai tata letak Relatif bagaimana menemukan Textview di dalam tata letak relatif?
Girish
1

Saya membuat solusi lain, seperti metode RecyclyerView void notifyItemChanged(int position), membuat kelas CustomBaseAdapter seperti ini:

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

    }
}

Jangan lupa untuk membuat kelas CustomDataSetObservable juga untuk mDataSetObservablevariabel di CustomAdapterClass , seperti ini:

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

di kelas CustomBaseAdapter ada metode notifyItemChanged(int position), dan Anda dapat memanggil metode itu ketika Anda ingin memperbarui baris di mana pun Anda inginkan (dari klik tombol atau di mana pun Anda ingin memanggil metode itu). Dan voila !, baris tunggal Anda akan diperbarui secara instan ..

Aprido Sandyasa
sumber
0

Solusi saya: Jika benar *, perbarui data dan item yang dapat dilihat tanpa menggambar ulang seluruh daftar. NotifyDataSetChanged lain.

Benar - ukuran oldData == ukuran data baru, dan ID data lama beserta pesanannya == ID data baru dan pesanan

Bagaimana:

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

dan tentu saja:

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

Abaikan param terakhir ke bindView (saya menggunakannya untuk menentukan apakah saya perlu mendaur ulang bitmap untuk ImageDrawable).

Seperti yang disebutkan di atas, jumlah total tampilan 'terlihat' kira-kira jumlah yang sesuai pada layar (mengabaikan perubahan orientasi dll), jadi tidak ada masalah besar dalam hal memori.

JRun
sumber
0

Selain solusi ini ( https://stackoverflow.com/a/3727813/5218712 ) hanya ingin menambahkan bahwa itu harus berfungsi hanya jika listView.getChildCount() == yourDataList.size(); Mungkin ada tampilan tambahan di dalam ListView.

Contoh bagaimana elemen anak diisi:  listView.mChildren array

Svyatoslav Ruzhitsky
sumber