Android学习笔记之ListView复用机制详解

Android学习笔记之ListView复用机制详解,第1张

概述PS:满打满算,差不多三个月没写博客了...前一阵忙的不可开交...总算是可以抽出时间研究研究其他事情了...

PS:满打满算,差不多三个月没写博客了...前一阵忙的不可开交...总算是可以抽出时间研究研究其他事情了...

1.ListVIEw的复用机制

  ListVIEw是我们经常使用的一个控件,虽然说都会用,但是却并不一定完全清楚ListVIEw的复用机制,虽然在AndroID 5.0版本之后提供了RecycleVIEw去替代ListVIEw和GrIDVIEw,提供了一种插拔式的体验,也就是所谓的模块化。本篇主要针对ListVIEw的复用机制进行探讨,因此就 提RecycleVIEw。昨天看了一下郭霖大神的ListVIEw原理深度解析的一篇博客,因此学习了一段时间,自己也说一下自己的理解。

i.RecycleBin的基本原理

  首先需要说一下RecycleBin的基本原理,这个类也是实现复用的关键类。接着我们需要明确ActiveVIEw的概念,ActivityVIEw其实就是在UI屏幕上可见的视图(onScreenVIEw),也是与用户进行交互的VIEw,那么这些VIEw会通过RecycleBin直接存储到mActivityVIEw数组当中,以便为了直接复用,那么当我们滑动ListVIEw的时候,有些VIEw被滑动到屏幕之外(offScreen) VIEw,那么这些VIEw就成为了ScrapVIEw,也就是废弃的VIEw,已经无法与用户进行交互了,这样在UI视图改变的时候就没有绘制这些无用视图的必要了。他将会被RecycleBin存储到mScrapVIEw数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。当新的VIEw需要显示的时候,先判断mActivityVIEw中是否存在,如果存在那么我们就可以从mActivityVIEw数组当中直接取出复用,也就是直接复用,否则的话从mScrapVIEw数组当中进行判断,如果存在,那么二次复用当前的视图,如果不存在,那么就需要inflate VIEw了。

这是一个总体的流程图,复用机制就是这样的。那么我们先来理解一下ListVIEw第一次加载的时候都做了哪些工作,首先会执行onLayout方法。。

/** * Subclasses should NOT overrIDe this method but {@link #layoutChildren()} * instead. */@OverrIDeprotected voID onLayout(boolean changed,int l,int t,int r,int b) {  super.onLayout(changed,l,t,r,b);  mInLayout = true;  if (changed) {    int childCount = getChildCount();    for (int i = 0; i < childCount; i++) {      getChildAt(i).forceLayout();    }    mRecycler.markChildrenDirty();  }  layoutChildren();  mInLayout = false;}

这里可以看到onLayout方法会调用layoutChildren()方法,也就是对item进行布局的流程,layoutChildren()方法就不进行粘贴了,代码量过长我们只需要知道,这是对ListVIEw中的子VIEw进行布局的一个方式就可以了,在我们第一次加载ListVIEw的时候,RecycleBin中的数组都没有任何的数据,因此第一次加载都需要inflate VIEw,也就是创建新的VIEw。并且第一次加载的时候是自顶向下对数据进行加载的,因此在layoutChildren()会执行fillFromtop()方法。fillFromtop()会执行filleDown()方法。

