ListView源码分析(二)

来源:互联网 发布:茶叶网络连锁 编辑:程序博客网 时间:2024/06/10 03:09

ListView的setAdapter实现
查看GrepCode网站ListView源码发现setAdapter主要有以下几个重要方法:

layoutChildren,fillFromTop,fillDown /fillUp
makeAndAddView,obtainView,setupChild

先简单看下layoutChildren源码

@Override    protected void layoutChildren() {            .........            boolean dataChanged = mDataChanged;            if (dataChanged) {                handleDataChanged();            }            .........            // Clear out old views            detachAllViewsFromParent();            switch (mLayoutMode) {            .........            case LAYOUT_FORCE_BOTTOM:                sel = fillUp(mItemCount - 1, childrenBottom);                adjustViewsUpOrDown();                break;            case LAYOUT_FORCE_TOP:                mFirstPosition = 0;                sel = fillFromTop(childrenTop);                adjustViewsUpOrDown();                break;            .........            }       .........    }

setAdapter之后dataChanged为true,执行handleDataChanged()方法。

@Override    protected void handleDataChanged() {        .........        // Nothing is selected. Give up and reset everything.        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;        mSelectedPosition = INVALID_POSITION;        mSelectedRowId = INVALID_ROW_ID;        mNextSelectedPosition = INVALID_POSITION;        mNextSelectedRowId = INVALID_ROW_ID;        mNeedSync = false;        mSelectorPosition = INVALID_POSITION;        checkSelectionChanged();    }

注意这个方法注意是设置mLayoutMode,通常是LAYOUT_FORCE_TOP,即从顶部开始一个一个的往下添加childview。
  布局中ListView默认没有设置android:stackfrombottom属性,因此回到layoutChildren()方法中,执行LAYOUT_FORCE_TOP条件语句,设置mFirstPosition=0后,将childrenTop(=0或padding top 后的值),进入fillFromTop方法:

/**     * 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);    }/**     * 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.     */     //第一次进来pos = 0,nexttop 是 padding.top    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;//减去paddingbotton属性值        }        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++;        }        return selectedView;    }

可视区域的高度(mBottom - mTop),如果ListView条目很少,可视区高度不超过屏幕高度,最大不超过屏幕高度。
  while循环中,判断累计添加到listview中child的高度,不超过可视区域(添加最后一个child时,有可能只显示部分),且添加的child的下标不超过总的个数(否则系统会报 OutOfBounds 的异常)。在循环中,会去调用makeAndAddView,这个方法不会真正的去添加child,但会调用之后的setupChild来真正添加到listview中:

/**     * 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;        //两种情况:1.数据源没有发生改变 2.数据源发生改变        if (!mDataChanged) {            // Try to use an existing view for this position            child = mRecycler.getActiveView(position);            if (child != null) {                if (ViewDebug.TRACE_RECYCLER) {                    ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,                            position, getChildCount());                }                // Found it -- we're using an existing child                // This just needs to be positioned                setupChild(child, position, y, flow, childrenLeft, selected, true);                return child;//数据源没有发生改变,调用mRecycler.getActiveView(position);            }        }        // Make a new view for this position, or convert an unused view if possible        child = obtainView(position, mIsScrap);//数据源发生改变,调用obtainView()        // This needs to be positioned and measured        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);        return child;    }

该方法分两种情况:

  1. adapter中的数据发生了变化,初始setAdapter,或之后我们将adapter中的数据做了新增/删除后,调用Adapter.notifyDataSetChanged;变化了,就会从RecycleBin中的mScrapView中,取之前滑出屏幕的view,即convertView来复用;

  2. 若没有变化,则从RecycleBin的mActiveView中取当前显示的view( 为啥会有这种情况?当listview稳定后,我们不滚动它,但有可能点击或长按 ),这时就会走到这里。
      obtainView就是从RecycleBin中,取移出去的View,传给Adapter.getView方法(convertView):

 /*** Get a view and have it show the data associated with the specified     * position. This is called when we have already discovered that the view is     * not available for reuse in the recycle bin. The only choices left are     * converting an old view or making a new one.     *     * @param position The position to display     * @param isScrap Array of at least 1 boolean, the first entry will become true if     *                the returned view was taken from the scrap heap, false if otherwise.     *     * @return A view displaying the data associated with the specified position     */    View obtainView(int position, boolean[] isScrap) {        isScrap[0] = false;        View scrapView;     scrapView = mRecycler.getScrapView(position);//获取scrapView        View child;        if (scrapView != null) {//如果缓存中有,将这个convertView传给Adapter.getView方法            if (ViewDebug.TRACE_RECYCLER) {                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,                        position, -1);            }            child = mAdapter.getView(position, scrapView, this);            if (ViewDebug.TRACE_RECYCLER) {                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,                        position, getChildCount());            }            if (child != scrapView) {//判断从getView方法中返回的view是否与scrapview一致            //如果一致,表明是复用的,反之,则程序又去创建了一个新的view(浪费了一块内存),且将得到的scrapview重新加入到RecycleBin.mScrapView中;                mRecycler.addScrapView(scrapView, position);                if (mCacheColorHint != 0) {                    child.setDrawingCacheBackgroundColor(mCacheColorHint);                }                if (ViewDebug.TRACE_RECYCLER) {                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,                            position, -1);                }            } else {                isScrap[0] = true;                child.dispatchFinishTemporaryDetach();            }        } else {//如果缓存中没有,则convertView为null,在Adapter中,需要自己去LayoutInflater一个view            child = mAdapter.getView(position, null, this);            if (mCacheColorHint != 0) {                child.setDrawingCacheBackgroundColor(mCacheColorHint);            }            if (ViewDebug.TRACE_RECYCLER) {                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,                        position, getChildCount());            }        }        return child;    }

这个方法,实际上是在AbsListView类中的。RecycleBin类,它的作用就是一个View的缓存,将移出屏幕外的view回收,并给新移入到屏幕内的view来复用,这样就能节省大量内存。

回到makeAndAddView方法,将child传给setupChild,开始真正的加入到listView中去显示。

/**     * Add a view as a child and make sure it is measured (if necessary) and     * positioned properly.     *     * @param child The view to add     * @param position The position of this child     * @param y The y position relative to which this view will be positioned     * @param flowDown If 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?     * @param recycled Has this view been pulled from the recycle bin? If so it     *        does not need to be remeasured.     */private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,boolean selected, boolean recycled) {        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 = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);        }        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));            }        }        //如果需要测量,先测量子view        if (needToMeasure) {            int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,                    mListPadding.left + mListPadding.right, p.width);            int lpHeight = p.height;            int childHeightSpec;            if (lpHeight > 0) {                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);            } else {                childHeightSpec = MeasureSpec.makeMeasureSpec(0, 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();        }    }

  attachViewToParent 和 addViewInLayout两者大致差不多,都是将view添加到parent view的array中,区别在于,attachView是不用去requestLayout的,而addViewInLayout的最后一个参数指明(true不用requestLayout,false则需要requestLayout),这两个方法都在ViewGroup中。
  flowDown ? -1 : 0 , -1 和 0 的区别?我们看下attachViewToParent代码吧:

