Day3(上) 一篇文章帶你喫透ViewPager的三大用法及刷新問題

今天我們來一起實現“愛閱”首頁滑動切換分類瀏覽閱讀的效果,並將ViewPager和TabLayout結合起來用以實現頂部導航欄的分類展示,並增加點擊快速切換分類的功能。ViewPager+TabLayout也是當前最炙手可熱的組合方式,我會首先對基礎的知識點做一些講解,然後對我們今天的內容進行實現。

首先看一下效果圖:

點此進入目錄:[乾貨] 十天 教你從創意到上線APP

1、ViewPager簡介

ViewPager是android擴展包v4包中的類,這個類可以讓用戶左右切換當前的view
- ViewPager類直接繼承了ViewGroup類,所以它是一個容器類,可以在其中添加其他的view類。
- ViewPager類需要一個PagerAdapter適配器類給它提供數據。
- ViewPager經常和Fragment一起使用,並且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中的ViewPager使用。

2、ViewPager的適配器

上文的簡介中提到了PagerAdapter,其實和RecyclerView、ListView等控件使用一樣,ViewPager需要設置PagerAdapter來完成頁面和數據的綁定,這個PagerAdapter是一個基類適配器,我們經常用它來實現App引導圖,它的子類有FragmentPagerAdapter和FragmentStatePagerAdapter,這兩個子類適配器用於和Fragment一起使用,在安卓應用中它們就像RecyclerView一樣出現的頻繁。

(1)實現一個最基本的PagerAdapter
    public class AdapterViewpager extends PagerAdapter {
        private List<View> mViewList;

        public AdapterViewpager(List<View> mViewList) {
            this.mViewList = mViewList;
        }

        @Override
        public int getCount() {//必須實現
            return mViewList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {//必須實現
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {//必須實現,實例化
            container.addView(mViewList.get(position));
            return mViewList.get(position);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {//必須實現,銷燬
            container.removeView(mViewList.get(position));
        }
    }
  • instantiateItem()
    可以看到instantiateItem()做了兩件事,第一:將當前視圖添加到container中,第二:返回當前View。也就是說instantiateItem()的功能是創建指定位置的頁面視圖,並且適配器有責任增加即將創建的View視圖添加到這裏給定的container中。它的返回值代表新增視圖頁面的Object(Key),這裏沒必要非要返回視圖本身,也可以返回可以代表當前頁面的任意值,只要你可以與你增加的View一一對應即可,比如position變量也可以做爲Key。

  • isViewFromObject()
    該函數用來判斷 instantiateItem() 函數所返回來的 Key 與一個頁面視圖是否是代表的同一個視圖(即它倆是否是對應的,對應的表示同一個 View),如果對應的是同一個View回 true,否則返回 false。

  • destroyItem()
    該方法的功能是移除一個給定位置的頁面,適配器有責任從容器中刪除這個視圖,這是爲了確保在 finishUpdate(viewGroup) 返回時視圖能夠被移除。

不過說道destroyItem()這個方法的時候就不得不提及ViewPager的刷新問題了,因爲ViewPager的刷新並不是我們最初想的調用一下notifyDataSetChanged()就完事了這麼簡單的,我們會在後文說明“愛閱”中遇到的坑和解決辦法。

(2)實現一個最基本的FragmentPagerAdapter
/**
 * Created by   : WGH.
 */
public class ViewPagerAdapter extends FragmentPagerAdapter {
    private ArrayList<Category> mCategoryList;
    private Context mContext;
    private Fragment mFragment;

    public ViewPagerAdapter(FragmentManager fm, Context context) {
        super(fm);
        mContext = context;
        mCategoryList = DataCacheHelper.getInstance().getPagerChildCategories();
    }

    public void onCategorysChange(String key) {
        if (key != null) {
            mCategoryList = DataCache.getInstance().getChildCategorys(key);
            notifyDataSetChanged();
        } else {
            DLog.e("onCategorysChange() Error!");
        }
    }

    @Override
    public Fragment getItem(int position) {// 必須實現
        return ViewPagerFragment.newInstance(mContext, mCategoryList.get(position));
    }

    @Override
    public int getCount() {// 必須實現
        if (mCategoryList != null) {
            return mCategoryList.size();
        } else {
            return 0;
        }
    }

    @Override
    public CharSequence getPageTitle(int position) {// 選擇性實現
        return mCategoryList.get(position).getName();
    }

    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

FragmentStatePagerAdapter的實現和FragmentPagerAdapter的實現一樣就不在寫了。而這裏的getItemPosition()之所以這樣寫同樣和ViewPager的刷新有關,我們後文講解。

三個適配器的區別:

PagerAdapter是基類適配器是一個通用的ViewPager適配器,相比PagerAdapter,FragmentPagerAdapter和FragmentStatePagerAdapter更專注於每一頁是Fragment的情況,而這兩個子類適配器使用情況也是有區別的。FragmentPagerAdapter適用於頁面比較少的情況,FragmentStatePagerAdapter適用於頁面比較多的情況,我們可以從兩個適配器的源碼得知。

  • FragmentStatePagerAdapter
@Override
  public Object instantiateItem(ViewGroup container, int position) {
      if (mFragments.size() > position) {
          Fragment f = mFragments.get(position);// fragment被釋放後這裏得到的null值
          if (f != null) {
              return f;
          }
      }

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }

      Fragment fragment = getItem(position);// fragment被釋放後或者是初次進入頁面拿到新的Fragment實例
      if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
      if (mSavedState.size() > position) {
          Fragment.SavedState fss = mSavedState.get(position);
          if (fss != null) {
              fragment.setInitialSavedState(fss);
          }
      }
      while (mFragments.size() <= position) {
          mFragments.add(null);
      }
      fragment.setMenuVisibility(false);
      fragment.setUserVisibleHint(false);
      mFragments.set(position, fragment);
      mCurTransaction.add(container.getId(), fragment);// 新的Fragment實例 是add上去的

      return fragment;
  }

 @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      Fragment fragment = (Fragment) object;

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }
      if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
              + " v=" + ((Fragment)object).getView());
      while (mSavedState.size() <= position) {
          mSavedState.add(null);
      }
      mSavedState.set(position, fragment.isAdded()
              ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
      mFragments.set(position, null);// 真正釋放了fragment實例

      mCurTransaction.remove(fragment);
  }
  • FragmentPagerAdapter
    @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);// 因爲fragment實例沒有被真正釋放,所以可以直接attach效率高。
        } else {
            fragment = getItem(position);// 初始化頁面的時候拿到fragment的實例
            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;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);// 並沒有真正釋放fragment對象只是detach
    }

