浅析listview原理

RecyclerView是Android 5.0系统官方推出的一个代替listView的组件,那么究竟好在哪里呢?我们来仔细分析一下这两个组件。

listView和RecycleView的原理大致相同,如下图


都是在内部维护一个缓存池,回收划出列表的item,添加给将要进入列表的item。只不过ListView内部是两级缓存,分别是mActiveViews和mScrapViews.而RecycleView内部有四级缓存。

listView的实现原理

首先需要介绍一下RecycleBin 这个类。

RecycleBin

RecycleBin中的变量

mRecyclerListener:
一般会在某个item被移除屏幕的时候调用,将这个item回收到scrap heap,并回收item上相关的资源布局

mActiveViews:
用来保存item的一个临时数组,主要是用在listView第二次onLayout()时,给ListView提供item,

mFirstActivePosition
存储在mActiveViews中的第一个View的位置

mViewTypeCount
view类型数量,

mScrapViews和mCurrentScrap
mCurrentScrap是ArrayList<View> 类型
而mScrapViews是ArrayList<View>[]类型

当mViewTypeCount=1,也就是listView中只有一种类型的item时(不包括header和footer),item是存入到mCurrentScrap中的
当mViewTypeCount>1的时候,ScrapViews数组就会有多个值,分别缓存一个类型的item

另外,adapter中getView()方法中的参数的convertView的值就是mScrapViews和mCurrentScrap传过去的。

RecycleBin中的方法

markChildrenDirty():该方法会为回收回来的每一个view调用forceLayout(),作用是在该view被复用的时候重新layout,

public void markChildrenDirty() {
    if (mViewTypeCount == 1) {
        final ArrayList<View> scrap = mCurrentScrap;
        final int scrapCount = scrap.size();
        for (int i = 0; i < scrapCount; i++) {
            scrap.get(i).forceLayout();
        }
    } else {
        final int typeCount = mViewTypeCount;
        for (int i = 0; i < typeCount; i++) {
            final ArrayList<View> scrap = mScrapViews[i];
            final int scrapCount = scrap.size();
            for (int j = 0; j < scrapCount; j++) {
                scrap.get(j).forceLayout();
            }
        }
    }   
}

/**
 * Forces this view to be laid out during the next layout pass.
 * This method does not call requestLayout() or forceLayout()
 * on the parent.
 */
public void forceLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
}

fillActiveViews():接受两个参数,第一个是要存储view的数量,一般会是一个屏幕所见的item数量,第二个参数表示屏幕中第一个可见item的position,这个方法被调用之后,mActiveViews这个数组就会缓存相应数量的item

 void fillActiveViews(int childCount, int firstActivePosition) {  
    if (mActiveViews.length < childCount) {  
        mActiveViews = new View[childCount];  
    }  
    mFirstActivePosition = firstActivePosition;  
    final View[] activeViews = mActiveViews;  
    for (int i = 0; i < childCount; i++) {  
        View child = getChildAt(i);  
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();  
        // Don't put header or footer views into the scrap heap  
        if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
            // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in  
            // active views.  
            // However, we will NOT place them into scrap views.  
            activeViews[i] = child;  
        }  
    }  
}  

getActiveView(): 与fillActiveViews()相对应,获取相应位置的item,但是为什么在获取到对应的View之后,要将mActiveViews[]数组对应位置的数据置为空呢?那是因为mActiveViews[]这个数组实际上只是一个临时数组,方便listView第二次onLayout的时候提高效率,置为空就可以回收内存了。

View getActiveView(int position) {
    int index = position - mFirstActivePosition;
    final View[] activeViews = mActiveViews;
    if (index >=0 && index < activeViews.length) {
        final View match = activeViews[index];
        activeViews[index] = null;
        return match;
    }
    return null;
}

addScrapView():首先判断是否需要回收,header和footer都是不回收的,加到忽略的list里,接着判断mViewTypeCount的数量,即item的类型有几种,mViewTypeCount==1,就加到mCurrentScrap中,>1,就加到mScrapViews[]中,

另mRecyclerListener.onMovedToScrapHeap(scrap);当item被回收的时候,通知监听器回收资源,这也是为什么listView能存放那么多数据而不崩溃的原因,

