首先转载一篇博客,了解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); }