最近在項(xiàng)目中用到ViewPager+FragmentPagerAdapter的方式來做界面,其中當(dāng)adapter的數(shù)據(jù)源數(shù)據(jù)更新時(shí),調(diào)用adapter.notifyDataSetChanged()更新數(shù)據(jù),發(fā)現(xiàn)ViewPager并沒有更新,還是原來的數(shù)據(jù)。
參考了別人的文章以及部分解決的的方法,加上自己的理解,拿出了下面這套解決方案。
目錄:
- 問題展示
- 解決方案
- 問題追究
問題展示
國際慣例,先上問題圖(圖一)以及正常圖(圖二)。


解決方案
- 在數(shù)據(jù)源更新的前面加入以下代碼
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();}
- 在你的adapter類中加入以下代碼
private int mChildCount = 0;
@Overridepublic void notifyDataSetChanged() {
// 重寫這個(gè)方法,取到子Fragment的數(shù)量,用于下面的判斷,以執(zhí)行多少次刷新
mChildCount = getCount();
super.notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這里利用判斷執(zhí)行若干次不緩存,刷新
mChildCount --;
// 返回這個(gè)是強(qiáng)制ViewPager不緩存,每次滑動都刷新視圖
return POSITION_NONE;
}
// 這個(gè)則是緩存不刷新視圖
return super.getItemPosition(object);}
較完整代碼一覽
初始化數(shù)據(jù)
viewPager= (ViewPager) findViewById(R.id.pager);
mList=new ArrayList<Fragment>();
for (int i=1;i<4;i++){
Bundle bundle=new Bundle();
bundle.putString("text","第"+i+"頁");
MyFragment myFragment=new MyFragment();
myFragment.setArguments(bundle);
mList.add(myFragment);
}
adapter=new MyAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
自定義adapter
public class MyAdapter extends FragmentPagerAdapter{
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override public Fragment getItem(int position) {
return mList.get(position);
}
@Override public int getCount() {
return mList.size();
}
// start
// 可以刪除這段代碼看看,數(shù)據(jù)源更新而viewpager不更新的情況
private int mChildCount = 0;
@Override public void notifyDataSetChanged() {
// 重寫這個(gè)方法,取到子Fragment的數(shù)量,用于下面的判斷,以執(zhí)行多少次刷新
mChildCount = getCount();
super.notifyDataSetChanged();
}
@Override public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這里利用判斷執(zhí)行若干次不緩存,刷新
mChildCount --;
// 返回這個(gè)是強(qiáng)制ViewPager不緩存,每次滑動都刷新視圖
return POSITION_NONE;
}
// 這個(gè)則是緩存不刷新視圖
return super.getItemPosition(object);
}
// end
}
更新數(shù)據(jù)源的方法
public void update(){
// start
// 可以刪除這段代碼看看,數(shù)據(jù)源更新而viewpager不更新的情況
// 在數(shù)據(jù)源更新前增加的代碼,將上一次數(shù)據(jù)源的fragment對象從FragmentManager中刪除
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();
}
// End
mList.clear();
for (int i=4;i<7;i++){
Bundle bundle=new Bundle();
bundle.putString("text","第"+i+"頁");
MyFragment myFragment=new MyFragment();
myFragment.setArguments(bundle);
mList.add(myFragment);
}
// 重寫adapter的notifyDataChanged方法
adapter.notifyDataSetChanged();}
demo源碼下載
前往github下載源碼
可根據(jù)注釋刪除對應(yīng)的代碼,體驗(yàn)有問題以及正常的情況。
問題追究
首先來理解兩個(gè)adapter,都是繼承與pageradapter
-
FragmentPagerAdapter:該類更專注于每一頁均為 Fragment 的情況。該類內(nèi)的每一個(gè)生成的 Fragment 都將保存在內(nèi)存之中,因此適用于那些相對靜態(tài)的頁,數(shù)量也比較少的那種;如果需要處理有很多頁,并且數(shù)據(jù)動態(tài)性較大、占用內(nèi)存較多的情況,應(yīng)該使用
FragmentStatePagerAdapter。 -
FragmentStatePagerAdapter:和
FragmentPagerAdapter不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實(shí)現(xiàn)將只保留當(dāng)前頁面,當(dāng)頁面離開視線后,就會被消除,釋放其資源;而在頁面需要顯示時(shí),生成新的頁面(就像 ListView 的實(shí)現(xiàn)一樣)。這么實(shí)現(xiàn)的好處就是當(dāng)擁有大量的頁面時(shí),不必在內(nèi)存中占用大量的內(nèi)存。 - 這兩個(gè)adapter最大的不同在于
instantiateItem()這個(gè)方法
接下來看看adapter里面getItemPosition這個(gè)方法
可以返回的值為POSITION_UNCHANGED和POSITION_NONE這兩個(gè)值。
而默認(rèn)都是返回POSITION_UNCHANGED
這個(gè)返回值會在adapter的instantiateItem()方法里進(jìn)行判斷:
POSITION_UNCHANGED不重新加載item
POSITION_NONE要求重新加載item
而網(wǎng)上的一些解決方案是直接復(fù)寫FragmentPagerAdapter的getItemPosition返回POSITION_NONE,這樣做及違反了FragmentPagerAdapter的設(shè)計(jì)原則(保存在內(nèi)存,加載更快等)也沒有解決今天這個(gè)坑,一樣是界面沒有刷新的。
繼續(xù)說下去
假如返回POSITION_NONE要求從新加載Item,ViewPager會首先去FragmentManager里面去查找有沒有相關(guān)的fragment如果有就直接使用如果沒有才會觸發(fā)FragmentPageadApter的getItem方法獲取一個(gè)fragment。所以你更新的fragmentList集合是沒有作用的,還要清除FragmentManager里面緩存的fragment。
這樣今天的解決方案思路救出來了:
- 復(fù)寫
notifyDataSetChanged
@Override
public void notifyDataSetChanged() {
// 重寫這個(gè)方法,取到子Fragment的數(shù)量,用于下面的判斷,以執(zhí)行多少次刷新
mChildCount = getCount();
super.notifyDataSetChanged();
}
- 復(fù)寫
getItemPosition,根據(jù)mChildCount判斷是返回POSITION_UNCHANGED還是itemPOSITION_NONE
@Override
public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這里利用判斷執(zhí)行若干次不緩存,刷新
mChildCount --;
// 返回這個(gè)是itemPOSITION_NONE
return POSITION_NONE;
}
// 這個(gè)則是POSITION_UNCHANGED
return super.getItemPosition(object);}
- 在
notifyDataSetChanged之前對FragmentManager進(jìn)行相應(yīng)的刪除操作。
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();}
- 這樣就會在
notifyDataSetChanged的時(shí)候刷新視圖,在平時(shí)滑動等情況使用緩存視圖,既保留了FragmentPagerAdapter的特點(diǎn),又解決了今天的坑。
到此,今天的坑又總算是跨過去了,如果有幫組到你,歡迎關(guān)注我的博客和github
源碼下載
本文參考自:
- http://www.cnblogs.com/lianghui66/p/3607091.html
- http://blog.sina.com.cn/s/blog_783ede03010173b4.html
2016年9月7日 00:35:49