如何高效的使用ViewPager,以及FragmentPagerAdapter与FragmentStatePagerAdapter的区别

ViewPager介绍

Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows.

ViewPager is most often used in conjunction with Fragment, which is a convenient way to supply and manage the lifecycle of each page. There are standard adapters implemented for using fragments with the ViewPager, which cover the most common use cases. These are FragmentPagerAdapter and FragmentStatePagerAdapter; each of these classes have simple code showing how to build a full user interface with them.

通过官网的介绍,我们可以看出,Viewpager可以通过PagerAdapter展示数据,同时ViewPager更常用于和Fragment结合使用,这样可以更好的管理页面的生命周期。有两种标准的适配器(FragmentPagerAdapter和FragmentStatePagerAdapter)来使fragments和ViewPager更好的结合,并且它们也满足大多数的情况了。

PagerAdapter

Base class providing the adapter to populate pages inside of a ViewPager.
You will most likely want to use a more specific implementation of this, such as FragmentPagerAdapter or FragmentStatePagerAdapter
.
PagerAdapter是ViewPager适配器的基类。也许你更想要一个具体的实现,比如FragmentPagerAdapter和FragmentStatePagerAdapter

一般我们在只用简单View来适配ViewPager的时候会选择使用PagerAdapter,比如轮播图。
When you implement a PagerAdapter, you must override the following methods at minimum:

  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object);
  • getCount();
  • isViewFromObject(View, Object);

关于PagerAdapter的使用可以参考本文

FragmentPagerAdapter 和 FragmentStatePagerAdapter的区别

在使用Fragment来适配ViewPager的时候,我们可以选择FragmentPagerAdapter 或者FragmentStatePagerAdapter作为适配器。但是这两者又有什么区别呢? 我们可以通过官网对这两者的介绍来做一个大体的了解,然后通过具体的实践来进行更深入的使用。

FragmentPagerAdapter

Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter

FragmentPagerAdapter继承自 PagerAdapter。相比通用的 PagerAdapter,该类更专注于每一页均为 Fragment 的情况。如文档所述,该类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter

FragmentStatePagerAdapter

Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment's state.
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

FragmentStatePagerAdapter和前面的 FragmentPagerAdapter 一样,是继承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一样的是,正如其类名中的** 'State'** 所表明的含义一样,该 PagerAdapter 的实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面(就像 ListView 的实现一样)。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。

通过观察Fragment的生命周期来看其中的不同

在观察ViewPager中Fragment的生命周期之前,我们先来看一个ViewPager的方法:

/**
 * Set the number of pages that should be retained to either side of the
 * current page in the view hierarchy in an idle state. Pages beyond this
 * limit will be recreated from the adapter when needed.
*/
viewPager.setOffscreenPageLimit(int limit);

该方法是设置左右被保留的page的数量的,也就是说当前被保留的page数量为 2*limit + 1。至于那些没有被保留的,又处于什么状态了呢?这就需要观察Fragment的生命周期了。

FragmentPagerAdapter

此处我们设置viewPager.setOffscreenPageLimit(1);

FragmentPagerAdapter初始化.png

通过Log打印我们可以看出,在刚进入ViewPager的时候,默认的应该是第0个page,
此时第0个page的生命周期是: onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume。同时setUserVisibleHint:true
此时第1个page的生命周期是:onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume同时setUserVisibleHint:false
可以看出,在初始化的过程,两个Fragment的生命周期是一致,而且第1个page此时虽然不可以见,但是依然调用了onStart,onResume。我们都知道在Activity的生命周期中,只有当Acitivty是可见的时候才会调用onStart,只有在可以交互的时候才会调用onResume。

此时我们向右滑动,让第1个page显示在界面上。

currentPage==1.png

此时第0个page的生命周期是没有发生变化的,因为我们设置了setOffscreenPageLimit(1);只是javasetUserVisibleHint:false
第1个page有了一点变化:setUserVisibleHint:true
此时第2个page的生命周期有了变化:onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume

此时我们向右滑动,让第2个page显示在界面上。

currentPage==2

此时第0个page的生命周期变化为:onPause -> onStop -> onDestroyView
第1个page生命周期没有变化,只是setUserVisibleHint:false
第2个page生命周期没有发生变化,只是setUserVisibleHint:true
第三个生命周期onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume同时javasetUserVisibleHint:false

可以看出,此时第0个page已经执行了onDestroyView了,但是它却没有走onDestroy()onDetach() 说明该page只是将View进行了回收,而真正的fragment是还没有被回收的

此时我们向左滑动,让第1个page显示在界面上。

currentPage == 1

可以看出,page0只是走了onCreateView -> onViewCreated ... 再次证明fragment没有被真正的回收

FragmentStatePagerAdapter

同样我们也设置viewPager.setOffscreenPageLimit(1);

FragmentStatePagerAdapter -- fragment初始化.png

此时我们向右滑动,让第1个page显示在界面上。

currentPage==1.png

此时我们向右滑动,让第2个page显示在界面上

currentPage==2.png

在这个时候,发现了与FragmentPagerAdapter不同的生命周期了。第0个page它执行了onDestroy -> onDetach。说明fragment是被真正的回收掉了

此时我们向右滑动,让第1个page显示在界面上

currentPage==1

为什么两个适配器回收方式不同

通过之前对两个适配器的了解,我们也知道了FragmentPagerAdapter每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;FragmentStatePagerAdapter将会对limit外的page进行回收。造成这两者区别的原因就是因为

  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object);
    通过观察两个适配器的源码,可以发现:

在FragmentPagerAdapter中

FragmentPagerAdapter

函数中判断了一下要生成的framgment是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach();如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。
FragmentPagerAdapter 会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该 Fragment 时,都会从 FragmentManager 读取,而不会再次调用 getItem() 方法

destroyItem

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

在FragmentStatePagerAdapter中,

instantiateItem
destroyItem

FragmentStatePagerAdapter 就是通过这种方式,每次都创建一个新的 Fragment,而在不用后就立刻释放其资源,来达到节省内存占用的目的的。FragmentStatePagerAdapter在销毁Fragment时会调用onSaveInstanceState方法保存一些数据信息,然后下一次创建Fragment时会将这些数据读取出来。

总结

FragmentPagerAdapter每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;FragmentStatePagerAdapter将会对limit外的page进行回收。FragmentStatePagerAdapter在销毁Fragment时会调用onSaveInstanceState方法保存一些数据信息,然后下一次创建Fragment时会将这些数据读取出来。
我们可以通过观察framgment的生命周期来进行判断。
造成这样的原因是因为:instantiateItem(ViewGroup, int)destroyItem(ViewGroup, int, Object);的实现方式不一样

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容