FragmenPagerAdapter与FragmentStatePagerAdapter区别

387 阅读5分钟

1.我开发的时候遇到的问题:

(1)ViewPager中添加多个个Fragment,支持删除,增加,移动;如下图

需求是当我在删除Tab的时候就完全删除,移动时已经创建的Fragment不重新创建,只是移动而已。 代码如下:

    public class BasePagerAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> mFragments;
    private List<String> mTitles;

    public BasePagerAdapter(FragmentManager fm, List<Fragment> fragments, List<String> mTitles) {
        super(fm);
        this.mFragments = fragments;
        this.mTitles = mTitles;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        if (mFragments != null && mTitles != null) {
            return mFragments.size();
        }
        return 0;
    }

    public List<String> getTitles() {
        return mTitles;
    }

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

    /**
     * 重新构建
     *
     * @param mFragments
     * @param mTitles
     */
    public void recreatItem(List<Fragment> mFragments, List<String> mTitles) {
        this.mTitles = mTitles;
        this.mFragments = mFragments;
        notifyDataSetChanged();
    }

但是这样写出来之后,在移动的时候,只是Tab会移动,然而下面的Fragment却不随着移动。后来在后面查了很多资料后说需要重写方法:

 @Override
    public int getItemPosition(@NonNull Object object) {
        return POSITION_NONE;
    }

问题虽然解决了,但是我还是没明白为什么?我们还是为着学习的精神来看看到底怎么回事。

2.FragmenPagerAdapter与FragmentStatePagerAdapter的区别

PagerAdapter是ViewPager的支持者ViewPager将调用他来显示所需要的页面,而PagerAdapter在数据变化也会通知到ViewPager。这个类也就是FragmentPagerAdapter和FragmentStatePagerAdapter的基类,这两个类都至少要实现这么几个方法:instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject();

**getItemPosition()**

    该函数用以返回给定对象的位置,给定对象是由 instantiateItem() 的返回值。
    在 ViewPager.dataSetChanged() 中将对该函数的返回值进行判断,以决定是否最终触发 PagerAdapter.instantiateItem() 函数。
     在 PagerAdapter 中的实现是直接传回 POSITION_UNCHANGED。如果该函数不被重载,则会一直返回 POSITION_UNCHANGED,从而导致 ViewPager.dataSetChanged() 被调用时,认为不必触发 PagerAdapter.instantiateItem()。很多人因为没有重载该函数,而导致调用
    PagerAdapter.notifyDataSetChanged() 后,什么都没有发生。

**instantiateItem()**

    在每次 ViewPager 需要一个用以显示的 Object 的时候,该函数都会被 ViewPager.addNewItem() 调用。

**notifyDataSetChanged()**

    在数据集发生变化的时候,一般 Activity 会调用 PagerAdapter.notifyDataSetChanged(),以通知 PagerAdapter,而 PagerAdapter 则会通知在自己这里注册过的所有 DataSetObserver。其中之一就是在 ViewPager.setAdapter() 中注册过的 PageObserver。PageObserver 则进而调用 ViewPager.dataSetChanged(),从而导致 ViewPager 开始触发更新其内含 View 的操作。

FragmentPagerAdapter

集成自PagerAdapter,相比于PagerAdapter而言更加的专注于ViewPager里面的都是Fragment的这种情况,如文档所说,该类的每生成一个
Fragment都会保存在内存之中,因此相对来说使用与页面较少,静态页面等情况比较适宜;如果说页面数量比较大、数据动态性比较大、占用
内存比较多,那么使用FragmentStatePagerAdapter更加的适合,除了几个必须的方法外还有几个方法:

**getItem()**

    该类中新增的一个虚函数。函数的目的为生成新的 Fragment 对象。重载该函数时需要注意这一点。在需要时,该函数将被 instantiateItem() 所调用。
    如果需要向 Fragment 对象传递相对静态的数据时,我们一般通过 Fragment.setArguments() 来进行,这部分代码应当放到 getItem()。它们只会在新生成 Fragment 对象时执行一遍。
    如果需要在生成 Fragment 对象后,将数据集里面一些动态的数据传递给该 Fragment,那么,这部分代码不适合放到 getItem() 中。因为当数据集发生变化时,往往对应的 Fragment 已经生成,如果传递数据部分代码放到了 getItem() 中,这部分代码将不会被调用。这也是为什么很多人发现调用 PagerAdapter.notifyDataSetChanged() 后,getItem() 没有被调用的一个原因。

**instantiateItem()**

    函数中判断一下要生成的 Fragment 是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach();如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。
    FragmentPagerAdapter 会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该Fragment 时,都会从 FragmentManager 读取,而不会再次调用 getItem() 方法。
    如果需要在生成 Fragment 对象后,将数据集中的一些数据传递给该 Fragment,这部分代码应该放到这个函数的重载里。在我们继承的子类中,重载该函数,并调用 FragmentPagerAdapter.instantiateItem() 取得该函数返回 Fragment 对象,然后,我们该 Fragment     对象中对应的方法,将数据传递过去,然后返回该对象。
    否则,如果将这部分传递数据的代码放到 getItem()中,在 PagerAdapter.notifyDataSetChanged() 后,这部分数据设置代码将不会被调用。

**destroyItem()**

    该函数被调用后,会对 Fragment 进行 FragmentTransaction.detach()。这里不是 remove(),只是 detach(),因此 Fragment 还在 FragmentManager 管理中,Fragment 所占用的资源不会被释放。

FragmentStatePagerAdapter

FragmentStatePagerAdapter集成只PageAdapter,和FragmentPageAdapter不同之处就是说:该PagerAdapter只保留当前页
面,当页面离开视线后就会被消除,释放资源;然而在需要的时候又会重新创建新的页面(此页面要收到setOffscreenPageLimit()
方法的限制也就是说当setOffscreenPageLimit(3)是前后3个Fragment都会被保留,其他的都会被销毁)。

getItem()

一个该类中新增的虚函数。
函数的目的为生成新的 Fragment 对象。
Fragment.setArguments() 这种只会在新建 Fragment 时执行一次的参数传递代码,可以放在这里。
由于 FragmentStatePagerAdapter.instantiateItem() 在大多数情况下,都将调用 getItem() 来生成新的对象,因此如果在该函数中放置与数据集相关的 setter 代码,基本上都可以在 instantiateItem() 被调用时执行,但这和设计意图不符。毕竟还有部分可能是不会调用 getItem() 的。因此这部分代码应该放到 instantiateItem() 中。

instantiateItem()

除非碰到 FragmentManager 刚好从 SavedState 中恢复了对应的 Fragment 的情况外,该函数将会调用 getItem() 函数,生成新的 Fragment 对象。新的对象将被 FragmentTransaction.add()。
FragmentStatePagerAdapter 就是通过这种方式,每次都创建一个新的 Fragment,而在不用后就立刻释放其资源,来达到节省内存占用的目的的。

destroyItem()

将 Fragment 移除,即调用 FragmentTransaction.remove(),并释放其资源。