在TabLayout+ViewPager使用場景下,往往會有這樣的需求。刪除、添加或者更改一個TabLayout分類。然後刷新ViewPager以及下面的Fragment。那麼調用PagerAdapter的notifyDataSetChanged()方法後,tabLayout的tab是可以更新了,但是Fragment並沒有刷新,並且如果修改某一個分類的情況下Adapter中的getItem方法也沒有執行回調。
如果你的需求是要求每次修改更新某一個分類並且其分類下面的數據也要刷新變化,那麼有如下方法可以解決:
解決辦法:
在你的自定義的PagerAdapter類中重寫getItemPosition()並且返回POSITION_NONE
class MyPagerAdapter(val list: List<PosCategory>, val manager: FragmentManager) : FragmentStatePagerAdapter(manager) {
//...省略一些必要方法
//注意這個方法
override fun getItemPosition(`object`: Any): Int {
return PagerAdapter.POSITION_NONE
}
}
在你的Activity中刷新Adapter
//更新數據...
//注意這裏
mPagerAdapter.notifyDataSetChanged()
這樣你的tabLayout分類會刷新同時ViewPager中的Fragment是會重新創建並刷新的。
我的踩坑:
在開發過程中,有這樣的需求,有很多分類,其分類下顯示不同的商品,我們的需求是可以刪除或者添加分類,也可以更改分類的名稱。
一開始以爲不做特殊處理是沒有問題的。後來發現了這樣的場景
假如有A B C D E五個分類,也就是在tabLayout中顯示了五個tab。其A分類下展示的是商品a,B分類下所展示的是商品b.....以此類推。此時我將Tab切換到E顯示的Fragment界面,也代表了E對應的分類的Fragment被加載,然後刪除了E分類,並且調用了PageraAdapter的notifyDataSetChange(),那麼E分類在TabLayout中刪除,TabLayout中只顯示A B C D四個分類。
就在此時我又添加了一個分類G並且G分類下對應的是顯示商品g。那麼添加後並且更新後TabLayout中有A B C D G五個分類,但是G分類下的fragment中顯示的商品任然是商品e。如果你讀到了這裏你應該知道,本來G下應該顯示的是商品g,但是這裏卻顯示了之前刪除的E分類的商品e。這顯然是一個容易跳進來的坑。
所以希望更多的小夥伴注意這個陷阱。
原理分析:
上面的原因想必讀者已經知道了主要原因,那麼就是緩存的原因。
我們爲什麼要重寫PagerAdapter的getItemPosition方法?
進入PagerAdapter的notifyDataSetChanged()方法的源碼,調用了mObservable.notifyChanged()。進入DataSetObservable類的notifyChanged()方法中
這個方法中把持有的每一個Observer的onChanged()方法調用一次,觀察者模式
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
//....
}
ViewPager中的內部類PagerObserver實現了onChanged()方法,並在這個方法中調用了ViewPager類中dataSetChanged()方法。我們主要分析ViewPager的dataSetChanged()方法
void dataSetChanged() {
//...
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
//注意這裏 如果等於POSITION_UNCHANGED 什麼都不做,如果不重寫getItemPosition方法中默認返回的
//就是POSITION_UNCHANGED
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
//調用FragmentStatePagerAdapter或者FragmentPagerAdapter的destroyItem方法移除已緩存的Fragment
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
//...
continue;
}
}
if (isUpdating) {
//finishUpdate方法中提交事務mCurTransaction.commitNowAllowingStateLoss();
mAdapter.finishUpdate(this);
}
//...
}
如果newPos==POSITION_UNCHANGED則啥也不做,如果newPos==POSITION_NONE那麼會調用destroyItem方法,無論是FragmentStatePagerAdapter還是FragmentPagerAdpater都重寫了這個方法,將Fragment移除。
接下來再看看destroyItem方法中是如何移除Fragment的
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//...
//移除fragment
mCurTransaction.remove(fragment);
if (fragment == mCurrentPrimaryItem) {
mCurrentPrimaryItem = null;
}
}
由於getItemPosition()方法默認返回的是POSITION_UNCHANGED,所以不會將原有的Fragment徹底移除,會複用原有的Fragment,複用了纔會出現我上述的G分類下應該顯示的是商品g,但是卻顯示了之前刪除的E分類的商品e的這個問題。因此我們纔要重寫getIntePosition()並返回return PagerAdapter.POSITION_NONE。