最近在使用ViewPage+FragmentPagerAdapter時遇到一個問題,就是在Fragment數(shù)據(jù)集合改變時候,調(diào)用adapter的notifyDataSetChanged()方法Fragement根本不刷新。
原因
研究FragmentPagerAdapter.instantiateItem源碼發(fā)現(xiàn):
@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時候,會根據(jù)名字在FragmentManager尋找是否有緩存的fragment,如果有的話就會直接只用緩存的fragment,這就是導(dǎo)致Fragment數(shù)據(jù)集改變之后調(diào)用notifyDataSetChanged()方法也不會刷新的原因。
解決方案
1、在設(shè)置了新的Fragment數(shù)據(jù)集之后,設(shè)置標(biāo)記位,在instantiateItem中替換FragmentManager緩存的fragment,代碼如下:
private ArrayList<Fragment> fragments;
private FragmentManager fm;
private boolean[] flags;//標(biāo)識,重新設(shè)置fragment時全設(shè)為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();
/**替換完成后設(shè)為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。
參見源碼可以發(fā)現(xiàn)notifyDataSetChanged()方法會回調(diào)ViewPager的dataSetChanged()方法,在該方法中會根據(jù)getItemPosition()的返回值進(jìn)行判斷,如果是POSITION_UNCHANGED(默認(rèn)返回)則什么都不做直接continue,如果是POSITION_NONE,則會調(diào)用PagerAdapter.destroyItem()并設(shè)置needPopulate為true,才會觸發(fā)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是根據(jù)viewId和getItemId(position)確定的,我們可以通過修改getItemId(position)方法來讓mFragmentManager找不到fragment從而去使用新的fragment替換。getItemId(position)默認(rèn)返回的是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;
}
兩種方法都親測有效,當(dāng)然大家也可以繼續(xù)研究源碼使用其他方法。
參考文檔:
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html