从源码分析 Android 中 ViewPager 的 Adapter 数据不更新问题
在 Android 中使用 ViewPager
时,经常需要动态增删 View
或者 Fragment
,但是当更新完数据并调用 notifyDataSetChanged()
方法之后,发现数据并没有更新。
对于一般类似组件的 BaseAdapter
比如 ListView
、GridView
等只需要调用 notifyDataSetChanged()
方法,就会直接触发数据更新。因此,很多人认为这是 ViewPager
的一个 bug 。
通常的解决方案是重写 PagerAdapter
的 getItemPosition()
方法,并返回 POSITION_NONE
,以触发 PagerAdapter
销毁并重建对象。
下面我们通过源代码分析为什么必须 @override
这个方法。
首先看 PagerAdapter 源代码,它的 notifiDataSetChanged()
方法内部调用了一个 DataSetObservable
对象的 notifyChanged()
方法:
public void notifyDataSetChanged() {
mObservable.notifyChanged();
}
然后看看 DataSetObservable 源代码,它的 notifyChanged()
方法内部调用了 DataSetObserver
的 onChanged()
方法:
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();
}
}
}
最后来看 ViewPager 源代码,它内部定义了一个继承自 DataSetObserver
的 PagerObserver
内部类:
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
而这个 PagerObserver
类的 onChanged()
方法内部调用了 dataSetChanged()
方法,那我们来看看这个方法的内部实现:
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
int newCurrItem = -1;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (newCurrItem >= 0) {
// TODO This currently causes a jump.
setCurrentItemInternal(newCurrItem, false, true);
needPopulate = true;
}
if (needPopulate) {
populate();
requestLayout();
}
}
从中我们看到,它会判断 adapter
的 getItemPosition()
方法返回值,只有当返回值为 POSITION_NONE
时,才会调用 item
的 remove()
方法以及 adapter
的 startUpdate()
和 destroyItem()
两个方法,进而去更新数据。而如果返回值为 POSITION_UNCHANGED
,则不执行任何操作。
再回过头看看 PagerAdapter
的 getItemPosition()
方法的默认实现:
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
可以看到这个方法默认返回的恰好是 POSITION_UNCHANGED
,这样就不难理解为什么我们必须 @override
这个方法了。