相信用过viewpager的同学都会遇到调用notifydataSetChanged()后不刷新或者不符合预期的问题,今天就来分析分析这里的来龙去脉。这一切还得从viewpager的setAdapter说起:
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(PagerAdapter adapter) {
...(省略若干行,下同)
...
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.registerDataSetObserver(mObserver);
...
...
}
...
}
mAdapter.registerDataSetObserver(mObserver)这里用到了观察者模式,mObserver是PagerObserver的一个实例,而PagerObserver是ViewPager的一个内部类,其声明如下:
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
//这里调用了viewpager的dataSetChanged()方法,下同
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
所以当mAdapter数据有变化调用notifydatasetchanged()刷新时就会调用到PagerObserver 的onChanged方法,继而调用到了viewpager的dataSetChanged()方法:
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < adapterCount;
int newCurrItem = mCurItem;
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 (needPopulate) {
...
...
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
这里看到,调用mAdapter.getItemPosition,如果返回的值是POSITION_UNCHANGED(PagerAdapter的默认实现),则needPopulate为false,就不会调用到setCurrentItemInternal(里面间接调用到instantiateItem(),后面讲到),所以就不会刷新视图。反之,如果返回值是POSITION_NONE,则needPopulate为true,就会调用到setCurrentItemInternal:
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
...
populate(item);
...
}
而populate(item)里面会调用到addNewItem:
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
这里看到调用到了mAdapter.instantiateItem(this, position);而PagerAdapter的instantiateItem里什么都没做。
所以如果我们用的是PagerAdapter,我们需要复写instantiateitem,例如我们可以这么写:
@Override
public Object instantiateItem(ViewGroup view, int position) {
view.addView(mList.get(position));
return mList.get(position);
}
而对于FragmentPagerAdapter,它复写了instantiateitem:
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
其中,getItemId的默认实现:
public long getItemId(int position) {
return position;
}
也就是item对应的下标就是item的id。另外makeFragmentName的实现:
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
这里makeFragmentName相当于为这个fragment组装成一个标识Tag,如果之前没添加过这个fragment,也就是mFragmentManager.findFragmentByTag(name)返回null,那么就调用 getItem(position);获取fragment,然后把这个fragment添加进去。
反之,也就是之前已经添加过这个fragment了,则不会调用 getItem(position)了,而是直接attach上这个fragment。
参考
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html