從源碼中我們可以看出:FragmentStatePagerAdapter中fragment實例在destroyItem的時候被真正釋放,FragmentPagerAdapter中的fragment實例在destroyItem的時候並沒有真正釋放fragment對象只是detach。所以FragmentStatePagerAdapter省內存,而FragmentPagerAdapter會消耗更多的內存,帶來的好處就是效率更高一些。所以得出這樣的結論:FragmentPagerAdapter適用於頁面比較少的情況,FragmentStatePagerAdapter適用於頁面比較多的情況,但是對性能有要求的情況下,推薦使用FragmentPagerAdapter。因此,“愛閱”爲了突出性能,選擇了FragmentPagerAdapter進行業務功能實現。

4、ViewPager的翻頁動畫

爲ViewPager設置適配器後,就可以正常使用了,接下來我們爲ViewPager增加翻頁動畫,ViewPager提供了PageTransformer接口用於實現翻頁動畫。

官方提供的PageTransformer實現例子
public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        Log.d("DepthPageTransformer", view.getTag() + " , " + position + "");
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;

    @SuppressLint("NewApi")
    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        Log.e("TAG", view + " , " + position + "");

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 1) 
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0) {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
                    / (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

實現翻頁動畫的關鍵就是重寫transformPage方法,方法裏有兩個參數view和position,理解這兩個參數非常重要。假設有三個頁面view1、view2、view3從左至右在viewPager中顯示:往左滑動時view1、view2、view3的position都是不斷變小的;往右滑動時view1、view2、view3的position都是不斷變大的。當position是正負無窮大時view就離開屏幕視野了。因此最核心的控制邏輯是在[-1,0]和(0,1]這兩個區間,通過設置透明度、平移、旋轉、縮放等動畫組合可以實現各式各樣的頁面變化效果。

5、ViewPager的高級用法

這裏主要介紹的是ViewPager結合第三方庫實現小圓點指示器效果和ViewPager結合design庫實現tab切換,首先我們看下實現效果:

代碼並不很複雜就不過多囉嗦了,大家可以到這裏來獲取源碼:ViewPager指示器效果

6、ViewPager使用中遇到的坑

在“愛閱”的開發過程中我遇到了這樣的現象:更新數據源之後視圖並沒有立即刷新,多滑動幾次再次回到更新的Item時才更新。

對應刷新部分的代碼編寫是這樣的:
    public void onCategorysChange(String key) {
        if (key != null) {
            mCategoryList = DataCache.getInstance().getChildCategorys(key);
            notifyDataSetChanged();
        } else {
            DLog.e("onCategorysChange() Error!");
        }
    }

並且不僅僅是更新數據,在單純的添加和刪除數據的時候同樣會出現這樣的問題。那麼究竟是什麼原因呢?我們對上文提到的三種適配器分別做出方案解答。

(1)PagerAdapter的解決方案

先來了解下 ViewPager 的刷新過程:
- 刷新的起始
ViewPager 的刷新是從調用其 PagerAdapter 的 notifyDataSetChanged() 方法開始的,那先看看該方法的源碼:

public void notifyDataSetChanged() {
    synchronized (this) {
        if (mViewPagerObserver != null) {
            mViewPagerObserver.onChanged();
        }
    }
    mObservable.notifyChanged();
}
  • DataSetObservable 的 notifyChanged()
    上面的方法中出現了兩個關鍵的成員變量:
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;

發現這是我們熟知的觀察者模式,接下來看看 mObservable.notifyChanged() 做了些什麼工作:

public void notifyChanged() {
    synchronized(mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged(); 
        }
    }
}

notifyChanged() 方法中是很典型的觀察者模式中遍歷所有的 Observer,通知變化發生了的代碼,接下來看看這個 mObservers 包含哪些 Observer 。

  • DataSetObserver
    直接從 mObservers 點進去你會發現這個:
protected final ArrayList<T> mObservers = new ArrayList<T>();

public abstract class DataSetObserver {
    public void onChanged() {
        // Do nothing
    }
    public void onInvalidated() {
        // Do nothing
    }
}
  • PagerObserver 內部類
    PagerObserver 是 ViewPager 中的一個內部類,實現就是調用了 ViewPager 中的 dataSetChanged() 方法,真正的關鍵來了:
private class PagerObserver extends DataSetObserver {
    @Override
    public void onChanged() {
        dataSetChanged();
    }
    @Override
    public void onInvalidated() {
        dataSetChanged();
    }
}
  • ViewPager 的 dataSetChanged()
    這個方法的實現較長,裏面的邏輯看上去挺複雜的,這裏就不展示全部的源碼了,列下關鍵點:
for (int i = 0; i < mItems.size(); i++) {
    final ItemInfo ii = mItems.get(i);
    final int newPos = mAdapter.getItemPosition(ii.object);

    if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue;
    }

    if (newPos == PagerAdapter.POSITION_NONE) {
        ...
        continue;
    }
}

