17

我正在使用ListView. 为了让初始加载更快,我只先加载名字,延迟图片加载。现在,每当我的后台线程完成加载图片时,它都会安排我的适配器notifyDataSetChanged()在 UI 线程上被调用。不幸的是,当这种情况发生时,ListView不会重新渲染(即调用getView())已经在屏幕上的项目。正因为如此,用户看不到新加载的图片,除非他们滚动并返回到同一组项目,以便视图被回收。一些相关的代码:

private final Map<Long, Bitmap> avatars = new HashMap<Long, Bitmap>();

// this is called *on the UI thread* by the background thread
@Override
public void onAvatarLoaded(long contactId, Bitmap avatar) {
    avatars.put(requestCode, avatar);
    notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // snip...
    final Bitmap avatar = avatars.get(contact.id);
    if (avatar != null) {
        tag.avatar.setImageBitmap(avatar);
        tag.avatar.setVisibility(View.VISIBLE);
        tag.defaultAvatar.setVisibility(View.GONE);
    } else {
        tag.avatar.setVisibility(View.GONE);
        tag.defaultAvatar.setVisibility(View.VISIBLE);
        if (!avatars.containsKey(contact.id)) {
            avatars.put(contact.id, null);
            // schedule the picture to be loaded
            avatarLoader.addContact(contact.id, contact.id);
        }
    }
}

AFAICT,如果您认为这notifyDataSetChanged()会导致重新创建屏幕上的项目,那么我的代码是正确的。但是,这似乎不是真的,或者我可能遗漏了一些东西。我怎样才能使这项工作顺利进行?

4

3 回答 3

21

在这里,我用我已经确定的hackaround回答我自己的问题。显然,notifyDataSetChanged()仅在您添加/删除项目时使用。如果您正在更新有关已显示项目的信息,您最终可能会getView()看到可见项目未更新其视觉外观(未在适配器上调用)。

此外,调用似乎并不像宣传invalidateViews()的那样工作。ListView我仍然得到相同的故障行为,getView()没有被调用来更新屏幕上的项目。

起初我认为这个问题是由我调用notifyDataSetChanged()/的频率引起的invalidateViews()(非常快,因为更新来自不同的来源)。因此,我尝试限制对这些方法的调用,但仍然无济于事。

我仍然不能 100% 确定这是平台的错,但我的 hackaround 工作似乎表明了这一点。因此,事不宜迟,我的解决方法包括扩展ListView以刷新可见项目。请注意,这仅适用于您在适配器中正确使用并且在传递a时convertView从不返回新的。出于显而易见的原因:ViewconvertView

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

}
于 2013-10-29T10:42:20.057 回答
4

将以下行添加到 onResume() listview.setAdapter(listview.getAdapter());

于 2014-12-03T16:43:10.410 回答
0

根据文档:

无效的 notifyDataSetChanged ()

通知任何已注册的观察者数据集已更改。... LayoutManagers 将被迫完全重新绑定和重新布局所有可见视图...

在我的情况下,这些项目是不可见的(然后整个 RecycleView 在屏幕之外),后来当它动画时,项目视图也没有刷新(因此显示旧数据)。

适配器类中的解决方法:

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

将 notifyDataSetChanged() 的所有调用替换为 notifyDataSetChanged_fix() 并且我的 RecyclerView 从那时起愉快地刷新......

于 2017-09-17T23:36:37.007 回答