android开发分享详解Android ViewPager2中的缓存和复用机制

目录1. 前言2. 回顾recyclerview缓存机制3. offscreenpagelimit原理4. fragmentstateadapter原理以及缓存机制4.1 简单使用4.2 原理5. 案

目录
  • 1. 前言
  • 2. 回顾recyclerview缓存机制
  • 3. offscreenpagelimit原理
  • 4. fragmentstateadapter原理以及缓存机制
    • 4.1 简单使用
    • 4.2 原理
  • 5. 案例讲解回收机制
    • 5.1 默认情况
    • 5.2 offscreenpagelimit=1
  • 总结

    上述就是android开发分享详解Android ViewPager2中的缓存和复用机制的全部内容,如果对大家有所用处且需要了解更多关于Android学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

    1. 前言

    众所周知viewpager2是viewpager的替代版本。它解决了viewpager的一些痛点,包括支持right-to-left布局,支持垂直方向滑动,支持可修改的fragment集合等。viewpager2内部是使用recyclerview来实现的。

    所以它继承了recyclerview的优势,包含但不限于以下:

    1. 支持横向和垂直方向布局
    2. 支持嵌套滑动
    3. 支持itemprefetch(预加载)功能
    4. 支持三级缓存

    viewpager2相对于recyclerview,它又扩展出了以下功能

    1. 支持屏蔽用户触摸功能setuserinputenabled
    2. 支持模拟拖拽功能fakedragby
    3. 支持离屏显示功能setoffscreenpagelimit
    4. 支持显示fragment的适配器fragmentstateadapter

    如果熟悉recyclerview,那么上手viewpager2将会非常简单。可以简单把viewpager2想象成每个itemview都是全屏的recyclerview。android开发分享详解Android ViewPager2中的缓存和复用机制将重点讲解viewpager2的离屏显示功能和基于fragmentstateadapter的缓存机制。

    2. 回顾recyclerview缓存机制

    详解Android ViewPager2中的缓存和复用机制

    本章节,简单回顾下recyclerview缓存机制。recyclerview有三级缓存,简单起见,这里只介绍mviewcaches和mrecyclerpool两种缓存池。更多关于recyclerview的缓存原理,请移步公众号相关文章。

    1. mviewcaches:该缓存离ui更近,效率更高,它的特点是只要position能对应上,就可以直接复用viewholder,无需重新绑定,该缓存池是用队列实现的,先进先出,默认大小为2,如果recyclerview开启了预抓取功能,则缓存池大小为2+预抓取个数,默认预抓取个数为1。所以默认开启预抓取缓存池大小为3。
    2. mrecyclerpool:该缓存池离ui最远,效率比mviewcaches低,回收到该缓存池的viewholder会将数据解绑,当复用该viewholder时,需要重新绑定数据。它的数据结构是类似hashmap。key为itemtype,value是数组,value存储viewholder,数组默认大小为5,最多每种itemtype的viewholder可以存储5个。

    3. offscreenpagelimit原理

      //androidx.viewpager2:viewpager2:1.0.0@aar  //viewpager2.java  public void setoffscreenpagelimit(@offscreenpagelimit int limit) {      if (limit < 1 && limit != offscreen_page_limit_default) {          throw new illegalargumentexception(                  "offscreen page limit must be offscreen_page_limit_default or a number > 0");      }      moffscreenpagelimit = limit;      mrecyclerview.requestlayout();    }  

    调用setoffscreenpagelimit方法就可以为viewpager2设置离屏显示的个数,默认值为-1。如果设置不当,会抛异常。我们看到该方法,只是给moffscreenpagelimit赋值。为什么就能实现离屏显示功能呢?如下代码

      //androidx.viewpager2:viewpager2:1.0.0@aar  //viewpager2$linearlayoutmanagerimpl  @override  protected void calculateextralayoutspace(@nonnull recyclerview.state state,          @nonnull int[] extralayoutspace) {      int pagelimit = getoffscreenpagelimit();      if (pagelimit == offscreen_page_limit_default) {          super.calculateextralayoutspace(state, extralayoutspace);          return;      }      final int offscreenspace = getpagesize() * pagelimit;      extralayoutspace[0] = offscreenspace;      extralayoutspace[1] = offscreenspace;  }  

    以水平滑动viewpager2为例:getpagesize()表示viewpager2的宽度,离屏的空间大小为getpagesize() * pagelimit。extralayoutspace[0]表示左边的大小,extralayoutspace[1]表示右边的大小。

    详解Android ViewPager2中的缓存和复用机制

    详解Android ViewPager2中的缓存和复用机制

    假设设置offscreenpagelimit为1,简单讲,android系统会默认把画布宽度增加到3倍。左右两边各有一个离屏viewpager2的宽度。

    4. fragmentstateadapter原理以及缓存机制

    4.1 简单使用

    fragmentstateadapter继承自recyclerview.adapter。它有一个抽象方法,createfragment()。它能将fragment与viewpager2完美结合。

      public abstract class fragmentstateadapter extends          recyclerview.adapter<fragmentviewholder> implements statefuladapter {      public abstract fragment createfragment(int position);  }  

    使用fragmentstateadapter非常简单,demo如下

      class viewpager2withfragmentsactivity : appcompatactivity() {      private lateinit var mviewpager2: viewpager2      override fun oncreate(savedinstancestate: bundle?) {          super.oncreate(savedinstancestate)          setcontentview(r.layout.activity_recycler_view_view_pager2)          mviewpager2 = findviewbyid(r.id.viewpager2)          (mviewpager2.getchildat(0) as recyclerview).layoutmanager?.apply {  //            isitemprefetchenabled = false          }          mviewpager2.orientation = viewpager2.orientation_vertical          mviewpager2.adapter = myadapter(this)  //        mviewpager2.offscreenpagelimit = 1      }        inner class myadapter(fragmentactivity: fragmentactivity) :          fragmentstateadapter(fragmentactivity) {          override fun getitemcount(): int {              return 100          }            override fun createfragment(position: int): fragment {              return myfragment("item $position")          }        }        class myfragment(val text: string) : fragment() {          init {              println("myfragment $text")          }          override fun oncreateview(              inflater: layoutinflater,              container: viewgroup?,              savedinstancestate: bundle?          ): view? {              var view = layoutinflater.inflate(r.layout.view_item_view_pager_snap, container)              view.findviewbyid<textview>(r.id.text_view).text = text              return view;          }      }  }    

    4.2 原理

    首先fragmentstateadapter对应的viewholder定义如下,它只是返回一个简单的带有id的framelayout。由此可以看出,fragmentstateadapter并不复用fragment,它仅仅是复用framelayout而已。

      public final class fragmentviewholder extends viewholder {      private fragmentviewholder(@nonnull framelayout container) {          super(container);      }        @nonnull static fragmentviewholder create(@nonnull viewgroup parent) {          framelayout container = new framelayout(parent.getcontext());          container.setlayoutparams(                  new viewgroup.layoutparams(viewgroup.layoutparams.match_parent,                          viewgroup.layoutparams.match_parent));          container.setid(viewcompat.generateviewid());          container.setsaveenabled(false);          return new fragmentviewholder(container);      }        @nonnull framelayout getcontainer() {          return (framelayout) itemview;      }  }    

    然后介绍fragmentstateadapter中两个非常重要的数据结构:

      final longsparsearray<fragment> mfragments = new longsparsearray<>();    private final longsparsearray<integer> mitemidtoviewholder = new longsparsearray<>();    

    mfragments:是position与fragment的映射表。随着position的增长,fragment是会不断的新建出来的。 fragment可以被缓存起来,当它被回收后无法重复使用。

    fragment什么时候会被回收掉呢?

    详解Android ViewPager2中的缓存和复用机制

    mitemidtoviewholder:是position与viewholder的id的映射表。由于viewholder是recyclerview缓存机制的载体。所以随着position的增长,viewholder并不会像fragment那样不断的新建出来,而是会充分利用recyclerview的复用机制。所以如下图,position 4处打上了一个大大的问号,具体的值是不确定的,它由缓存的大小以及离屏个数共同决定的。

    详解Android ViewPager2中的缓存和复用机制

    接下来我们讲解onviewrecycled()。当viewholder从mviewcaches缓存中移出到mrecyclerpool缓存中时会调用该方法

      @override  public final void onviewrecycled(@nonnull fragmentviewholder holder) {      final int viewholderid = holder.getcontainer().getid();      final long bounditemid = itemforviewholder(viewholderid); // item currently bound to the vh      if (bounditemid != null) {          removefragment(bounditemid);          mitemidtoviewholder.remove(bounditemid);      }  }  

    该方法的作用是,当viewholder回收到recyclerpool中时,将viewholder相关的信息从上面两张表中移除。

    举例 当viewholder1发生回收时,position 0对应的信息从两张表中删除

    详解Android ViewPager2中的缓存和复用机制

    详解Android ViewPager2中的缓存和复用机制

    最后讲解onbindviewholder方法

      @override  public final void onbindviewholder(final @nonnull fragmentviewholder holder, int position) {      final long itemid = holder.getitemid();      final int viewholderid = holder.getcontainer().getid();      final long bounditemid = itemforviewholder(viewholderid); // item currently bound to the vh      if (bounditemid != null && bounditemid != itemid) {          removefragment(bounditemid);          mitemidtoviewholder.remove(bounditemid);      }        mitemidtoviewholder.put(itemid, viewholderid); // this might overwrite an existing entry      ensurefragment(position);        /** special case when {@link recyclerview} decides to keep the {@link container}       * attached to the window, but not to the view hierarchy (i.e. parent is null) */      final framelayout container = holder.getcontainer();      if (viewcompat.isattachedtowindow(container)) {          if (container.getparent() != null) {              throw new illegalstateexception("design assumption violated.");          }          container.addonlayoutchangelistener(new view.onlayoutchangelistener() {              @override              public void onlayoutchange(view v, int left, int top, int right, int bottom,                      int oldleft, int oldtop, int oldright, int oldbottom) {                  if (container.getparent() != null) {                      container.removeonlayoutchangelistener(this);                      placefragmentinviewholder(holder);                  }              }          });      }        gcfragments();  }    

    该方法可以分成3个部分:

    1. 检查该复用的viewholder在两张表中是否还有残留的数据,如果有,将它从两张表中移除掉。
    2. 新建fragment,并将viewholder与fragment和position的信息注册到两张表中
    3. 在合适的时机把fragment展示在viewpager2上。

    大概的脉络就是这样,为了避免文章冗余,其它的细支且也蛮重要的方法就没有列出来

    5. 案例讲解回收机制

    5.1 默认情况

    默认情况:offscreenpagelimit = -1,开启预抓取功能

    因为开启了预抓取,所以mviewcaches大小为3。

    详解Android ViewPager2中的缓存和复用机制

    1. 刚开始进入viewpager2,没有触发touch事件,不会触发预抓取,所以只有fragment1
    2. 滑动到fragment2,会触发fragment3预抓取,由于offscreenpagelimit = -1,所以只有fragment2会展示在viewpager2上,1和3进入mviewcaches缓存中
    3. 滑动到fragment3。1、2、4进入mviewcaches缓存中
    4. 滑动到fragment4。2、3、5进入mviewcaches缓存中,由于缓存数量为3,所以1被挤出到mrecyclerpool缓存中,同时把fragment1从mfragments中移除掉
    5. 滑动到fragment5。fragment6会复用fragment1对应的viewholder。3、4、6进入mviewcaches缓存中,2被挤出到mrecyclerpool缓存中

    5.2 offscreenpagelimit=1

    详解Android ViewPager2中的缓存和复用机制

    offscreenpagelimit=1,所以viewpager2一下子能展示3屏fragment,左右各显示一屏

    1. fragment1左边没有数据,所以屏幕只有1和2
    2. 滑动到fragment2,1、2、3显示在屏幕上(1和3肉眼不可见,下同),同时预抓取4放入mviewcaches
    3. 滑动到fragment3,2、3、4显示在屏幕上,1和5放入mviewcaches
    4. 滑动到fragment4,3、4、5显示在屏幕上,1、2、6放入mviewcaches
    5. 滑动到fragment5,4、5、6显示在屏幕上,2、3、7放入mviewcaches,1被回收到mrecyclerpool缓存中。fragment1同时从mfragments中删除掉

    总结

    到此这篇关于android viewpager2中缓存和复用机制的文章就介绍到这了,更多相关viewpager2缓存和复用机制内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!

    本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

    ctvol管理联系方式QQ:251552304

    本文章地址:https://www.ctvol.com/addevelopment/937624.html

    (0)
    上一篇 2021年11月12日
    下一篇 2021年11月12日

    精彩推荐