上面截取的代碼中 for 循環裏面有兩個 continue 語句,這可能是比較關鍵的代碼,幸好不用我們繼續深入了,官方給出瞭解釋:如果 Item 的位置如果沒有發生變化,則返回 POSITION_UNCHANGED。如果返回了 POSITION_NONE,表示該位置的 Item 已經不存在了。默認的實現是假設 Item 的位置永遠不會發生變化,而返回 POSITION_UNCHANGED。

說道這裏,我們需要了解下 Viewpager 的刷新過程:

在每次調用 PagerAdapter 的 notifyDataSetChanged() 方法時,都會激活 getItemPosition(Object object) 方法,該方法會遍歷 ViewPager 的所有 Item(由緩存的 Item 數量決定,默認爲當前頁和其左右加起來共3頁,這個可以自行設定,但是至少會緩存2頁),爲每個 Item 返回一個狀態值(POSITION_NONE/POSITION_UNCHANGED),如果是 POSITION_NONE,那麼該 Item 會被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉然後重新加載,如果是 POSITION_UNCHANGED就不會重新加載。默認是 POSITION_UNCHANGED,所以如果不重寫 getItemPosition(Object object)並修改返回值,就無法看到 notifyDataSetChanged() 的刷新效果。

最簡單的解決方案:

那就是直接重寫PagerAdapter的getItemPosition(Object object)方法,將返回值固定爲POSITION_NONE。正如前文所寫的那樣:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
該方案的缺點:

