解決Android ViewPager不刷新界面的問題

tbow9164 8年前發布 | 54K 次閱讀 ViewPager Android開發 移動開發

最近在項目中用到ViewPager+FragmentPagerAdapter的方式來做界面,其中當adapter的數據源數據更新時,調用adapter.notifyDataSetChanged()更新數據,發現ViewPager并沒有更新,還是原來的數據。

參考了別人的文章以及部分解決的的方法,加上自己的理解,拿出了下面這套解決方案。

目錄:

  1. 問題展示
  2. 解決方案
  3. 問題追究

問題展示

國際慣例,先上問題圖(圖一)以及正常圖(圖二)。

圖一

圖二

解決方案

  1. 在數據源更新的前面加入以下代碼
    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();}
  2. 在你的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);
    自定義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

  1. FragmentPagerAdapter :該類更專注于每一頁均為 Fragment 的情況。 該類內的每一個生成的 Fragment 都將保存在內存之中 ,因此適用于那些相對靜態的頁,數量也比較少的那種;如果需要處理有很多頁,并且數據動態性較大、占用內存較多的情況,應該使用 FragmentStatePagerAdapter 。
  2. FragmentStatePagerAdapter :和 FragmentPagerAdapter 不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實現將只保留當前頁面,當頁面離開視線后,就會被消除,釋放其資源;而在頁面需要顯示時,生成新的頁面(就像 ListView 的實現一樣)。這么實現的好處就是當擁有大量的頁面時,不必在內存中占用大量的內存。
  3. 這兩個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 。

這樣今天的解決方案思路救出來了:

  1. 復寫 notifyDataSetChanged
    @Override
    public void notifyDataSetChanged() {
     // 重寫這個方法,取到子Fragment的數量,用于下面的判斷,以執行多少次刷新
     mChildCount = getCount();
     super.notifyDataSetChanged();
    }
  2. 復寫 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);}
  3. 在 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();}
  4. 這樣就會在 notifyDataSetChanged 的時候刷新視圖,在平時滑動等情況使用緩存視圖,既保留了 FragmentPagerAdapter 的特點,又解決了今天的坑。

 

 

本文參考自:

 

 

來自:http://www.jianshu.com/p/2a8a298caf5f

 

 本文由用戶 tbow9164 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!