最近在使用ViewPage+FragmentPagerAdapter时遇到一个问题,就是在Fragment数据集合改变时候,调用adapter的notifyDataSetChanged()
方法Fragement根本不刷新。
原因
研究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;
}
在instantiateItem的时候,生成的fragment会存到FragmentManager
中,下次再instantiateItem同一位置的item时候,会根据名字在FragmentManager寻找是否有缓存的fragment,如果有的话就会直接只用缓存的fragment,这就是导致Fragment数据集改变之后调用notifyDataSetChanged()方法也不会刷新的原因。
解决方案
1、在设置了新的Fragment数据集之后,设置标记位,在instantiateItem中替换FragmentManager缓存的fragment,代码如下:
private ArrayList<Fragment> fragments;
private FragmentManager fm;
private boolean[] flags;//标识,重新设置fragment时全设为true
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (flags != null && flags[position]) {
/**得到缓存的fragment, 拿到tag并替换成新的fragment*/
Fragment fragment = (Fragment) super.instantiateItem(container, position);
String fragmentTag = fragment.getTag();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(fragment);
fragment = fragments.get(position);
ft.add(container.getId(), fragment, fragmentTag);
ft.attach(fragment);
ft.commit();
/**替换完成后设为false*/
flags[position] = false;
if (fragment != null){
return fragment;
}else {
return super.instantiateItem(container, position);
}
} else {
return super.instantiateItem(container, position);
}
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void setFragments(ArrayList<Fragment> fragments) {
if (this.fragments != null) {
flags = new boolean[fragments.size()];
for (int i = 0; i < fragments.size(); i++) {
flags[i] = true;
}
}
this.fragments = fragments;
notifyDataSetChanged();
}
这其中还有一个问题要注意的就是需要重写getItemPosition()
方法并返回POSITION_NONE
。
参见源码可以发现notifyDataSetChanged()方法会回调ViewPager的dataSetChanged()方法,在该方法中会根据getItemPosition()的返回值进行判断,如果是POSITION_UNCHANGED(默认返回)则什么都不做直接continue,如果是POSITION_NONE,则会调用PagerAdapter.destroyItem()并设置needPopulate为true,才会触发instantiateItem()方法去生成新的对象。
2、因为FragmentPagerAdapter.instantiateItem()中mFragmentManager是通过name去寻找是否有缓存的fragment:
final long itemId = getItemId(position);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
而name是根据viewId和getItemId(position)确定的,我们可以通过修改getItemId(position)
方法来让mFragmentManager找不到fragment从而去使用新的fragment替换。getItemId(position)默认返回的是position,我这里使用position+时间戳来替换。
private long time;
public void setFragments(ArrayList<Fragment> fragments) {
time = System.currentTimeMillis();
this.fragments = fragments;
notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return super.getItemId(position) + time;
}
两种方法都亲测有效,当然大家也可以继续研究源码使用其他方法。
参考文档:
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html