ListView没有刷新已经可见的项目

Fel*_*lix 17 multithreading android android-listview

我正在使用显示联系人列表(姓名+图片)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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

AFAICT,如果你认为这notifyDataSetChanged()会导致重新创建屏幕上的项目,我的代码是正确的.然而,这似乎不是真的,或者我可能错过了一些东西.我怎样才能顺利完成这项工作?

Fel*_*lix 21

在这里,我以一个我已经解决的hackaround来回答我自己的问题.显然,notifyDataSetChanged()只有在添加/删除项目时才会使用.如果要更新有关已显示的项目的信息,最终可能会显示未更新其可视外观的可见项目(getView()未在适配器上调用).

此外,呼吁invalidateViews()ListView似乎不像宣传的那样工作.我仍然得到同样的毛病行为而getView()没有被调用来更新屏幕上的项目.

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

我仍然不能100%确定这是平台的错,但我的hackaround工作的事实似乎暗示了这一点.所以,不用多说,我的hackaround包括扩展ListView刷新可见项目.请注意,这仅适用convertView于您正确使用适配器中的内容并且在传递View时不再返回新内容的convertView情况.原因很明显:

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

}
Run Code Online (Sandbox Code Playgroud)