1. 程式人生 > >ViewPager+Fragment使用中的幾個常見問題總結

ViewPager+Fragment使用中的幾個常見問題總結

1.實現迴圈切換

思路一:在ViewPager的Adapter中返回count的值為 Integer.MAX_VALUE ,進行初始化的時候講ViewPager的 setCurrentItem(int item) 的方法中傳入Integer.MAX_VALUE的一箇中間值,因為Int的最大值是2147483647 如果設它的中間值使用者是很難滑到兩端的,但是並意味著不能滑到兩端。

思路二:在item的兩端各增加一個Item,當ViewPager滑動到第一個Item的時候跳轉到倒數第二Item,當滑動到最後一個Item時候跳轉到第二個Item,設定跳轉的方法一定要用

public void setCurrentItem(int item, boolean smoothScroll)

傳遞引數為false ,這樣跳轉的就會很流暢。

部分實現程式碼

@Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        removeCheck();
        Log.i(TAG, "onPageScrolled: positionOffset = "+positionOffset+ ", positionOffsetPixels = "+positionOffsetPixels);
    }

    @Override
    public void onPageSelected(int position) {
        RceLog.i(TAG, "onPageSelected() position  : " + position);
        if (position == 0) {
            isChange = true;
            tabIndex = moduleList.size() - 2;
        } else if (position == moduleList.size() - 1) {
            isChange = true;
            tabIndex = 1;
        } else {
            tabIndex = position;
          
        }

    }

    @Override
    public void onPageScrollStateChanged(int state) {
        Log.i(TAG, "onPageScrollStateChanged: state = "+state);
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            if (isChange) {
                isChange = false;
                viewPager.setCurrentItem(tabIndex, false);
            }
        }
    }

2.禁止預載入問題

看了一下網上的解決方案,不少人都建議使用ViewPager的setOffscreenPageLimit(int limit),但是通過檢視原始碼發現要禁止預載入使用該方法是無效的。

 private static final int DEFAULT_OFFSCREEN_PAGES = 1;
 public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }

從上面的原始碼可以明顯的看出,使用該方法根本行不通,通過他設定預載入介面至少為一個介面,所以怎麼設定都會預載入的。

方案二:
在Fagment可見的時候在進行載入資料,重寫setUserVisibleHint(boolean isVisibleToUser)方法 進行判斷當前Fragment是否可見

public abstract class BaseFragment extends Fragment {
    protected boolean isUserVisible;
    protected boolean isPrepared;//標誌已經初始化完成

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {
            isUserVisible = true;
            onUserVisible();
        } else {
            isUserVisible = false;
            onUserInvisible();
        }
    }

    /**
     * fragment 可見
     */
    protected void onUserVisible() {
        if (isPrepared && isUserVisible && isAdded()) {
            update();
        }
    }

    /**
     * fragment 不可見
     */
    protected void onUserInvisible() {

    }

    /**
     * 子類重寫此方法,在fragment 可見的時候更新 UI
     */
    protected abstract void update();


}


子類在重寫update方法,在update中進行資料更新

3.FragmentPagerAdapter與FragmentStatePagerAdapter

FragmentPagerAdapter:對於不再需要的fragment,選擇呼叫detach方法,僅銷燬檢視,並不會銷燬fragment例項。再次載入的時候時呼叫attach方法

destroyItem 的處理

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        this.mCurTransaction.detach((Fragment)object);
    }

再次載入時instantiateItem 中的部分程式碼

Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            this.mCurTransaction.attach(fragment);
        } else {
            fragment = this.getItem(position);
            this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
        }

FragmentStatePagerAdapter:會呼叫remove方法銷燬不再需要的fragment,噹噹前事務提交以後,會徹底的將fragmeng從當前Activity的FragmentManager中移除,state標明,銷燬時,會將其onSaveInstanceState(Bundle outState)中的bundle資訊儲存下來,當用戶切換回來,可以通過該bundle恢復生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中儲存一些資料,在onCreate中進行恢復建立。

destroyItem 的處理:呼叫FragmentTransaction的remove方法從FragmentManager中移除Fragment,同時在mSaveState中儲存了Fragment的狀態。

 public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        while(this.mSavedState.size() <= position) {
            this.mSavedState.add((Object)null);
        }

        this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
        this.mFragments.set(position, (Object)null);
        this.mCurTransaction.remove(fragment);
    }

再次載入的時候回從mSavedState獲取狀態,設定給Fragment

instantiateItem(@NonNull ViewGroup container, int position) 中的部分程式碼

 fragment = this.getItem(position);
        if (this.mSavedState.size() > position) {
            SavedState fss = (SavedState)this.mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }

通過兩個Adapter的對比FragmentPagerAdapter沒一個Fragment都會儲存在記憶體中,因此使用一些頁面較少的情況,如果介面比較多的情況應該使用FragmentStatePagerAdapter。