向 ViewPager 添加页面

Adding a page to ViewPager

尝试以编程方式将片段页面添加到我的 ViewPager,我得到:

java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged!
Expected adapter item count: 3, found: 2
Pager id: com.my.app:id/view_pager
Pager class: class android.support.v4.view.ViewPager
Problematic adapter: class com.my.app.ui.BaseFragmentPagerAdapter
            at android.support.v4.view.ViewPager.populate(ViewPager.java:1000)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:952)
            at ...

我只是在 FragmentPagerAdapter 实现中调用以下几行:

adapter.addFragment(new Fragment(), "FIRST");
adapter.addFragment(new Fragment(), "SECOND");
pager.setAdapter(adapter);

//later... (on click of a button)
adapter.addFragment(new Fragment(), "THIRD");
adapter.notifyDataSetChanged();

它实际上添加了第三页,但是当我尝试滑动到那里时,它失败并出现上述异常。直到今天,我还以为我对适配器的工作原理有了相当完整的了解。现在我不知道是什么问题。

从调试来看,似乎一直 adapter.getCount() 正确 returns 3(添加第三页后),但是当我到达第三页时它最终 returns 2 和中断,好像有人在上面调用 destroyItem(),但那不是我。

这是我的简单 class:

public class BaseFragmentPagerAdapter extends FragmentPagerAdapter {

    private SparseArray<Fragment> mFragments;
    private ArrayList<String> mFragmentTitles;

    public BaseFragmentPagerAdapter(FragmentManager manager) {
        super(manager);
        this.mFragments = new SparseArray<>();
        this.mFragmentTitles = new ArrayList<>();
    }

    public void addFragment(Fragment f, String title) {
        this.mFragments.append(mFragments.size() , f);
        this.mFragmentTitles.add(title);
    }

    @Override
    public Fragment getItem(int position) {
        return this.mFragments == null ? null : this.mFragments.get(position) ;
    }

    @Override
    public int getItemPosition(Object object) {
        return this.mFragments.indexOfValue((Fragment) object);
    }


    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        this.mFragments.remove(position);
        this.mFragmentTitles.remove(position);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitles.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentTitles.size();
    }

}

请注意,如果我使用 FragmentStatePagerAdapter 而不是 FragmentPagerAdapter,则没有任何变化。

我会自己回答这个问题,因为我在写问题时(经常)找到了答案。我不确定这是最好的解决方案,但它确实有效。

基本上,当转到第 3 页时,由于它不能直接刷到,适配器将调用第 1 页上的 destroyItem()。不应该 FragmentPagerAdapter 将所有项目保存在内存中而不销毁它们?

好吧,我确实通过 mFragments 字段将片段保存在内存中。对 destroyItem() 的调用会破坏片段的关联视图,但不应破坏片段本身(我在这里可能有点不对,但你明白了)。

所以由你(我)决定将片段保存在内存中,不要destroyItem()上使它们无效。具体来说,我必须删除这两行:

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        //removed: this.mFragments.remove(position);
        //removed: this.mFragmentTitles.remove(position);
    }

这样getCount()一直正确返回3,当你回到第1页时,adapter可以通过getItem().

获取它的fragment

编辑

处理了一天之后,我可以说,乍一看,FragmentPagerAdapter 在内存中不保存片段对我来说毫无意义。

据记载,它应该只用于一些静态片段。 ViewPager,默认情况下,保留当前项目,前一项和后一项,但您可以通过 viewPager.setOffscreenPageLimit().

调整此设置

如果你在 destroyItem() 期间摧毁它们,事情就会变得糟糕。通过getItem()中的一些逻辑重新实例化它们会很容易,但是保存它们的实例状态是相当困难的。例如,onSaveInstanceState(Bundle outstate) 不会在 destroyItem() 之后调用。

如果你的碎片很多并且你想接受一个更多的碎片被摧毁的可能性,你只需切换到 FragmentStatePagerAdapter,但这是另一回事:它会自动调用 onSaveInstanceState 并让你保留你需要保留的。

使用FragmentPagerAdapter,因此放弃FragmentStatePagerAdapter的状态保存功能,如果不保留就没有意义。我的意思是,要么保留实例,要么保存它们的状态(如评论中所建议的那样)。不过,对于后者,我会选择 FragmentStatePagerAdapter,这很容易。

(注意:我不是在谈论在 activity 被销毁时保留实例,而是在 ViewPager 的页面经过 destroyItem 和相关片段时经过 onDestroyView()).