我正在使用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()
导致重新创建屏幕上的项目,我的代码是正确的。然而,这似乎不是真的,或者我可能错过了一些东西。我怎样才能顺利完成这项工作?
答案 0 :(得分: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);
}
}
}
}
}
答案 1 :(得分:3)
将以下行添加到onResume()
listview.setAdapter(listview.getAdapter());
答案 2 :(得分:0)
根据文件:
void notifyDataSetChanged()
通知任何已注册的观察者数据集已更改。 ... LayoutManagers将被迫完全重新绑定并重新布局所有可见的视图 ......
在我的情况下,项目不可见(然后整个RecycleView在屏幕外),稍后当它动画时,项目视图也没有刷新(因此显示旧数据)。
Adapter类中的解决方法:
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从此开始快乐地刷新......