解決Android ViewPager不刷新界面的問題
最近在項目中用到ViewPager+FragmentPagerAdapter的方式來做界面,其中當adapter的數據源數據更新時,調用adapter.notifyDataSetChanged()更新數據,發現ViewPager并沒有更新,還是原來的數據。
參考了別人的文章以及部分解決的的方法,加上自己的理解,拿出了下面這套解決方案。
目錄:
- 問題展示
- 解決方案
- 問題追究
問題展示
國際慣例,先上問題圖(圖一)以及正常圖(圖二)。
圖一
圖二
解決方案
- 在數據源更新的前面加入以下代碼
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() { // 重寫這個方法,取到子Fragment的數量,用于下面的判斷,以執行多少次刷新 mChildCount = getCount(); super.notifyDataSetChanged(); } @Override public int getItemPosition(Object object) { if ( mChildCount > 0) { // 這里利用判斷執行若干次不緩存,刷新 mChildCount --; // 返回這個是強制ViewPager不緩存,每次滑動都刷新視圖 return POSITION_NONE; } // 這個則是緩存不刷新視圖 return super.getItemPosition(object);}
較完整代碼一覽
初始化數據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);
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 // 可以刪除這段代碼看看,數據源更新而viewpager不更新的情況 private int mChildCount = 0; @Override public void notifyDataSetChanged() { // 重寫這個方法,取到子Fragment的數量,用于下面的判斷,以執行多少次刷新 mChildCount = getCount(); super.notifyDataSetChanged(); } @Override public int getItemPosition(Object object) { if ( mChildCount > 0) { // 這里利用判斷執行若干次不緩存,刷新 mChildCount --; // 返回這個是強制ViewPager不緩存,每次滑動都刷新視圖 return POSITION_NONE; } // 這個則是緩存不刷新視圖 return super.getItemPosition(object); } // end }
public void update(){ // start // 可以刪除這段代碼看看,數據源更新而viewpager不更新的情況 // 在數據源更新前增加的代碼,將上一次數據源的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下載源碼
可根據注釋刪除對應的代碼,體驗有問題以及正常的情況。
問題追究
首先來理解兩個adapter,都是繼承與pageradapter
- FragmentPagerAdapter :該類更專注于每一頁均為 Fragment 的情況。 該類內的每一個生成的 Fragment 都將保存在內存之中 ,因此適用于那些相對靜態的頁,數量也比較少的那種;如果需要處理有很多頁,并且數據動態性較大、占用內存較多的情況,應該使用 FragmentStatePagerAdapter 。
- FragmentStatePagerAdapter :和 FragmentPagerAdapter 不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實現將只保留當前頁面,當頁面離開視線后,就會被消除,釋放其資源;而在頁面需要顯示時,生成新的頁面(就像 ListView 的實現一樣)。這么實現的好處就是當擁有大量的頁面時,不必在內存中占用大量的內存。
- 這兩個adapter最大的不同在于 instantiateItem() 這個方法
接下來看看adapter里面getItemPosition這個方法
可以返回的值為 POSITION_UNCHANGED 和 POSITION_NONE 這兩個值。
而默認都是返回 POSITION_UNCHANGED
這個返回值會在adapter的 instantiateItem() 方法里進行判斷:
POSITION_UNCHANGED 不重新加載item
POSITION_NONE 要求重新加載item
而網上的一些解決方案是直接復寫 FragmentPagerAdapter 的 getItemPosition 返回 POSITION_NONE ,這樣做及違反了 FragmentPagerAdapter 的設計原則(保存在內存,加載更快等)也沒有解決今天這個坑,一樣是界面沒有刷新的。
繼續說下去
假如返回 POSITION_NONE 要求從新加載Item,ViewPager會首先去 FragmentManager 里面去查找有沒有相關的 fragment 如果有就直接使用如果沒有才會觸發 FragmentPageadApter 的 getItem 方法獲取一個 fragment 。所以你更新的fragmentList集合是沒有作用的,還要清除 FragmentManager 里面緩存的 fragment 。
這樣今天的解決方案思路救出來了:
- 復寫 notifyDataSetChanged
@Override public void notifyDataSetChanged() { // 重寫這個方法,取到子Fragment的數量,用于下面的判斷,以執行多少次刷新 mChildCount = getCount(); super.notifyDataSetChanged(); }
- 復寫 getItemPosition ,根據 mChildCount 判斷是返回 POSITION_UNCHANGED 還是 itemPOSITION_NONE
@Override public int getItemPosition(Object object) { if ( mChildCount > 0) { // 這里利用判斷執行若干次不緩存,刷新 mChildCount --; // 返回這個是itemPOSITION_NONE return POSITION_NONE; } // 這個則是POSITION_UNCHANGED return super.getItemPosition(object);}
- 在 notifyDataSetChanged 之前對 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();}
- 這樣就會在 notifyDataSetChanged 的時候刷新視圖,在平時滑動等情況使用緩存視圖,既保留了 FragmentPagerAdapter 的特點,又解決了今天的坑。
本文參考自:
- http://www.cnblogs.com/lianghui66/p/3607091.html
- http://blog.sina.com.cn/s/blog_783ede03010173b4.html
來自:http://www.jianshu.com/p/2a8a298caf5f