有個很明顯的缺陷,那就是會刷新所有的 Item,這將導致系統資源的浪費,所以這種方式不適合數據量較大的場景。

注意:

這種方式還有一個需要注意的地方,就是重寫 destoryItem() 方法:

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    // 把 Object 強轉爲 View,然後將 view 從 ViewGroup 中清除
    container.removeView((View) object);
}

最簡方案的優化

在 instantiateItem() 方法中給每個 View 添加 tag(使用 setTag() 方法),然後在 getItemPosition() 方法中通過 View.getTag() 來判斷是否是需要刷新的頁面,是就返回 POSITION_NONE,否就返回 POSITION_UNCHANGED。

注意:

這裏有一點要注意的是,當清空數據源的時候需要返回 POSITION_NONE,可用如下代碼:

if (mDataList != null && mDataList.size()==0) {
    return POSITION_NONE;
}

關於 PagerAdapter 的介紹就到這裏了,雖然 FragmentPagerAdapter 與 FragmentStatePagerAdapter 都是繼承自 PagerAdapter。但是這兩個是專門爲以 Fragment 爲 Item 的 ViewPager 所準備的,所以有其特殊性,我們下面來介紹。

(2)FragmentPagerAdapter的解決方案

上面通過使 getItemPosition() 方法返回 POSITION_NONE 到達數據源變化(也就是調用 notifyDataSetChanged())時刷新視圖的目的。但是當我們使用 Fragment 作爲 ViewPager 的 Item 時,就需要多考慮一些了,而且一般是使用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter。

解決方案:清除 FragmentManager 中緩存的 Fragment

當數據源發生變化時,先將 FragmentManger 裏面所有緩存的 Fragment 全部清除,然後重新創建,這樣達到刷新視圖的目的。下面給出核心代碼:

public class FPagerAdapter1 extends FragmentPagerAdapter {

    private ArrayList<Fragment> mFragmentList;
    private FragmentManager mFragmentManager;

    public FPagerAdapter1(FragmentManager fm, List<Integer> types) {
        super(fm);
        this.mFragmentManager = fm;
        mFragmentList = new ArrayList<>();
        for (int i = 0, size = types.size(); i < size; i++) {
            mFragmentList.add(FragmentTest.instance(i));
        }
        setFragments(mFragmentList);
    }

    public void updateData(List<Integer> dataList) {
        ArrayList<Fragment> fragments = new ArrayList<>();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Log.e("FPagerAdapter1", dataList.get(i).toString());
            fragments.add(FragmentTest.instance(dataList.get(i)));
        }
        setFragments(fragments);
    }

    private void setFragments(ArrayList<Fragment> mFragmentList) {
        if(this.mFragmentList != null){
            FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
            for(Fragment f:this.mFragmentList){
                fragmentTransaction.remove(f);
            }
            fragmentTransaction.commit();
            mFragmentManager.executePendingTransactions();
        }
        this.mFragmentList = mFragmentList;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return this.mFragmentList.size();
    }

    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }
}

但是,這樣做有一個缺點,那就是會造成不必要的浪費,會影響性能。還有就是必須使用一個 List 緩存所有的 Fragment,這也得佔用不少內存,那接下來看看如何去優化。

優化方案:通過 Tag 獲取緩存的 Fragment

首先我們應該知道 FragmentManager 是通過 Tag 找相應的 Fragment,從而達到緩存 Fragment 的目的。如果可以找到,就不會創建新的 Fragment,Fragment 的 onCreate()、onCreateView() 等方法都不會再次調用。那優化的思路就有了:

首先,需要緩存所有 Fragment 的 Tag:
private List<String> mTagList; // 用來存放所有的 Tag

// 生成 Tag:直接從 FragmentPageAdapter 源碼裏拷貝 Fragment 生成 Tag 的方法
private String makeFragmentName(int viewId, int index) {
    return "android:switcher:" + viewId + ":" + index;
}

// 將 Tag 緩存到 List 中
@Override
public Object instantiateItem(ViewGroup container, int position) {
    mTagList.add(position, makeFragmentName(container.getId(),
            (int) getItemId(position)));
    return super.instantiateItem(container, position);
}
其次,在更新 Fragment 時使用相應的 Tag 去 FragmentManamager 中找相應的 Fragment,如果存在就直接更新:
public void update(int position, String str) {
    Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
    if (fragment == null) return;
    if (fragment instanceof FragmentTest) {
        ((FragmentTest)fragment).update(str);
    }
    notifyDataSetChanged();
}

