ListView not refreshing already-visible items

后端 未结 3 1984
长情又很酷
长情又很酷 2020-12-03 05:23

I\'m displaying a list of contacts (name + picture) using the ListView. In order to make the initial load fast, I only load the names first, and defer picture l

相关标签:
3条回答
  • 2020-12-03 05:49

    Here I go answering my own question with a hackaround that I've settled on. Apparently, notifyDataSetChanged() is only to be used if you are adding / removing items. If you are updating information about items that are already displayed, you might end up with visible items not updating their visual appearance (getView() not being called on your adapter).

    Furthermore, calling invalidateViews() on the ListView doesn't seem to work as advertised. I still get the same glitchy behavior with getView() not being called to update on-screen items.

    At first I thought the issue was caused by the frequency at which I called notifyDataSetChanged() / invalidateViews() (very fast, due to updates coming from different sources). So I've tried throttling calls to these methods, but still to no avail.

    I'm still not 100% sure this is the platform's fault, but the fact that my hackaround works seems to suggest so. So, without further ado, my hackaround consists in extending the ListView to refresh visible items. Note that this only works if you're properly using the convertView in your adapter and never returning a new View when a convertView was passed. For obvious reasons:

    public class ProperListView extends ListView {
    
        private static final String TAG = ProperListView.class.getName();
    
        @SuppressWarnings("unused")
        public ProperListView(Context context) {
            super(context);
        }
    
        @SuppressWarnings("unused")
        public ProperListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @SuppressWarnings("unused")
        public ProperListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        class AdapterDataSetObserver extends DataSetObserver {
            @Override
            public void onChanged() {
                super.onChanged();
    
                refreshVisibleViews();
            }
    
            @Override
            public void onInvalidated() {
                super.onInvalidated();
    
                refreshVisibleViews();
            }
        }
    
        private DataSetObserver mDataSetObserver = new AdapterDataSetObserver();
        private Adapter mAdapter;
    
        @Override
        public void setAdapter(ListAdapter adapter) {
            super.setAdapter(adapter);
    
            if (mAdapter != null) {
                mAdapter.unregisterDataSetObserver(mDataSetObserver);
            }
            mAdapter = adapter;
    
            mAdapter.registerDataSetObserver(mDataSetObserver);
        }
    
        void refreshVisibleViews() {
            if (mAdapter != null) {
                for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i ++) {
                    final int dataPosition = i - getHeaderViewsCount();
                    final int childPosition = i - getFirstVisiblePosition();
                    if (dataPosition >= 0 && dataPosition < mAdapter.getCount()
                            && getChildAt(childPosition) != null) {
                        Log.v(TAG, "Refreshing view (data=" + dataPosition + ",child=" + childPosition + ")");
                        mAdapter.getView(dataPosition, getChildAt(childPosition), this);
                    }
                }
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-03 06:00

    According to the documentation:

    void notifyDataSetChanged ()

    Notify any registered observers that the data set has changed. ... LayoutManagers will be forced to fully rebind and relayout all visible views...

    In my case, the items were not visible (then whole RecycleView was outside the screen), and later on when it animated in, the item views didn't refresh either (thus showing the old data).

    Workaround in the Adapter class:

    public void notifyDataSetChanged_fix() {
        // unfortunately notifyDataSetChange is declared final, so cannot be overridden. 
        super.notifyDataSetChanged();
        for (int i = getItemCount()-1; i>=0; i--) notifyItemChanged(i);
    }
    

    Replaced all calls of notifyDataSetChanged() to notifyDataSetChanged_fix() and my RecyclerView happily refreshing ever since...

    0 讨论(0)
  • 2020-12-03 06:14

    Add the following line to onResume() listview.setAdapter(listview.getAdapter());

    0 讨论(0)
提交回复
热议问题