1. 程式人生 > >從原始碼的角度分析為什麼fragmentPagerAdapter.notifyDataSetChanged()無效!

從原始碼的角度分析為什麼fragmentPagerAdapter.notifyDataSetChanged()無效!

首先轉載一篇部落格,瞭解fragmentPagerAdapter和fragmentPagerStateAdapter的區別,對後面的分析很重要:
                 https://blog.csdn.net/DJH2717/article/details/81101834

通過上面的部落格,我們大致知道了這兩個介面卡對fragment的生命週期影響是不一樣的,主要是因為他們destoryItem的方式不一樣.
                 
下面我們來分析為什麼這兩個介面卡的notifyDataSetChanged()都無效的原因:
 
首先,檢視原始碼發現,notifyDataSetChanged()最終會呼叫viewPager的void 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) {
        mItems.remove(i);
        i--;

        if (!isUpdating) {
            mAdapter.startUpdate(this);
            isUpdating = true;
        }

        mAdapter.destroyItem(this, ii.position, ii.object);
        needPopulate = true;

        if (mCurItem == ii.position) {
            // Keep the current item in the valid range
            newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
            needPopulate = true;
        }
        continue;
    }

注意加粗的部分,這個mAdapter.getItemPosition方法官方描述是這樣的:

Called when the host view is attempting to determine if an item's position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed or POSITION_NONE if the item is no longer present in the adapter.
The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.

官方說道,這個方法預設實現是返回的POSITION_UNCHANGED,而此時在dataSetChange中,如果返回的是UNChange,就直接continue了.....我們更新後的資料根本就沒有得到呼叫,,所以這也是為什麼notifyDataSetChanged()無效的最根本的原因了!!

怎麼解決?

網上流傳了一種說法,不要使用fragmentPagerAdapter,直接繼承fragmentStatePagerAdapter並且重寫getItemPosition方法然後返回POSITION_NONE,沒錯,這樣確實可以奏效.

但是為什麼會奏效呢?

如果讀者注意到我前面轉載的那篇部落格,仔細看fragmentStatePagerAdapter的destroyItem方法和instantiateItem方法就會發現,

如果getItemPosition返回的是POSITION_NONE,會觸發destoryItem,然後有這麼兩句:

mFragments.set(position, null);

mCurTransaction.remove(fragment);

然後在instantiateItem中有這麼幾句:

Fragment f = mFragments.get(position);
if (f != null) {
    return f;
}
Fragment fragment = getItem(position);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;

相信看到這裡應該明白為什麼這個方法會奏效了吧,在destroyItem中徹底的移除了當前fragment,然後在instantiateItem中獲取不到快取的fragment,然後就會呼叫我們自己實現的getItem方法,去資料集合中拿資料,所以就奏效了!

但是我想說,這種解決方法不區分這兩個介面卡之間的特性,胡亂使用是不好的,因此如果我們在使用有限個fragment時,仍然希望使用fragmentPagerAdapter來提高效率,經過本人對fragmentPagerAdapter原始碼的分析,有了如下解決方法:

大致思路為,重寫getItemPosition,返回NONE.重寫instantiateItem,在這個方法中對快取的fragment做判斷,判斷當前更新後的資料集合中的fragment和快取的fragment是否是同一個fragment,如果是就直接返回快取的fragment,如果不是就替換,同時設定

viewPager.setOffscreenPageLimit(),如果不設定,會有bug出現.

解決程式碼如下:

@NonNull
@Override
public Object instantiateItem(ViewGroup container, int position) {
    this.position = position;
    Fragment cacheFragment = (Fragment) super.instantiateItem(container, position);
    Fragment currentFragment = fragmentPagerList.get(position);
    //Judge the cache fragment and current fragment from data set is the same.
    if (judgeCurrentSameToCache(currentFragment, cacheFragment)) {
        return cacheFragment;
    } else {
        //This tag is see the source code found it set every fragment a tag,and use the tag to find aging the cache fragment.
        //So we also need set the tag,then we replace the cache fragment by current fragment,it is from data set.
        String tag = cacheFragment.getTag();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(cacheFragment);
        fragmentTransaction.add(container.getId(), currentFragment, tag);
        fragmentTransaction.attach(currentFragment);
        fragmentTransaction.commit();
        return currentFragment;
    }
}

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

/**
 * Judge the current fragment is the same to cache fragment.
 */
private boolean judgeCurrentSameToCache(Fragment currentFragment, Fragment cacheFragment) {
    return currentFragment == cacheFragment && currentFragment.equals(cacheFragment);
}