竖屏和横屏使用不同的适配器的ViewPager

6
在竖屏模式下,我的ViewPager有3个片段A、B、C,但在横屏模式下,它只有2个片段A和C。因此,我为每种模式创建了两个FragmentStatePagerAdapter。问题是当屏幕方向改变时,ViewPager会恢复并使用旧方向的先前片段。例如,从纵向到横向旋转时,ViewPager现在显示2个片段A、B而不是A和C。我知道为什么会发生这种情况,但找不到一个好的解决方案。
我的当前解决方法是为ViewPager使用不同的id(例如:id/viewpager_portrait用于纵向,id/viewpager_landscape用于横向布局),以防止重用片段,但这会导致内存泄漏,因为旧片段不会被销毁,仍然保存在内存中。
我尝试了一些解决方法,比如在activity的onCreate中调用super.onCreate(null),或者在activity的onSaveInstanceState中删除ViewPager的片段,但它们都会使我的应用程序崩溃。
所以我的问题是,在方向改变时如何避免在FragmentStatePagerAdapter中重新使用一个或多个片段?
任何帮助将不胜感激。提前感谢。

你尝试过重写 onSaveInstanceState 方法并避免在该方法中调用 super.onSaveInstanceState 吗?这将防止 Fragment 被保存。 - Manish Mulimani
这是我的当前解决方法,但是我避免调用super.onSaveInstanceState,而是清除ViewPager创建的所有状态。我认为这不是最好的解决方案,因此我仍然提出问题,因为有时我会在ViewPager内部看到崩溃。 - Wayne
4个回答

3
问题可能在于Android提供的Fragment内置PagerAdapter实现假定项目将保持不变,因此保留并重复使用对所有添加到ViewPagerFragment的基于索引的引用。即使由于配置更改或进程被杀死而重新创建Activity(和Fragment),这些引用也通过FragmentManager进行维护。
您需要做的是编写自己的PagerAdapter实现,将自定义标记与每个Fragment关联,并以基于标记而不是索引的格式存储Fragment。您可以从现有实现之一中派生这种类型的通用实现,在getItem()方法旁边添加提供基于索引的标记的抽象方法。当然,您还必须从ViewPager中删除先前配置中添加的孤立/未使用的Fragment(同时理想情况下保留其状态)。
如果您不想自己实现整个解决方案,则可以使用CWAC-Pager库中的ArrayPagerAdapter来提供一个合理的实现,只需很少的努力即可。在初始化时,您可以根据提供的标记分离相关Fragment,并根据需要从适配器中删除/添加它。

我知道自己编写适配器可以解决问题,但我认为还有比那更好、更简单的解决方案,所以我在StackOverflow上提问。 - Wayne
@Wayne:我在答案的末尾添加了更多信息,并建议使用CWAC-Pager库。 - corsair992
谢谢corsair992,我的解决方案是复制PagerAdapter源代码并根据您的答案进行一些代码更改。 非常抱歉,上个周末我不在线,所以忘记给你完整的赏金了。 - Wayne

1
为什么在使用ViewPager时要使用两个不同的ID,当你可以在方向改变时删除Fragment B呢?
你可以像这样在onCreateView()或onResume()中检索你的Fragments(这个例子适用于父Fragment,但也可在父Activity中使用,比如在onResume()中):
@Override
public void onResume() {
    super.onResume();

    // ... initialize components, etc.

    pager.setAdapter(pagerAdapter);

    List<Fragment> children = getChildFragmentManager().getFragments();

    if (children != null) {
        pagerAdapter.restoreFragments(children, orientation);
    }
}

然后在你的适配器内部:

@Override
public void restoreFragments(List<Fragment> fragments, int orientation) {
    List<Fragment> fragmentsToAdd = new ArrayList<Fragment>();
    Collections.fill(fragmentsToAdd, null);
    if (Configuration.ORIENTATION_LANDSCAPE == orientation) {
        for (Fragment f : fragments) {
            if (!(f instanceof FragmentB)) {
                fragmentsToAdd.add(f);
            }
        }
    }
    this.fragmentsInAdapter = Arrays.copyOf(temp.toArray(new Fragment[0]), this.fragments.length); // array of all your fragments in your adapter (always refresh them, when config changes, else you have old references in your array!
    notifyDataSetChanged(); // notify, to remove FragmentB
}

那应该可以工作。

顺便说一下,如果您正在使用Support Library V13,则无法使用FragmentManager.getFragments(),因此您需要通过id或标签获取它们。


1

哦,我忘记了getItemPosition()。也许这会是一个好的解决方案。谢谢。 - Wayne
1
这个答案是不正确的。ViewPager 只会在 PagerAdapter 上调用 notifyDataSetChanged() 时查找项目位置的更改,而且这对于从 FragmentStatePagerAdapter 中删除项目来说也无法正常工作,因为它假定项目是不可变的,并且会破坏其保存的状态关联。对于 FragmentPagerAdapter 也不起作用,因为它也假设相同的事情,并且将只重用原始的 Fragment - corsair992

1

在您的Activity中覆盖OnConfigurationChanged()方法(同时在清单文件中为该Activity添加android:configChanges="orientation"),这样您就可以手动管理方向更改。现在,您只需要在OnOrientaionChanged()方法中更改适配器(还要跟踪当前位置)。这样,您就可以使用单个布局、单个ViewPager,并且不必担心片段无法被回收利用(好吧,您将有很多工作要做来弥补这一点)。 祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接