該方法需要自行在 Fragment 中提供。

最後,對於動態改變 ViewPager 中 Fragment 的數量,如果是添加那沒什麼要注意的,但是刪除就有點棘手,這裏給出實例代碼:
public void remove(int position) {
    mDataList.remove(position);
    isDataSetChange = true;
    Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
    mTagList.remove(position);
    if (fragment == null) {
        notifyDataSetChanged();
        return;
    }
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
    fragmentTransaction.remove(fragment);
    fragmentTransaction.commit();
    mFragmentManager.executePendingTransactions();
    notifyDataSetChanged();
}

(3)“愛閱”中的解決方案

“愛閱”中的處理方式和上述優化的方式很像,但是又做了進一步的優化,代碼如下所示:

    public void updateView(String pagerKey) {
        for (int i = 0; i < mViewPagerAdapter.getCount(); i++) {
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(mViewPagerAdapter.getFragmentTag(R.id.viewpager_main, i));
            if (null != fragment) {
                String categoryKey = DataCache.getInstance().getCategory(pagerKey).getNextKey(i);
                if (categoryKey != null) {
                    Category viewPagerCategory = DataCache.getInstance().getCategory(categoryKey);
                    ViewPagerFragment viewPagerFragment = (ViewPagerFragment) fragment;
                    viewPagerFragment.setCategoryData(viewPagerCategory);
                }
            }
        }
    }
    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

可以看到,我們通過getSupportFragmentManager().findFragmentByTag()方法去ViewPager中找到當前緩存的Fragment,經過非空判斷後到數據源中獲取要更新的數據,然後把對應的Fragment中的數據進行更新,以此來實現界面的更新。這樣做的好處是:不用ViewPager進行操作,從而使數據的處理輕量級,不必因爲刷新界面而消耗過多的CPU資源。

(4)FragmentStatePagerAdapter的解決方案

FragmentStatePagerAdapter 與 FragmentPagerAdapter 類似,這兩個類都繼承自 PagerAdapter。但是和 FragmentPagerAdapter 不一樣的是,FragmentStatePagerAdapter 只保留當前頁面,當頁面離開視線後就會被消除並釋放其資源;而在頁面需要顯示時,生成新的頁面(這和 ListView 的實現一樣)。這種方式的好處就是當擁有大量的頁面時,不必在內存中佔用大量的內存。

public class FSPagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<Fragment> mFragmentList;

    public FSPagerAdapter(FragmentManager fm, List<Integer> types) {
        super(fm);
        updateData(types);
    }

    public void updateData(List<Integer> dataList) {
        ArrayList<Fragment> fragments = new ArrayList<>();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Log.e("FPagerAdapter1", dataList.get(i).toString());
            fragments.add(FragmentTest.instance(dataList.get(i)));
        }
        setFragmentList(fragments);
    }

    private void setFragmentList(ArrayList<Fragment> fragmentList) {
        if(this.mFragmentList != null){
            mFragmentList.clear();
        }
        this.mFragmentList = fragmentList;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return this.mFragmentList.size();
    }

    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }
}

上述代碼是對該情況下的一種解決方式,對應的解釋如下:

1、緩存所有的 Fragment

使用一個 List 將數據源對應的 Fragment 都緩存起來

2、更新數據源,刷新 Fragment

當有數據源更新的時候,從 List 中取出相應的 Fragment,然後刷新 Adapter

3、刪除數據時,刪除 List 中對應的 Fragment

當數據源中刪除某項時,將 List 中對應的 Fragment 也刪除,然後刷新 Adapter

總結

關於 ViewPager 的使用和 ViewPager 數據源刷新的問題到此我們就實現完畢了,其中 ViewPager 數據源刷新的問題比較麻煩的地方是從數據源中刪除數據的情況,這和 ViewPager 的實現方式有關,我們在解決該問題的時候要分具體情況來採取不同的方案。

至此爲止,我們今天一半的工作量就已經完成了,接下來的時間我們一起去結合 TabLayout 實現頂部的導航功能,我們下篇見!

聯繫方式:

簡書:WillFlow
CSDN:WillFlow
微信公衆號:WillFlow

微信公衆號:WillFlow

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章