Làm cách nào để cập nhật một hàng trong ListView?


131

Tôi có một ListViewtrong đó hiển thị các mục tin tức. Chúng chứa một hình ảnh, một tiêu đề và một số văn bản. Hình ảnh được tải trong một luồng riêng biệt (với một hàng đợi và tất cả) và khi hình ảnh được tải xuống, bây giờ tôi gọi notifyDataSetChanged()bộ điều hợp danh sách để cập nhật hình ảnh. Điều này hoạt động, nhưng getView()được gọi quá thường xuyên, vì notifyDataSetChanged()các cuộc gọi getView()cho tất cả các mục có thể nhìn thấy. Tôi muốn cập nhật chỉ mục duy nhất trong danh sách. Làm thế nào tôi sẽ làm điều này?

Các vấn đề tôi gặp phải với cách tiếp cận hiện tại của tôi là:

  1. Di chuyển chậm
  2. Tôi có một hình ảnh động mờ dần trên hình ảnh xảy ra mỗi khi một hình ảnh mới trong danh sách được tải.

Câu trả lời:


199

Tôi tìm thấy câu trả lời, nhờ thông tin của bạn Michelle. Bạn thực sự có thể có được quan điểm đúng bằng cách sử dụng View#getChildAt(int index). Điều hấp dẫn là nó bắt đầu đếm từ vật phẩm có thể nhìn thấy đầu tiên. Trong thực tế, bạn chỉ có thể nhận được các mục có thể nhìn thấy. Bạn giải quyết điều này với ListView#getFirstVisiblePosition().

Thí dụ:

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
lưu ý đến bản thân: mImgView.setImageURI () sẽ cập nhật mục danh sách nhưng chỉ một lần; vì vậy tôi tốt hơn hết là sử dụng mLstVwAd Module.notifyDataSetInvalidated ().
kelloss

Giải pháp này hoạt động. Nhưng điều này chỉ có thể được thực hiện với yourListView.getChildAt (index); và thay đổi quan điểm. Cả hai giải pháp đều hoạt động !!
AndroidDev

Điều gì sẽ xảy ra nếu tôi chỉ gọi getView () trên bộ điều hợp chỉ khi vị trí nằm giữa getFirstVisibleP vị trí () và getLastVisibleP vị trí ()? nó sẽ làm việc như nhau? Tôi nghĩ rằng nó sẽ cập nhật quan điểm như bình thường, phải không?
nhà phát triển Android

Trong trường hợp của tôi, một số lần xem là null, vì mỗi mục được cập nhật không đồng bộ, tốt hơn để kiểm tra xem view = null
Khawar

2
Công trình tuyệt vời. Nó giúp tôi hoàn thành nhiệm vụ triển khai tính năng Like trong một ứng dụng giống như instagram. Tôi đang sử dụng một bộ chuyển đổi tùy chỉnh, phát trong nút thích bên trong một thành phần danh sách, khởi chạy một asynctask để nói với phụ trợ về phần tương tự với một bộ thu phát trên đoạn gốc và cuối cùng là câu trả lời của Erik để cập nhật danh sách.
Josh

71

Câu hỏi này đã được hỏi tại Google I / O 2010, bạn có thể xem tại đây:

Thế giới của ListView, thời gian 52:30

Về cơ bản những gì Romain Guy giải thích là để gọi getChildAt(int)trên ListViewđể có được những cái nhìn và (tôi nghĩ) gọi getFirstVisiblePosition()để tìm hiểu mối tương quan giữa vị trí và chỉ mục.

Romain cũng chỉ ra dự án có tên Shelves là một ví dụ, tôi nghĩ rằng anh ta có thể có nghĩa là phương pháp ShelvesActivity.updateBookCovers(), nhưng tôi không thể tìm thấy cuộc gọi của getFirstVisiblePosition().

CẬP NHẬT TUYỆT VỜI:

RecyclerView sẽ khắc phục điều này trong tương lai gần. Như đã chỉ ra trên http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , bạn sẽ có thể gọi các phương thức để chỉ định chính xác thay đổi, chẳng hạn như:

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

Ngoài ra, mọi người sẽ muốn sử dụng các chế độ xem mới dựa trên RecyclerView vì chúng sẽ được thưởng bằng các hình ảnh động độc đáo! Tương lai có vẻ tuyệt vời! :-)


3
Điều tốt! :) Có lẽ bạn cũng có thể thêm kiểm tra null tại đây - v có thể là null nếu chế độ xem không khả dụng trong thời điểm này. Và tất nhiên, dữ liệu này sẽ biến mất nếu người dùng cuộn ListView, do đó, người ta cũng nên cập nhật dữ liệu trong bộ điều hợp (có lẽ không cần gọi notifyDataSetChanged ()). Nói chung tôi nghĩ rằng sẽ là một ý tưởng tốt để giữ tất cả logic này trong bộ điều hợp, tức là chuyển tham chiếu ListView cho nó.
mreichelt

Điều này làm việc cho tôi, ngoại trừ - khi chế độ xem rời khỏi màn hình, khi nhập lại, thay đổi được đặt lại. Tôi đoán bởi vì trong getview nó vẫn nhận được thông tin ban đầu. Làm thế nào để tôi có được xung quanh này?
gloscherrybomb

6

Đây là cách tôi đã làm nó:

