帶你走過ViewPager不重新整理介面的坑

SamanLan發表於2017-12-13

最近在專案中用到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();}
複製程式碼
  1. 在你的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_UNCHANGEDPOSITION_NONE這兩個值。 而預設都是返回POSITION_UNCHANGED 這個返回值會在adapter的instantiateItem()方法裡進行判斷: POSITION_UNCHANGED不重新載入item POSITION_NONE要求重新載入item

而網上的一些解決方案是直接複寫FragmentPagerAdaptergetItemPosition返回POSITION_NONE,這樣做及違反了FragmentPagerAdapter的設計原則(儲存在記憶體,載入更快等)也沒有解決今天這個坑,一樣是介面沒有重新整理的。

繼續說下去

假如返回POSITION_NONE要求從新載入Item,ViewPager會首先去FragmentManager裡面去查詢有沒有相關的fragment如果有就直接使用如果沒有才會觸發FragmentPageadAptergetItem方法獲取一個fragment。所以你更新的fragmentList集合是沒有作用的,還要清除FragmentManager裡面快取的fragment

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

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

到此,今天的坑又總算是跨過去了,如果有幫組到你,歡迎關注我的部落格github 原始碼下載 本文參考自:

  • http://www.cnblogs.com/lianghui66/p/3607091.html
  • http://blog.sina.com.cn/s/blog_783ede03010173b4.html

2016年9月7日 00:35:49

相關文章