ListView原理概述

     ListView的源码特别长,流程特别多,看完郭神的解析博客( http://blog.csdn.net/guolin_blog/article/details/44996879)还是一脸懵逼。这篇文章根据郭神的文章,尝试记录下ListView源码的总体思路。


RecycleBin

     首先ListView继承自AbsListView。AbsListView中有一个内部类RecycleBin,根据RecycleBin的介绍:

/**
* The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
* storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
* start of a layout. By construction, they are displaying current information. At the end of
* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
* could potentially be used by the adapter to avoid allocating views unnecessarily.
*
* @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
* @see android.widget.AbsListView.RecyclerListener
*/
   
     可以知道这个类是用来帮助复用view的。这个类中有个保存当前在屏幕上显示的view的数组mActiveViews和可以被用来复用的view的mScrapViews:

/**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
private View[] mActiveViews = new View[0];

/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews;
     
     在RecycleBin中提供了add/get/fill等方法用于对mActiveViews和mScrapViews进行操作,这里不列出。

第一次Layout
     
     在View的工作流程中,已经知道onLayout方法是用于给ViewGroup确定子元素的位置的。ListView也是ViewGroup,但它没有重写onLayout方法,而是在其父类AbsListView中有实现。

@Override
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();          // 注1
    mInLayout = false;

    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

    // TODO: Move somewhere sane. This doesn't belong in onLayout().
    if (mFastScroll != null) {
        mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
    }
}
     在onLayout方法中,前边是判断ListView的大小或位置有没有发生变化,如果发生变化就要求所有的子布局强制进行重绘。接下来看注1的layoutChildren方法。layoutChildren由ListView进行重写了,layoutChildren方法很长,我们直接看它的一个关键调用:

/**
* Fills the list from top to bottom, starting with mFirstPosition
*
* @param nextTop The location where the top of the first item should be
*        drawn
*
* @return The view that is currently selected
*/
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);
}

     fillFromTop的任务就是从mFirstPosition开始,自顶至底去填充ListView。在这里又调用了fillDown()方法,填充ListView的操作都是在这里面进行的:

/**
* Fills the list from pos down to the end of the list view.
*
* @param pos The first position to put in the list
*
* @param nextTop The location where the top of the item associated with pos
*        should be drawn
*
* @return The view that is currently selected, if it happens to be in the
*        range that we draw.
*/
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;
}
     在fillDown方法中,有个while循环来执行重复逻辑,填充ListView,直到nextTop(即元素的顶部坐标值)超过屏幕的底部了,或者pos大于mItemCount(即Adapter中所有的元素都被遍历了)时跳出循环。在while中,调用了makeAndAddView方法来获取一个View。

/**
* Obtain the view and add it to our list of children. The view can be made
* fresh, converted from an unused view, or used as is if it was in the
* recycle bin.
*
* @param position Logical position in the list
* @param y Top or bottom edge of the view to add
* @param flow If flow is true, align top edge to y. If false, align bottom
*        edge to y.
* @param childrenLeft Left edge where children should be positioned
* @param selected Is this position selected?
* @return View that was added
*/
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;
}
     在上面的makeAndAddView方法中,首先尝试从RecycleBin中获取一个active view。但第一次Layout时里面并没有缓存任何数据,得到的是null。然后调用obtainView获取View,obtainView的总体流程是,先用RecycleBin的getScrapView方法尝试获取一个废弃缓存中的View,如果没有,则调用mAdapter的getView方法,即是我们重写的getView方法来获取一个View,第一次Layout过程中,所有的View都是通过getView方法的inflate方法加载出来的,因此会比较耗时。获取到View之后就通过setupChild方法将View设置到ListView上。第一次layout至此结束。


第二次layout

     第二次layout中,此时ListView中已有子View,因此可以保存到mActiveViews数组中。然后通过detachAllViewsFromParent方法将所有的子View清除,避免产生重复的数据。然后我们直接来看makeAndAddView方法。此时尝试从RecycleBin中获取active views一定可以获取到了,获取到后,直接setupChild,就不用再去通过getView方法inflate。


滑动加载更多数据
     
     活动触摸监听在onTouchEvent中,具体的调用栈就不再去分析,太多也记不住。总体的处理逻辑是,当判断到子View的bottom/top值小于ListView的top/bottom值时,说明这个子View已经滑出屏幕,所以会调用RecycleBin的addScrapView方法将这个View加入到废弃缓存中,然后把所有移出屏幕的子View从ListView中detach掉。然后调用offsetChildrenTopAndBottom方法将ListView中的所有子View进行偏移,实现随着手指的移动,ListView跟着滚动的效果。
     当有新的View移入屏幕时,就会尝试从RecycleBin中的getScrapView方法来从废弃缓存中获取一个View,然后调用getView方法,把获取到的废弃的View作为convertView参数传入,在getView方法中,我们就可以直接将convertView中的数据替换即可,免去inflate。


























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值