FragmentPagerAdapter
执行notifyDataSetChanged
刷新无效的根本原因是,其维护的FragmentManager
会缓存其加入的Fragment导致,解决方法自然也是从FragmentManager
入手。
一、问题场景
使用ViewPager+Fragment实现首页的翻页效果,在未登录的情况下,以包含跳转登录的Frament填充:FragmentLoginNeeded
Log如下:
注:MainFragmemtMine对未登录情况有自己的处理
但是在登录后,执行notifyDataSetChanged
操作,页面并没有刷新,刷新代码如下:
boolean login = isLogin();
if (login) {
FragmentMain fragmentMain = //...
fragments.add(fragmentMain);
MainFragmentPerson fragmentPerson = //。。。
fragments.add(fragmentPerson);
fragments.add(FragmentRecord.getInstance());
fragments.add(MainFragmentMessage.getInstance());
} else {
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
}
fragments.add(MainFragmentMine.getInstance("我的"));
mFragments.clear();
mFragments.addAll(fragments);
mFragmentAdapter.notifyDataSetChanged();
此时log如下:
以上可以看出,虽然执行了destory
操作,但instantiateItem
初始化的目标仍然是原来的对象,所以导致明明ViewPager的Adapter数据源都已经有了变化,但界面数据并没有发生变化。这就是现有的问题。
二、源码查看
同样是继承Adapter
,在我们自己实现的Adapter中,在数据源发生变化后,执行Adapter.notifyDataSetChanged()
时数据会刷新至最新;但是FramentPagerAdapter
却没有执行刷新,所以需要从FramentPagerAdapter
的源码查起。
FragmentPagerAdapter的源码中,其他方法未发现异常,但instantiateItem
中对Fragment的处理有点意思代码如下:
对于Fragment的处理是通过FragmentManager.findFragmentByTag()
来实现的(FragmentManager会缓存添加到其事务中的Fragment,下次通过TAG来取出)其中Tag的组成方式如下:
在此,可以看出,Tag是由container.getId()
和getItemId()
决定的,其中container.getId()
的值便是Adapter中View的Id,getItemId()
则默认返回的是position的值:
public long getItemId(int position) {
return position;
}
container的ID和position的组合来控制是否初始化新的Fragment,并没有给Fragment任何改变的机会…,因此问题解决的方法,也在每次初始化Item时,对Fragment的判断上,但是因为源码上对Fragment的判断和操作涉及缓存优化,所以,退而求其次,在destoryItem的时候,将Fragment从FragmentManager清除掉,问题就解决了(仅仅局限于数量少,且一次性初始化完毕的情况)。
三、问题解决
经过以上的分析,解决的方法夜呼之欲出了,重写destoryItem()
方法:如下
最后模仿FragmentPagerAdapter源码中的处理方式,将事务提交就OK了。
@Override
public void finishUpdate(@NonNull ViewGroup container) {
super.finishUpdate(container);
if (mTransaction != null) {
mTransaction.commitNowAllowingStateLoss();
mTransaction = null;
}
}
注:以上处理方法仅限于通过设置mVpFragmentMain.setOffscreenPageLimit(4);
来实现,初次初始化就会全部初始化整个列表的情况。对于Fragment+ViewPager的情况,基本都是用于首页、所以能解决大部分的场景