FragmentStatePagerAdapter的刷新问题

众所周知,FragmentStatePagerAdapter是谷歌官方专门为Fragment和ViewPager推出的Adapter,其特点是为Fragment提供缓存,避免重复加载Fragment。这个我们从源代码就可以看得出来:

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
        // 存在缓存表示该Fragment已经加载过了
        // 值得注意的是,这个地方Fragment的缓存是与position绑定在一起的
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

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

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                //Fragment未加载,但是存在缓存的状态信息
                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);

        return fragment;
    }

这就是FragmentStatePagerAdapter加载缓存的代码,其中两个关键点,我都加了中文注释。在这里我要讲的是第一个地方,也就是Fragment是和position绑定在一起的,这样的话就会有一个问题,当我们想要隐藏或者显示(插入)其中某一个Fragment,我们就必须要让所有的Fragment重新走一遍生命周期。

这是因为,想要刷新ViewPager的Fragment必须修改getItemPosition(Object object)这个方法,使其返回POSITION_NONE,但是这样的话,所有的Fragment都会先被remove,然后再重新add。

    /**
     * Called when the host view is attempting to determine if an item's position
     * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
     * item has not changed or {@link #POSITION_NONE} if the item is no longer present
     * in the adapter.
     */
    public int getItemPosition(Object object) {
        return POSITION_UNCHANGED;
    }

然儿这并不是我们想要,因此就有必要寻找一种替代方案。我们依然可以从getItemPosition这个方法入手,getItemPosition返回值表示的是page的位置,也就是说我们可以用这个方法来调整page的位置,而fragment也不需要重新加载。

既然知道问题在哪,只要对症下药就好了。修改一下FragmentStatePagerAdapter的部分逻辑即可,具体代码如下:

  /**
 * Description: Copied from android.support.v4.app.FragmentStatePagerAdapter, Fix bug for that fragments cache not refreshed when adapter items's position changed.
 * FixBug#修复Fragment的位置更新而缓存未更新导致显示错误以及crash的问题
 * Author: xuqingqi
 * E-mail: [email protected]
 * Date: 2017/12/8
 */
@Keep
public abstract class FragmentStatePagerAdapterCompat extends PagerAdapter {

    private static final String TAG = FragmentStatePagerAdapterCompat.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;

    private SparseArray mSavedState = new SparseArray();
    private SparseArray mFragments = new SparseArray();
    private Fragment mCurrentPrimaryItem = null;

    public FragmentStatePagerAdapterCompat(FragmentManager fm) {
        mFragmentManager = fm;
    }

    /**
     * Return the Fragment associated with a specified position.
     */
    public abstract Fragment getItem(int position);

    /**
     * Return a unique identifier for the item at the given position.
     *
     * 

The default implementation returns the given position. * Subclasses should override this method if the positions of items can change.

* * @param position Position within this adapter * @return Unique identifier for the item at position */
//添加一个方法,将Fragment和Id绑定在一起而不是position public int getItemId(int position) { return position; } @Override public void startUpdate(ViewGroup container) { if (container.getId() == View.NO_ID) { throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id"); } } @Override public Object instantiateItem(ViewGroup container, int position) { // If we already have this item instantiated, there is nothing // to do. This can happen when we are restoring the entire pager // from its saved state, where the fragment manager has already // taken care of restoring the fragments we previously had instantiated. int itemId = getItemId(position); Fragment f = mFragments.get(itemId); if (f != null) { return f; } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); Fragment.SavedState fss = mSavedState.get(itemId); if (fss != null) { fragment.setInitialSavedState(fss); } fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); mFragments.put(itemId, fragment); mCurTransaction.add(container.getId(), fragment); 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()); int index = mFragments.indexOfValue(fragment); if (index >= 0) { int itemId = mFragments.keyAt(index); mSavedState.put(itemId, fragment.isAdded() ? mFragmentManager.saveFragmentInstanceState(fragment) : null); mFragments.remove(itemId); } mCurTransaction.remove(fragment); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (fragment != mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem.setMenuVisibility(false); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setMenuVisibility(true); fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); mCurTransaction = null; } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment)object).getView() == view; } @Override public Parcelable saveState() { Bundle state = null; if (mSavedState.size() > 0) { state = new Bundle(); for (int i=0; i< mSavedState.size(); i++) { int itemId = mFragments.keyAt(i); Fragment.SavedState ss = mSavedState.get(itemId); if (ss != null) { String key = "s" + itemId; state.putParcelable(key, ss); } } } for (int i=0; i< mFragments.size(); i++) { int itemId = mFragments.keyAt(i); Fragment f = mFragments.get(itemId); if (f != null && f.isAdded()) { if (state == null) { state = new Bundle(); } String key = "f" + itemId; mFragmentManager.putFragment(state, key, f); } } return state; } @Override public void restoreState(Parcelable state, ClassLoader loader) { if (state != null) { Bundle bundle = (Bundle)state; bundle.setClassLoader(loader); mSavedState.clear(); mFragments.clear(); Iterable keys = bundle.keySet(); for (String key: keys) { if (key.startsWith("f")) { int index = Integer.parseInt(key.substring(1)); Fragment f = mFragmentManager.getFragment(bundle, key); if (f != null) { f.setMenuVisibility(false); mFragments.put(index, f); } else { Log.w(TAG, "Bad fragment at key " + key); } } else if (key.startsWith("s")) { int index = Integer.parseInt(key.substring(1)); Parcelable parcelable = bundle.getParcelable(key); if (parcelable instanceof Fragment.SavedState) { mSavedState.put(index, (Fragment.SavedState) parcelable); } } } } } }

你可能感兴趣的:(Android,Fragment,Adapter)