void addScrapView(View scrap, int position) {
        final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
        if (lp == null) {
            // Can't recycle, but we don't know anything about the view.
            // Ignore it completely.
            return;
        }

        lp.scrappedFromPosition = position;

        // Remove but don't scrap header or footer views, or views that
        // should otherwise not be recycled.
        final int viewType = lp.viewType;
        if (!shouldRecycleViewType(viewType)) {
            // Can't recycle. If it's not a header or footer, which have
            // special handling and should be ignored, then skip the scrap
            // heap and we'll fully detach the view later.
            if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                getSkippedScrap().add(scrap);
            }
            return;
        }

        .....

        // Don't scrap views that have transient state.
        final boolean scrapHasTransientState = scrap.hasTransientState();
        if (scrapHasTransientState) {
          .......
        } else {
            if (mViewTypeCount == 1) {
                mCurrentScrap.add(scrap);
            } else {
                mScrapViews[viewType].add(scrap);
            }

            if (mRecyclerListener != null) {
                mRecyclerListener.onMovedToScrapHeap(scrap);
            }
        }
    }

getScrapView():复用item

      View getScrapView(int position) {
        final int whichScrap = mAdapter.getItemViewType(position);
        if (whichScrap < 0) {
            return null;
        }
        if (mViewTypeCount == 1) {
            return retrieveFromScrap(mCurrentScrap, position);
        } else if (whichScrap < mScrapViews.length) {
            return retrieveFromScrap(mScrapViews[whichScrap], position);
        }
        return null;
    }

retrieveFromScrap():根据position,查找对应的view,从代码来看分三种情况
1.如果view.scrappedFromPosition==position,直接返回view
这里的scrappedFromPosition就是在addScrapView回收时的位置。
2.不符合1中的条件,则会返回mScrapView中的最后一个数据
3.如果mScrapView中没有的话,则返回null,这个时候的view需要getView()来加载

情景案列:
listView中正好有6个item,此时mScrapView为空,这个时候listView像上滑动一点,则需要展示第7个item,mScrapView中没有,只能通过getView()加载,当第7个item完全显示,则第一个item会被回收,mScrapView中便会有一个item的缓存。

这个时候如果再向上滑动,第8个item需要显示,注意,这里的第8个,实际上对于listView来说是第7个,从mScrapView查询对应下标的item,无
果,只能返回mScrapView中最后一个item作为convert。

如果刚才不是向上滑动,而是向下滑动,第1个item需要显示,从mScrapView中查找,正好有对应下标的item,直接返回做convert。

private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
    final int size = scrapViews.size();
    if (size > 0) {
        // See if we still have a view for this position or ID.
        for (int i = 0; i < size; i++) {
            final View view = scrapViews.get(i);
            final AbsListView.LayoutParams params =
                    (AbsListView.LayoutParams) view.getLayoutParams();
            //不重写BaseAdapter的hasStableIds方法,这个if就会是false
            if (mAdapterHasStableIds) {
                final long id = mAdapter.getItemId(position);
                if (id == params.itemId) {
                    return scrapViews.remove(i);
                }
            } else if (params.scrappedFromPosition == position) {
                final View scrap = scrapViews.remove(i);
                clearAccessibilityFromScrap(scrap);
                return scrap;
            }
        }
        final View scrap = scrapViews.remove(size - 1);
        clearAccessibilityFromScrap(scrap);
        return scrap;
    } else {
        return null;
    }
}

进入调用流程

ListView的继承关系

Object->View->...->AbsListView->ListView

由于ListView最终还是继承自view的,所以有着View该有的属性,自然也有onMeasure(),onLayout()和onDraw()

一般情况下,如果View的子视图不为空的话,onMeasure()和onLayout()都会执行两遍。

因为最开始父视图是没有子视图的,这个时候会执行一次onMeasure()和onLayout(),然后当你往父视图里添加子视图的时候,父视图的状态会发生变化,重新执行onMeasure()和onLayout()。

listView初始化时必然要经历的过程

1.ListView.onMeasure():

2.AbsListView.onLayout():一般初始化子View的时候,都是调用父视图的onLayout()