Các mục (hàng) của bạn phải có id duy nhất để bạn có thể cập nhật chúng sau này. Đặt thẻ của mọi chế độ xem khi danh sách nhận chế độ xem từ bộ điều hợp. (Bạn cũng có thể sử dụng thẻ khóa nếu thẻ mặc định được sử dụng ở nơi khác)

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

Đối với bản cập nhật, hãy kiểm tra mọi yếu tố của danh sách, nếu một chế độ xem với id đã cho thì nó sẽ hiển thị để chúng tôi thực hiện cập nhật.

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

Nó thực sự dễ dàng hơn các phương thức khác và tốt hơn khi bạn xử lý các id không phải vị trí! Ngoài ra, bạn phải gọi cập nhật cho các mục có thể nhìn thấy.


3

lấy lớp mô hình đầu tiên là toàn cầu như đối tượng lớp mô hình này

SampleModel golbalmodel=new SchedulerModel();

và khởi tạo nó ra toàn cầu

lấy hàng hiện tại của mô hình bằng cách khởi tạo mô hình đó cho mô hình toàn cầu

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

đặt giá trị thay đổi thành phương thức đối tượng mô hình toàn cầu được đặt và thêm notifyDataSetChanged các công việc của nó cho tôi

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Đây là một câu hỏi liên quan về điều này với câu trả lời tốt.


Đây là cách tiếp cận phù hợp, vì bạn đang nhận được đối tượng mà bạn muốn cập nhật, cập nhật nó và sau đó thông báo cho bộ điều hợp và điều đó sẽ thay đổi thông tin hàng với dữ liệu mới.
Gastón Saillén

2

Các câu trả lời rõ ràng và chính xác, tôi sẽ thêm một ý tưởng cho CursorAdaptertrường hợp ở đây.

Nếu bạn đang phân lớp CursorAdapter(hoặc ResourceCursorAdapter, hoặc SimpleCursorAdapter), thì bạn có thể thực hiện ViewBinderhoặc ghi đè bindView()newView()phương thức, chúng không nhận được chỉ mục mục danh sách hiện tại trong các đối số. Do đó, khi một số dữ liệu đến và bạn muốn cập nhật các mục danh sách hiển thị có liên quan, làm thế nào để bạn biết các chỉ số của chúng?

Cách giải quyết của tôi là:

  • giữ một danh sách tất cả các chế độ xem danh sách đã tạo, thêm các mục vào danh sách này từ newView()
  • khi dữ liệu đến, lặp lại chúng và xem cái nào cần cập nhật - tốt hơn là làm notifyDatasetChanged()và làm mới tất cả chúng

Do chế độ xem tái chế, số lượng tham chiếu chế độ xem tôi sẽ cần lưu trữ và lặp lại sẽ gần bằng số lượng mục danh sách hiển thị trên màn hình.


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

Đối với RecyclView, vui lòng sử dụng mã này


tôi đã thực hiện điều này trên recyclView, nó hoạt động. Nhưng khi bạn cuộn danh sách, nó sẽ thay đổi một lần nữa. Có ai giúp đỡ không?
Fahid Nadeem ngày 25/8/2015

1

Tôi đã sử dụng mã cung cấp Erik, hoạt động rất tốt, nhưng tôi có một bộ điều hợp tùy chỉnh phức tạp cho listview của mình và tôi đã phải đối mặt với việc thực hiện hai lần mã cập nhật giao diện người dùng. Tôi đã cố gắng để có được chế độ xem mới từ phương thức getView của bộ điều hợp (danh sách mảng chứa dữ liệu listview đã được cập nhật / thay đổi):

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

Nó hoạt động tốt, nhưng tôi không biết liệu đây có phải là một thực hành tốt. Vì vậy, tôi không cần phải thực hiện mã cập nhật mục danh sách hai lần.


1

chính xác tôi đã sử dụng cái này

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

Tôi đang nhận View dưới dạng bố cục tương đối làm thế nào để tìm Textview bên trong bố cục tương đối?
Girish

1

Tôi đã tạo ra một giải pháp khác, như phương thức RecyclyerView void notifyItemChanged(int position), tạo lớp CustomBaseAd CHƯƠNG như thế này:

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

    }
}

Đừng quên tạo một lớp CustomDataSetObservable cho mDataSetObservablebiến trong CustomAd ModuleClass , như thế này:

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

trên lớp CustomBaseAd CHƯƠNG có một phương thức notifyItemChanged(int position)và bạn có thể gọi phương thức đó khi bạn muốn cập nhật một hàng bất cứ nơi nào bạn muốn (từ nhấp vào nút hoặc bất cứ nơi nào bạn muốn gọi phương thức đó). Và voila!, Hàng duy nhất của bạn sẽ cập nhật ngay lập tức ..


0

Giải pháp của tôi: Nếu đúng *, hãy cập nhật dữ liệu và các mục có thể xem mà không cần vẽ lại toàn bộ danh sách. Khác thông báoDataSetChanged.

Đúng - kích thước oldData == kích thước dữ liệu mới và ID dữ liệu cũ và thứ tự của chúng == ID dữ liệu mới và thứ tự

Làm sao:

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

và tất nhiên:

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

Bỏ qua thông số cuối cùng để bindView (Tôi sử dụng nó để xác định xem tôi có cần tái chế bitmap cho ImageDrawable hay không).

Như đã đề cập ở trên, tổng số lượt xem 'hiển thị' gần bằng số lượng phù hợp trên màn hình (bỏ qua thay đổi hướng, v.v.), do đó, không có trí nhớ lớn.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.