/** * Fills the List from pos down to the end of the List vIEw. * * @param pos The first position to put in the List * * @param nexttop The location where the top of the item associated with pos *    should be drawn * * @return The vIEw that is currently selected,if it happens to be in the *     range that we draw. *  * @param pos:列表中的一个绘制的Item在Adapter数据源中对应的位置 * @param nexttop:表示当前绘制的Item在ListVIEw中的实际位置.. */private VIEw fillDown(int pos,int nexttop) {  VIEw selectedVIEw = null;  /**   * end用来判断Item是否已经将ListVIEw填充满   */  int end = (getBottom() - gettop()) - mListpadding.bottom;  while (nexttop < end && pos < mItemCount) {     /**     * nexttop < end确保了我们只要将新增的子VIEw能够覆盖ListVIEw的界面就可以了     *pos < mItemCount确保了我们新增的子VIEw在Adapter中都有对应的数据源item     */    // is this the selected item?    boolean selected = pos == mSelectedposition;    VIEw child = makeAndAddVIEw(pos,nexttop,true,mListpadding.left,selected);    /**      *将最新child的bottom值作为下一个child的top值,存储在nexttop中      */    nexttop = child.getBottom() + mdivIDerHeight;    if (selected) {      selectedVIEw = child;    }    pos++;  }  return selectedVIEw;}
在while循环中添加子VIEw,我们先不看while循环的具体条件,先看一下循环体。在循环体中,将pos和nexttop传递给makeAndAddVIEw方法,该方法返回一个VIEw作为child,该方法会创建VIEw,并把该VIEw作为child添加到ListVIEw的children数组中。 然后执行nexttop = child.getBottom() + mdivIDerHeight,child的bottom值表示的是该child的底部到ListVIEw顶部的距离,将该child的bottom作为下一个child的top,也就是说nexttop一直保存着下一个child的top值。 最后调用pos++实现position指针下移。现在我们回过头来看一下while循环的条件while (nexttop < end && pos < mItemCount)。 nexttop < end确保了我们只要将新增的子VIEw能够覆盖ListVIEw的界面就可以了,比如ListVIEw的高度最多显示10个子VIEw,我们没必要向ListVIEw中加入11个子VIEw。 pos < mItemCount确保了我们新增的子VIEw在Adapter中都有对应的数据源item,比如ListVIEw的高度最多显示10个子VIEw,但是我们Adapter中一共才有5条数据,这种情况下只能向ListVIEw中加入5个子VIEw,从而不能填充满ListVIEw的全部高度。

这里存在一个关键方法,也就是makeAndAddVIEw()方法,这是ListVIEw将Item显示出来的核心部分,也是这个部分涉及到了ListVIEw的复用

private VIEw makeAndAddVIEw(int position,int y,boolean flow,int childrenleft,boolean selected) {  VIEw child;  //判断数据源是否发生了变化.  if (!mDataChanged) {    // Try to use an exsiting vIEw for this position    //如果mActivityVIEw[]数组中存在可以直接复用的VIEw,那么直接获取,然后重新布局.    child = mRecycler.getActiveVIEw(position);    if (child != null) {      // Found it -- we're using an existing child      // This just needs to be positioned      setupChild(child,position,y,flow,childrenleft,selected,true);      return child;    }  }  // Make a new vIEw for this position,or convert an unused vIEw if possible  /**   *如果mActivityVIEw[]数组中没有可用的VIEw,那么尝试从mScrapVIEw数组中读取.然后重新布局.   *如果可以从mScrapVIEw数组中可以获取到,那么直接返回调用mAdapter.getVIEw(position,scrapVIEw,this);   *如果获取不到那么执行mAdapter.getVIEw(position,null,this)方法.   */  child = obtainVIEw(position,mIsScrap);  // This needs to be positioned and measured  setupChild(child,mIsScrap[0]);  return child;}

这里可以看到如果数据源没有变化的时候,会从mActivityVIEw数组中判断是否存在可以直接复用的VIEw,可能很多读者都不太明白直接复用到底是怎么个过程,举个例子,比如说我们ListVIEw一页可以显示10条数据,那么我们在这个时候滑动一个Item的距离,也就是说把position = 0的Item移除屏幕,将position = 10 的Item移入屏幕,那么position = 1的Item是不是就直接能够从mActivityVIEw数组中拿到呢?这是可以的,我们在第一次加载Item数据的时候,已经将position = 0~9的Item加入到了mActivityVIEw数组当中,那么在第二次加载的时候,由于position = 1 的Item还是ActivityVIEw,那么这里就可以直接从数组中获取,然后重新布局。这里也就表示的是Item的直接复用。

  如果我们在mActivityVIEw数组中获取不到position对应的VIEw,那么就尝试从mScrapVIEw废弃VIEw数组中尝试去获取,还拿刚才的例子来说当position = 0的Item被移除屏幕的时候,首先会Detach让VIEw和视图进行分离,清空children,然后将废弃VIEw添加到mScrapVIEw数组当中,当加载position = 10的Item时,mActivityVIEw数组肯定是没有的,也就无法获取到,同样mScrapVIEw中也是不存在postion = 10与之对应的废弃VIEw,说白了就是mScrapVIEw数组只有mScrapVIEw[0]这一项数据,肯定是没有mScrapVIEw[10]这项数据的,那么我们就会这样想,肯定是从Adapter中的getVIEw方法获取新的数据喽,其实并不是这样,虽然mScrapVIEw中虽然没有与之对应的废弃VIEw,但是会返回最后一个缓存的VIEw传递给convertvIEw。那么也就是将mScrapVIEw[0]对应的VIEw返回。总体的流程就是这样。

  这里我们可以看到,ListVIEw始终只会在getVIEw方法中inflate一页的Item,也就是new VIEw只会执行一页Item的次数。后续的Item通过直接复用和间接复用完成。

 注意一种情况:比如说还是一页的Item,但是position = 0的Item没有完全滑动出UI,position = 10的Item没有完全进入到UI的时候,那么position = 0的Item不会被detach掉,同样不会被加入到废弃VIEw数组,这时mScrapVIEw是空的,没有任何数据,那么position = 10的Item即无法从mActivityVIEw中直接复用VIEw,因为是第一次加载。mActivityVIEw[10]是不存在的,同时mScrapVIEw是空的,因此position = 10的Item只能重新生成VIEw,也就是从getVIEw方法中inflate。这里obtainVIEw方法没有具体贴出,大家可以自己进去看看。obtainVIEw其实就是判断能否从废弃VIEw中获取到VIEw,获取到了则执行:

if (scrapVIEw != null) {   child = mAdapter.getVIEw(position,this);  } 

这里是可以获取到,那么getVIEw会传递scrapVIEw。否则的话:

else {   child = mAdapter.getVIEw(position,this); } 

 获取不到就传递null,这样就会执行我们定义的Adapter中的方法。

@OverrIDepublic VIEw getVIEw(int position,VIEw convertVIEw,VIEwGroup parent) {  if(convertVIEw == null){    convertVIEw = VIEw.inflate(context,R.layout.List_item_layout,null);  }  return convertVIEw;}

至于向上滑动会执行其他的一些方法,也就是自底向上铺满ListVIEw,同样也会直接或者间接复用控件。理解了复用的机制才是关键,因此向上滑基本就不难理解了。补充一点,RecycleBin中还存在一个方法,setVIEwTypeCount()方法。这个是针对Adapter中的getVIEwTypeCount()设定的。针对每一种数据类型,setVIEwTypeCount()会为每种数据类型开启一个单独的RecycleBin回收机制。这里我们只需要知道就可以了。至于在郭神博客中看到ListVIEw会onLayout多次,这是肯定的,由于AndroID VIEw加载机制问题,子控件需要根据父控件的大小要重新测量大小,经过多次测量才能够显示在UI上。这是VIEw测量多次的原因。至于ListVIEw在多次布局的问题我就不进行赘余了,总之无论几次测量,ListVIEw是不会多次执行重复的逻辑的,也就是说数据不会有多份,只会存在一份数据。

 这里也就是ListVIEw复用的基本原理和RecycleBin的回收机制了。代码贴的很少,都是一些关键代码,没必要去一行一行的研究代码,毕竟和大神还差很大的一个档次。我们只需要知道这个执行过程和原理就可以了。

2.VIEwHolder

 最后说一说VIEwHolder这个东西,很多AndroID学习者会把这个东西和ListVIEw的复用机制搞混。这里VIEwHolder也是在复用的时候进行使用,但是和复用机制是没太大关系的。

@OverrIDepublic VIEw getVIEw(int position,VIEwGroup parent) {    final VIEwHolder holder;    ListVIEwItem itemData = items.get(position);    if(convertVIEw == null){      convertVIEw = VIEw.inflate(context,null);      holder = new VIEwHolder();      holder.userimg = (ImageVIEw) convertVIEw.findVIEwByID(R.ID.user_header_img);      holder.username = (TextVIEw) convertVIEw.findVIEwByID(R.ID.user_name);      holder.userComment = (TextVIEw) convertVIEw.findVIEwByID(R.ID.user_coomment);      convertVIEw.setTag(holder);    }else{      holder = (VIEwHolder) convertVIEw.getTag();    }    holder.userimg.setimageResource(itemData.getUserimg());    holder.username.setText(itemData.getUsername());    holder.userComment.setText(itemData.getUserComment());    return convertVIEw;}static class VIEwHolder{    ImageVIEw userimg;    TextVIEw username;    TextVIEw userComment;}

在实现Adapter的时候,我们一般会加上VIEwHolder这个东西,VIEwHolder和复用机制和原理是无关的,他的主要目的是持有Item中控件的引用,从而减少findVIEwByID()的次数,因为findVIEwByID()方法也是会影响效率的,因此在复用的时候他起的作用是这个,减少方法执行次数增加效率。这里做个简单的提醒,别弄混就行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android学习笔记之ListView复用机制详解全部内容,希望文章能够帮你解决Android学习笔记之ListView复用机制详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1146943.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存