• ListView原理


    表明转载自http://blog.csdn.net/iispring/article/details/50967445



    在自己定义Adapter时,我们经常会重写Adapter的getView方法,该方法的签名例如以下所看到的:

    <code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">abstract</span> View <span class="hljs-title" style="box-sizing: border-box;">getView</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position, View convertView, ViewGroup parent) </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

    此处会传入一个convertView变量。它的值有可能是null。也有可能不是null,假设不为null,我们就能够复用该convertView。对convertView里面的一些控件赋值后能够将convertView作为getView的返回值返回,这么做的目的是降低LayoutInflater.inflate()的调用次数。从而提升了性能(LayoutInflater.inflate()比較消耗性能)。

    本文将介绍ListView中的RecycleBin机制,让大家对ListView中的优化机制有个概括的了解。同一时候也说明convertView的来龙去脉。

    首先,我们知道,Adapter是数据源,AdapterView是展示数据源的UI控件。Adapter是给AdapterView使用的。通过调用AdapterView的setAdapter方法就能够让一个AdapterView绑定Adapter对象,从而AdapterView会将Adapter中的数据展示出来。

    AdapterView的子类有AbsListView和AbsSpinner等,当中AbsListView的子类又有ListView、GridView等。所以ListView继承自AdapterView。

    假设Adapter中有10000条数据。将这个Adapter对象赋给ListView。假设ListView创建10000个子View,那么App肯定崩溃了,由于Android没有能力同一时候绘制这么多的子View。并且,即便能同一时候绘制这10000个子View也没什么意义,由于手机的屏幕大小是有限的,有可能ListView的高度仅仅能最多显示10个子View。

    基于此,Android在设计ListView这个类的时候。引入了RecycleBin机制—–对子View进行回收利用,RecycleBin直译过来就是回收站的意思。


    RecycleBin基本原理

    以下先简要说一下RecycleBin中的工作原理。后面会结合源代码具体说明。

    在某一时刻,我们看到ListView中有很多View呈如今UI上,这些View对我们来说是可见的,这些可见的View能够称作OnScreen的View,即在屏幕中能看到的View,也能够叫做ActiveView。由于它们是在UI上可操作的。

    当触摸ListView并向上滑动时,ListView上部的一些OnScreen的View位置上移。并移除了ListView的屏幕范围。此时这些OnScreen的View就变得不可见了,不可见的View叫做OffScreen的View。即这些View已经不在屏幕可见范围内了,也能够叫做ScrapView。Scrap表示废弃的意思。ScrapView的意思是这些OffScreen的View不再处于能够交互的Active状态了。ListView会把那些ScrapView(即OffScreen的View)删除,这样就不用绘制这些本来就不可见的View了,同一时候,ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把临时没用的资源放到回收站一样。

    当ListView的底部须要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView參数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中运行LayoutInflater.inflate()方法了。

    RecycleBin中有两个重要的View数组,各自是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,仅仅只是mActiveViews中存储的是OnScreen的View。这些View非常有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。

    上面对mActiveViews和mScrapViews的说明比較笼统,事实上在细节上还牵扯到Adapter的数据源发生变化的情况,详细细节后面会解说。


    源代码解析

    AdapterView是继承自ViewGroup的,ViewGroup中有addView方法能够向ViewGroup中加入子View。可是AdapterView重写了addView方法,例如以下所看到的:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">addView</span>(View child) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throw</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> UnsupportedOperationException(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"addView(View) is not supported in AdapterView"</span>);
        }
    
    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">addView</span>(View child, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> index) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throw</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> UnsupportedOperationException(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"addView(View, int) is not supported in AdapterView"</span>);
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

    在AdapterView的addView方法中会抛出异常,也就是说AdapterView禁用了addView方法。

    在详细解说之前。我们还是先花一点时间简要说一下View的每一帧的显示流程,当然,ListView也肯定遵循此流程。一个View要想在界面上呈现出来。须要经过三个阶段:measure->layout->draw。

    View是一帧一帧绘制的,每一帧绘制都经历了measure->layout->draw这三个阶段,绘制完一帧之后,假设UI须要更新,比方用户滚动了ListView,那么又会绘制下一帧,再次经历measure->layout->draw方法,假设对此不了解。能够參见还有一篇博文《 Android中View的量算、布局及画图机制》

    我们上面说了,AdapterView把addView方法给禁用了。那么ListView怎么向当中加入child呢?奥秘就在layout中,在布局的时候,ListView会运行layoutChildren方法。该方法是ListView对View进行加入以及回收的关键方法,RecycleBin的非常多方法都在layoutChildren方法中被调用。在layoutChildren方法中实现对子View的增删。经过layoutChildren方法之后,ListView中全部的子View都是在屏幕中可见的,也就是说layoutChildren方法为接下来的帧绘制把子View准备完好了。这就保证了在后面的draw方法的运行过程中可以正确绘制ListView。

    ListView的layoutChildren方法代码比較多。我们仅仅研究和View增删相关的关键代码,主要分下面三个阶段:

    1. ListView的children->RecycleBin
    2. ListView清空children
    3. RecycleBin->ListView的children

    在layout这种方法刚刚開始运行的时候,ListView中的children事实上还是上一帧中须要绘制的子View的集合,在layout这种方法运行完毕的时候,ListView中的children就变成了当前帧立即要进行绘制的子View的集合。

    以下对以上这三个阶段分别说明。

    1. ListView的children->RecycleBin 
      该阶段的关键代码例如以下所看到的:

      <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//mFirstPosition是ListView的成员变量,存储着第一个显示的child所相应的adapter的position</span>
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> firstPosition = mFirstPosition;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> RecycleBin recycleBin = mRecycler;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (dataChanged) {
                  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//假设数据发生了变化,那么就把ListView的全部子View都放入到RecycleBin的mScrapViews数组中</span>
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < childCount; i++) {
                      <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//addScrapView方法会传入一个View。以及这个View所相应的position</span>
                      recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                  }
              } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//假设数据没发生变化,那么把ListView的全部子View都放入到RecycleBin的mActiveViews数组中</span>
                  recycleBin.fillActiveViews(childCount, firstPosition);
              }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

      再次强调一下,在上面的代码刚開始的时候,ListView的中的children还是上一帧须要绘制的子View。

      • 假设Adapter调用了notifyDataSetChanged方法。那么AdapterView就会知道Adapter的数据源发生了变化,此时dataChanged变量就为true,这样的情况下,ListView会觉得children中的View都是不合格的了。这时候会用getChildAt方法遍历children中全部的child。并把这些child通过RecycleBin的addScrapView方法将其放入RecycleBin的mScrapViews数组中。

      • 假设adapter的数据没有发生变化,那么会调用RecycleBin的fillActiveViews方法将全部的children都放入到RecycleBin的mActiveViews数组中。

      经过上面的操作之后,ListView全部的子View都放入到了RecycleBin中。这就实现了ListView的children->RecycleBin的迁移过程。放到RecycleBin的目的是为了分类缓存ListView中的children,以便在兴许过程中对这些View进行复用。

    2. ListView清空children 
      然后调用ViewGroup的detachAllViewsFromParent方法,该方法将全部的子View从ListView中分离。也就是清空了children。该方法源代码例如以下所看到的:

      <code class="hljs axapta has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> detachAllViewsFromParent() {
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">count</span> = mChildrenCount;
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">count</span> <= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>;
          }
      
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> View[] children = mChildren;
          mChildrenCount = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
      
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">count</span> - <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>; i >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i--) {
              children[i].mParent = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
              children[i] = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
          }
      }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>
    3. RecycleBin->ListView的children

      然后ListView会依据mLayoutMode进行推断,源代码例如以下所看到的:

      <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> (mLayoutMode) {
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_SET_SELECTION:
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (newSel != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                      sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
                  } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                      sel = fillFromMiddle(childrenTop, childrenBottom);
                  }
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_SYNC:
                  sel = fillSpecific(mSyncPosition, mSpecificTop);
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_FORCE_BOTTOM:
                  sel = fillUp(mItemCount - <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, childrenBottom);
                  adjustViewsUpOrDown();
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_FORCE_TOP:
                  mFirstPosition = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
                  sel = fillFromTop(childrenTop);
                  adjustViewsUpOrDown();
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_SPECIFIC:
                  sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> LAYOUT_MOVE_SELECTION:
                  sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">default</span>:
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (childCount == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
                      <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mStackFromBottom) {
                          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position = lookForSelectablePosition(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);
                          setSelectedPositionInt(position);
                          sel = fillFromTop(childrenTop);
                      } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position = lookForSelectablePosition(mItemCount - <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
                          setSelectedPositionInt(position);
                          sel = fillUp(mItemCount - <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, childrenBottom);
                      }
                  } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                      <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mSelectedPosition >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> && mSelectedPosition < mItemCount) {
                          sel = fillSpecific(mSelectedPosition,
                                  oldSel == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> ?

      childrenTop : oldSel.getTop()); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mFirstPosition < mItemCount) { sel = fillSpecific(mFirstPosition, oldFirst == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> ? childrenTop : oldFirst.getTop()); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> { sel = fillSpecific(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, childrenTop); } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li></ul>

      在该switch代码段中,会依据不同情况增删子View,这些方法的代码逻辑大部分终于调用了fillDown、fillUp等方法。 
      fillDown用子View从指定的position自上而下填充ListView,fillUp则是自下而上填充,我们以fillDown方法为例具体说明。

       
      fillDown方法的源代码例如以下所看到的:

      <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> View <span class="hljs-title" style="box-sizing: border-box;">fillDown</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> pos, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> nextTop) {
          View selectedView = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
      
          <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//end表示ListView的高度</span>
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> end = (mBottom - mTop);
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
              end -= mListPadding.bottom;
          }
      
          <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//nextTop < end确保了我们仅仅要将新增的子View可以覆盖ListView的界面就行了</span>
          <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//pos < mItemCount确保了我们新增的子View在Adapter中都有相应的数据源item</span>
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> (nextTop < end && pos < mItemCount) {
              <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// is this the selected item?</span>
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> selected = pos == mSelectedPosition;
              View child = makeAndAddView(pos, nextTop, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>, mListPadding.left, selected);
              <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//将最新child的bottom值作为下一个child的top值,存储在nextTop中</span>
              nextTop = child.getBottom() + mDividerHeight;
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (selected) {
                  selectedView = child;
              }
              <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//position自增</span>
              pos++;
          }
      
          setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>);
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> selectedView;
      }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li></ul>
      • fillDown接收两个參数,pos表示列表中第一个要绘制的item的position,其相应着Adapter中的索引,nextTop表示第一个要绘制的item在ListView中实际的位置, 即该item所相应的子View的顶部到ListView的顶部的像素数。

      • 首先将mBottom - mTop的值作为end,end表示ListView的高度。

      • 然后在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的所有高度。

    经过了上面的while循环之后,ListView对子View的增删就完毕了。即children中存放的就是要在后面画图过程中即将渲染的子View的集合。

    上面while循环的方法体中调用了makeAndAddView方法。通过该方法会获得一个子View。并把该子View加入到ListView的children中。该方法的方法签名例如以下所看到的:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> View <span class="hljs-title" style="box-sizing: border-box;">makeAndAddView</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> y, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> flow, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> childrenLeft,
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> selected)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

    其源代码例如以下所看到的:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> View <span class="hljs-title" style="box-sizing: border-box;">makeAndAddView</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> y, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> flow, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> childrenLeft,
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> selected) {
            View child;
    
    
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mDataChanged) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 假设数据源没发生变化,那么尝试用该position从RecycleBin的mActiveViews中获取可复用的View</span>
                child = mRecycler.getActiveView(position);
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (child != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 假设child 不为空,说明我们找到了一个已经存在的child,这样mActiveViews中存储的View就被直接复用了</span>
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 调用setupChild,对child进行定位</span>
                    setupChild(child, position, y, flow, childrenLeft, selected, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);
    
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> child;
                }
            }
    
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 假设没可以从mActivieViews中直接复用View。那么就要调用obtainView方法获取View,该方法尝试间接复用RecycleBin中的mScrapViews中的View。假设不能间接复用,则创建新的View</span>
            child = obtainView(position, mIsScrap);
    
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 调用setupChild方法。进行定位和量算</span>
            setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>]);
    
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> child;
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li></ul>

    我们重点说一下前两个參数position和y,position表示的是数据源item在Adapter中的索引。y表示要生成的View的top值或bottom值。假设第三个參数flow是true,那么y表示top值,否则表示bottom值。

    • 假设数据源没发生变化,那么尝试用该position从RecycleBin的mActiveViews中获取可复用的View。RecycleBin的getActiveView方法接收一个position參数,能够在RecycleBin的mActiveViews数组中查找有没有相应position的View。假设能找到就能够直接复用该View作为child了。

      举一个样例,假设在某一时刻ListView中显示了10个子View,position依次为从0到9。

      然后我们手指向上滑动,且向上滑动了一个子View的高度,ListView须要绘制下一帧。这时候ListView在layoutChildren方法中把这10个子View都放入到了RecycleBin的mActiveViews数组中了,然后清空了children数组。然后调用fillDown方法,向ListView中依次加入position1到10的子View。在加入position为1的子View的时候,因为在上一帧中position为1的子View已经被放到mActiveViews数组中了。这次直接能够将其从mActiveViews数组中取出来,这样就是直接复用子View,所以说RecycleBin的mActiveViews数组主要是用于直接复用的

      在直接复用了子View后,我们须要调用setupChild方法。该方法会将child加入到ListView的children数组中,并对child进行定位。

    • 假设没可以从mActivieViews中直接复用View。那么就要调用obtainView方法获取View。该方法尝试间接复用RecycleBin中的mScrapViews中的View,假设不能间接复用,则创建新的View。

      在通过obtainView获取了View之后,调用setupChild方法。该方法会将child加入到ListView的children数组中,并对child进行定位和量算。

    以下我们再来看一下obtainView方法,该方法的方法签名例如以下所看到的:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">View obtainView(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> position, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span>[] isScrap)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

    该方法接收position參数,其关键的源代码有下面两行:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> View scrapView = mRecycler.getScrapView(position);
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> View child = mAdapter.getView(position, scrapView, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute;  50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right- 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

    通过调用RecycleBin的getScrapView方法。从mScrapViews数组中获取一个View,该View是用来间接复用的,该View可能为null,也可能不为null。将其作为我们熟悉的convertView传递给Adapter的getView方法,这样我们就能够在AdapterView的getView方法中通过推断convertView是否为空进行间接复用了。

    希望本文对大家理解ListView的RecycleBin机制有所帮助!

  • 相关阅读:
    Avoiding React setState() Pitfalls(译)
    rax学习(十):实现微信消息长列表(LongList)之列表扩展
    rax学习(九):实现微信消息长列表(LongList)之配置透出
    rax学习(八):实现微信消息长列表(LongList)之性能优化
    rax学习(七):实现微信消息长列表(LongList)之性能监控
    rax学习(六):实现微信消息长列表(LongList)之业务埋点
    jQuery获取父 兄 子 节点
    css相关
    两端对齐justify
    js页面刷新的几种方法
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7269041.html
Copyright © 2020-2023  润新知