2.1 ListView.layoutChildren()
2.2 ListView.fillFromTop()
2.3 ListView.fillDown()
2.4 ListView.makeAndAddView()
2.5 ListView.setupChild()

3.ListView.onDraw():

onMeasure()和onDraw()对于listView来说,并没有什么特殊的,所以下文着重介绍onLayout()的过程。

ListView的onLayout()方法在父类AbsListView中

onLayout(): 该方法中有个if语句,判断当数据发生变化的时候,强制子视图重绘视图。除此之外,就是一个layoutChildren的方法了,这个方法一看就知道是给子类布局的。具体的实现也在子类listView中

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);

    mInLayout = true;

    final int childCount = getChildCount();
    if (changed) {
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
        mRecycler.markChildrenDirty();
    }

    layoutChildren();
    mInLayout = false;
    .....
}

layoutChildren(): layoutChildren的代码有点多,下面的代码已经把不必要的代码省略,首先会获取子类的数量,这是childCount==0,接下来是两个if判断,判断数据是否发生变化,这里是否,所以跳过两个if,进入swich语句,mLayoutMode默认情况下是LAYOUT_NORMAL,所以进入default,接着两个判断,childCount==0,布局顺序默认从上往下,所以会调用fillFromTop()方法,

@Override
protected void layoutChildren() {
    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (blockLayoutRequests) {
        return;
    }

    mBlockLayoutRequests = true;

    try {
        ....
        final int childCount = getChildCount();
        ...
        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }
        ....
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // Clear out old views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }

        ...
}

fillFromTop(): 获取mFirstPosition的值,然后调用fillDown()方法

private View fillFromTop(int nextTop) {
    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    if (mFirstPosition < 0) {
        mFirstPosition = 0;
    }
    return fillDown(mFirstPosition, nextTop);
}

fillDown():首先获取listView整个控件的高度end,这里用的是Botton-top-padding.bottom的方式,接下来会进入一个while循环,判断nextTop也就是item的top值是否小于end,小于表示还在屏幕内。同时还要判断pos是否小于mItemCount,mItemCount是Adapter中的item个数。满足两个条件,则往listView中添加item,用的是makeAndAddView()方法。

private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

