在使用ViewPager结合Fragment时,如果第二次无法显示,可能是因为FragmentManager没有正确处理Fragment的状态,或者ViewPager的适配器没有正确处理Fragment的生命周期。
解决方法:
确保你的FragmentPagerAdapter或FragmentStatePagerAdapter重写了getItemPosition()方法,并在Fragment数据改变时返回POSITION_NONE。
使用FragmentStatePagerAdapter而不是FragmentPagerAdapter,因为它能够保存和恢复Fragment的状态,有助于解决内存问题和确保第二次能够正确显示。
确保在适配器中正确实现了getCount()方法,以便ViewPager可以为每一个页面创建或者重用Fragment。
如果你在Fragment的生命周期中做了某些数据初始化或者界面更新的操作,确保这些操作在Fragment可见的情况下进行,例如在onResume()方法中。
如果你使用的是Fragment的回退栈,确保在适配器的getItem()方法中,对于已经存在的Fragment没有重新创建新的实例,而是使用了回退栈中的实例。
如果你在Fragment的onCreateView()方法中进行了布局的初始化,确保你在onCreateView()的布局被请求时,布局是被正确地创建和返回的。
如果你在ViewPager的setAdapter()之后做了任何可能影响Fragment状态的操作,确保这些操作在ViewPager完全设置好之后进行。
如果你在Activity的onCreate()中设置了ViewPager的适配器,确保这个调用是在Activity的onResume()或之后的生命周期中进行的。
如果以上步骤仍然不能解决问题,可以考虑使用日志或调试工具来跟踪Fragment的生命周期和ViewPager适配器的行为,以便找到问题的根源。
有的 建议不用 FragmentPagerAdapter,而改用 FragmentStatePagerAdapter,并且重载 getItemPosition() 并返回 POSITION_NONE,以触发销毁对象以及重建对象。从上面的分析中看,后者给出的建议确实可以达到调用 notifyDataSetChanged() 后,Fragment 被以新的参数重新建立的效果。
但是问题在于,如果我们只能这么解决这个问题,岂不是 FragmentPagerAdapter 就用不上了?最关键的是,二者对应的情况不同。对于页面相对较少的情况,我仍旧希望能够将生成的 Fragment 保存在内存中,在需要显示的时候直接调用,而不要产生生成、销毁对象的额外的开销,这样效率更高。这种情况下,选择 FragmentPagerAdapter 是更适合,不加考虑的选择 FragmentStatePagerAdapter 是不合适的。我们不能够因噎废食。
因此,对于 FragmentPagerAdapter 的解决方案就是,分别重载 getItem() 以及 instantiateItem() 对象。getItem() 只用于生成新的与数据无关的 Fragment;而 instantiateItem() 函数则先调用父类中的 instantiateItem() 取得所对应的 Fragment 对象,然后,根据对应的数据,调用该对象对应的方法进行数据设置。
当然,不要忘记重载 getItemPosition() 函数,返回 POSITION_NONE,这个两个类的解决方案都需要的。二者不同之处在于,FragmentStatePagerAdapter 在会在因 POSITION_NONE 触发调用的 destroyItem() 中真正的释放资源,重新建立一个新的 Fragment;而 FragmentPagerAdapter 仅仅会在 destroyItem() 中 detach 这个 Fragment,在 instantiateItem() 时会使用旧的 Fragment,并触发 attach,因此没有释放资源及重建的过程。
这样,当 notifyDataSetChanged() 被调用后,会最终触发 instantiateItem(),而不管 getItem() 是否被调用,我们都在重载的 instantiateItem() 函数中已经将所需要的数据传递给了相应的 Fragment。在 Fragment 接下来的 onCreateView(), onStart() 以及 onResume() 的事件中,它可以正确的读取新的数据,Fragment 被成功复用了。
这里需要注意一个问题,在 Fragment 没有被添加到 FragmentManager 之前,我们可以通过 Fragment.setArguments() 来设置参数,并在 Fragment 中,使用 getArguments() 来取得参数。这是常用的参数传递方式。但是这种方式对于我们说的情况不适用。因为这种数据传递方式只可能用一次,在 Fragment 被添加到 FragmentManager 后,一旦被使用,我们再次调用 setArguments() 将会导致 java.lang.IllegalStateException: Fragment already active 异常。因此,我们这里的参数传递方式选择是,在继承的 Fragment 子类中,新增几个 setter,然后通过这些 setter 将数据传递过去。反向也是类似。相关信息可以参考 [5]。哦,这些 setter 中要注意不要操作那些 View,这些 View 只有在 onCreateView() 事件后才可以操作。
针对 FragmentPagerAdapter 的解决办法如下列代码所示:
1 @Override
2 public Fragment getItem(int position) {
3 MyFragment f = new MyFragment();
4 return f;
5 }
6
7 @Override
8 public Object instantiateItem(ViewGroup container, int position) {
9 MyFragment f = (MyFragment) super.instantiateItem(container, position);
10 String title = mList.get(position);
11 f.setTitle(title);
12 return f;
13 }
14
15 @Override
16 public int getItemPosition(Object object) {
17 return PagerAdapter.POSITION_NONE;
18 }