從原始碼的角度分析為什麼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); }