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机制有所帮助!

原文地址:https://www.cnblogs.com/zsychanpin/p/7269041.html