/**     * Attaches a view to this view group. Attaching a view assigns this group as the parent,     * sets the layout parameters and puts the view in the list of children so it can be retrieved     * by calling {@link #getChildAt(int)}.     *     * This method should be called only for view which were detached from their parent.     *     * @param child the child to attach     * @param index the index at which the child should be attached     * @param params the layout parameters of the child     *     * @see #removeDetachedView(View, boolean)     * @see #detachAllViewsFromParent()     * @see #detachViewFromParent(View)     * @see #detachViewFromParent(int)     */    protected void attachViewToParent(View child, int index, LayoutParams params) {        child.mLayoutParams = params;        if (index < 0) {            index = mChildrenCount;        }        addInArray(child, index);        child.mParent = this;        child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) |                DRAWN | INVALIDATED;        this.mPrivateFlags |= INVALIDATED;        if (child.hasFocus()) {            requestChildFocus(child, child.findFocus());        }    }

如果是-1,则将index = mChildrenCount,即从当前child view数组的尾部开始加入,如果是0, 则从当前child view的头部开始加入。
  默认情况下,添加到listview中的item,即child都会measure一次高度和宽度,然后,调用child.layout,通知新添加的child,layout一下它里面的children。
  然后,然后就没有了然后,整个流程走完,回到ListView.layoutChildren中,adjustViewsUpOrDown将所有child调整对齐,刷新一下RecycleBin的Active和Scrap缓存,调用updateScrollIndicators更新一下滚动条的值,若有注意OnScrollListener,也通知一下invokeOnItemScrollListener。

0 0