makeAndAddView():首先会尝试从RecycleBin获取一个view,可惜第一次是获取不到的,接下来会执行到obtainView()方法,

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {
    View child;
    if (!mDataChanged) {
        // Try to use an existing view for this position
        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
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

obtainView():代码第四行,尝试从RecycleBin中获取一个view,不过这时返回的结果会是null,接下来一个if语句,假设scrapView!=null,那会调用mAdapter.getView()方法,相信这个方法大家都再熟悉不过了吧!同时将scrapView当成一个参数传给getView,这里的scrapView就是getView中的convert。
那么当scrapView==null时,同样调用mAdapter.getView()方法,只不过这个时候getView中的convert==null,这个时候的convert就需要在你自己重写的getview中初始化,通过LayoutInflater函数。不过如何,最后都会返回一个view。

View obtainView(int position, boolean[] isScrap) {  
    isScrap[0] = false;  
    View scrapView;  
    scrapView = mRecycler.getScrapView(position);  
    View child;  
    if (scrapView != null) {  
        child = mAdapter.getView(position, scrapView, this);  
        if (child != scrapView) {  
            mRecycler.addScrapView(scrapView);  
            if (mCacheColorHint != 0) {  
                child.setDrawingCacheBackgroundColor(mCacheColorHint);  
            }  
        } else {  
            isScrap[0] = true;  
            dispatchFinishTemporaryDetach(child);  
        }  
    } else {  
        child = mAdapter.getView(position, null, this);  
        if (mCacheColorHint != 0) {  
            child.setDrawingCacheBackgroundColor(mCacheColorHint);  
        }  
    }  
    return child;  
}  

回到makeAndAddView()中,代码继续执行,调用setupChild()方法,

setupChild():改方法主要是设置子view,所以看相关方法就行了。在第二个if处,判断逻辑有点复杂,没关系,一个个来看。
(recycled&&!p.forceAdd) recycled是一个boolean值,取自mIsScrap[0],初始化的时候为false,后面那个(!p.forceAdd)表示是否添加到布局中,这时应该是没有,所以(!p.forceAdd)==true.

后面一个判断,viewType是否==header or footer,不是,所以最后会执行else,addViewInLayout(),将子视图加到listView中,再结合之前提到的一个while循环,当加载满一个屏幕或者pos大于adapter中的item的数量时跳出。
这就算加载好了一屏的数据了。

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");

    final boolean isSelected = selected && shouldShowSelector();
    final boolean updateChildSelected = isSelected != child.isSelected();
    final int mode = mTouchMode;
    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
            mMotionPosition == position;
    final boolean updateChildPressed = isPressed != child.isPressed();
    final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();

    // Respect layout params that are already in the view. Otherwise make some up...
    // noinspection unchecked
    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
    if (p == null) {
        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
    }
    p.viewType = mAdapter.getItemViewType(position);

    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
        attachViewToParent(child, flowDown ? -1 : 0, p);
    } else {
        p.forceAdd = false;
        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            p.recycledHeaderFooter = true;
        }
        addViewInLayout(child, flowDown ? -1 : 0, p, true);
    }

    if (updateChildSelected) {
        child.setSelected(isSelected);
    }

    if (updateChildPressed) {
        child.setPressed(isPressed);
    }

    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
        if (child instanceof Checkable) {
            ((Checkable) child).setChecked(mCheckStates.get(position));
        } else if (getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            child.setActivated(mCheckStates.get(position));
        }
    }

    if (needToMeasure) {
        final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                mListPadding.left + mListPadding.right, p.width);
        final int lpHeight = p.height;
        final int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
                    MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    } else {
        cleanupLayoutState(child);
    }

    final int w = child.getMeasuredWidth();
    final int h = child.getMeasuredHeight();
    final int childTop = flowDown ? y : y - h;

    if (needToMeasure) {
        final int childRight = childrenLeft + w;
        final int childBottom = childTop + h;
        child.layout(childrenLeft, childTop, childRight, childBottom);
    } else {
        child.offsetLeftAndRight(childrenLeft - child.getLeft());
        child.offsetTopAndBottom(childTop - child.getTop());
    }

    if (mCachingStarted && !child.isDrawingCacheEnabled()) {
        child.setDrawingCacheEnabled(true);
    }

    if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
            != position) {
        child.jumpDrawablesToCurrentState();
    }

    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

走完上述的流程,第一次的onLayout()就结束了。因为数据加载进来,导致父视图发生变化,所以会执行第二次的onLayout()

第二次onLayout()

第二次onLayout()流程和第一次的流程差不多

layoutChildren():这次childCount!=0,所以下面recycleBin.fillActiveViews()方法会对数据做缓存,接下来会调用detachAllViewsFromParent()方法,将所有的view从父视图中剥离(detach),为了不在第二次layout时,重复加载数据。下面的switch方法中,会进入else语句,并满足第二条if语句,mSelectedPosition在没有选中item的时候,默认应该为-1,mFirstPosition默认为0,所以会进入第二条if语句,执行fillSpecific()方法。

@Override
protected void layoutChildren() {
    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (blockLayoutRequests) {
        return;
    }

    mBlockLayoutRequests = true;

    try {
        ....
        final int childCount = getChildCount();
        ...
        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }
        ....
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // Clear out old views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }

        ...
}

fillSpecific():在fillSpecific方法中,还是调用了makeAndAddView()方法,所以再次进入到makeAndAddView().

/**
 * Put a specific item at a specific location on the screen and then build up and down from there.
 */
private View fillSpecific(int position, int top) {
    boolean tempIsSelected = position == mSelectedPosition;
    View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
    // Possibly changed again in fillUp if we add rows above this one.
    mFirstPosition = position;

    View above;
    View below;

    final int dividerHeight = mDividerHeight;
    if (!mStackFromBottom) {
        above = fillUp(position - 1, temp.getTop() - dividerHeight);
        // This will correct for the top of the first view not touching the top of the list
        adjustViewsUpOrDown();
        below = fillDown(position + 1, temp.getBottom() + dividerHeight);
        int childCount = getChildCount();
        if (childCount > 0) {
            correctTooHigh(childCount);
        }
    } else {
        below = fillDown(position + 1, temp.getBottom() + dividerHeight);
        // This will correct for the bottom of the last view not touching the bottom of the list
        adjustViewsUpOrDown();
        above = fillUp(position - 1, temp.getTop() - dividerHeight);
        int childCount = getChildCount();
        if (childCount > 0) {
             correctTooLow(childCount);
        }
    }

    if (tempIsSelected) {
        return temp;
    } else if (above != null) {
        return above;
    } else {
        return below;
    }
}

再次进入到makeAndAddView():这次直接能从RecycleBin.getActiveView()取得Item了,取得item之后,调用setupChild()方法,注意RecycleBin.getActiveView()方法在返回一个view的同时,会把数据里对应位置的数据删除,所以当listView布局完成之后,对应的mActiveList就会被清空,之后listView滑动的时候,起作用的主要是mScrapView数组。

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {
    View child;
    if (!mDataChanged) {
        // Try to use an existing view for this position
        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
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

setupChild():同样省略部分代码,看第二个if语句,这个时候的recycled是从makeAndAddView()中传来的值,所以recycled==true,在加上第二次layout()的时候调用了detachAllViewsFromParent()方法,解绑了所有的子view,所以(!p.forceAdd)==true,然后执行下面的attachViewToParent()方法,将子view添加到父布局中。另外,当子view被回收的时候,也会触发detach()方法,解绑,节省资源。

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {
    ...
    if (p == null) {
        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
    }
    p.viewType = mAdapter.getItemViewType(position);

    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
        attachViewToParent(child, flowDown ? -1 : 0, p);
    } else {
        p.forceAdd = false;
        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            p.recycledHeaderFooter = true;
        }
        addViewInLayout(child, flowDown ? -1 : 0, p, true);
    }

  ...
}

再回到fillSpecific()方法:将第一个item布局好之后,接下来会根据布局模式,从上往下,或者从下往上布局,来调用fillUp和fillDown,通过while循环,将其他的item布局到listView上,直至填满屏幕,或者遍历mAdapter的itemCount。

private View fillSpecific(int position, int top) {
    boolean tempIsSelected = position == mSelectedPosition;
    View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
    // Possibly changed again in fillUp if we add rows above this one.
    mFirstPosition = position;

    View above;
    View below;

    final int dividerHeight = mDividerHeight;
    if (!mStackFromBottom) {
        above = fillUp(position - 1, temp.getTop() - dividerHeight);
        // This will correct for the top of the first view not touching the top of the list
        adjustViewsUpOrDown();
        below = fillDown(position + 1, temp.getBottom() + dividerHeight);
        int childCount = getChildCount();
        if (childCount > 0) {
            correctTooHigh(childCount);
        }
    } else {
        below = fillDown(position + 1, temp.getBottom() + dividerHeight);
        // This will correct for the bottom of the last view not touching the bottom of the list
        adjustViewsUpOrDown();
        above = fillUp(position - 1, temp.getTop() - dividerHeight);
        int childCount = getChildCount();
        if (childCount > 0) {
             correctTooLow(childCount);
        }
    }

    if (tempIsSelected) {
        return temp;
    } else if (above != null) {
        return above;
    } else {
        return below;
    }
}

至此,第二个onLayout也结束,最后就是onDraw()的事情了。

总结一下:
1.在第一次布局的时候调用我们写的getView()的逻辑代码来加载第一次的数据,
2.因为需要二次布局的原因,将第一次加载的item放到一个临时数组mActiveViews中,用于在第二次makeAndAddView()时复用,
3.通过while循环和makeAndAddView()填满可见屏幕
4.接下来的listView中item的复用主要通过mScrapViews来解决。

下篇将会分析一下recycleView的实现原理。

参考博客:

http://blog.csdn.net/chunqiuwei/article/details/48765007
http://blog.csdn.net/linghu_java/article/details/39496921
http://blog.csdn.net/guolin_blog/article/details/44996879

本人也只是Android开发路上一只稍大一点的菜鸟,如果各位读者中发现文章中有误之处,请帮忙指出,你的批评和鼓励都是我前进的动力。

写在文末:如果读者朋友有什么问题或者意见可以在评论里指出.

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

推荐阅读更多精彩内容