android Adapter,AdapterView,AbsListView,ListView

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DucklikeJAVA/article/details/81948954

ListView完全解析

在着手分析之前,并不知道 guolin 大神之前分析过。然后忽然发现了。我只能评价4个字,叹为观止。

大神分析:guolin- Android ListView工作原理完全解析,带你从源码的角度彻底理解


ListView源码的代码量非常大,而且还有很多父类,接口,辅助类等等。代码可谓是“浩如烟海”(相对于自己写的代码而言)。里面的部分分析参考了上面给出的大神分析。

~_~ ❗️

那么,这么复杂的东西,要怎么看?

  1. 首先,大体浏览一下全部的类,接口,辅助类。但是不用看里面任何一个方法实现,变量定义。(走马观花即可,甚至可以不要这一步)
  2. 【精要❗️️】看ListView.java源码。但是,在看的过程中会见到很多红色的方法和变量。这些红色的方法和变量全部来自父类,父类的父类。然后就去对应的父类中去看这个方法或者变量。看完了,再继续回来看ListView.java的代码。

内部类 AbsListView.RecycleBin

该类专门有一篇针对分析:android AbsListView.RecycleBin 分析 。可以先大致浏览一下。

内部类 ArrowScrollFocusResult

    static private class ArrowScrollFocusResult {
        private int mSelectedPosition; // 当前选中的 pos
        private int mAmountToScroll; // 要滑动的总距离

        /**
         * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
         */
        void populate(int selectedPosition, int amountToScroll) {
            mSelectedPosition = selectedPosition;
            mAmountToScroll = amountToScroll;
        }

        public int getSelectedPosition() {
            return mSelectedPosition;
        }

        public int getAmountToScroll() {
            return mAmountToScroll;
        }
    }

内部类 FocusSelector

    private class FocusSelector implements Runnable {
        // the selector is waiting to set selection on the list view
        private static final int STATE_SET_SELECTION = 1;
        // the selector set the selection on the list view, waiting for a layoutChildren pass
        private static final int STATE_WAIT_FOR_LAYOUT = 2;
        // the selector's selection has been honored and it is waiting to request focus on the
        // target child.
        private static final int STATE_REQUEST_FOCUS = 3;

        private int mAction;
        private int mPosition;
        private int mPositionTop;

        // 设置属性,遍历赋值
        FocusSelector setupForSetSelection(int position, int top) {
            mPosition = position;
            mPositionTop = top;
            mAction = STATE_SET_SELECTION;
            return this;
        }

        /*
        如果当前 mAction == STATE_SET_SELECTION , 就调用 lv.setSelectionFromTop(mPosition, mPositionTop);
        并把 mAction 赋值为:STATE_WAIT_FOR_LAYOUT;
如果 mAction == STATE_REQUEST_FOCUS , 就根据 pos 去获取 childView ,并调用child.requestFocus(); ,最后把 mAction 赋值为 -1
        */
        public void run() {
            if (mAction == STATE_SET_SELECTION) {
                setSelectionFromTop(mPosition, mPositionTop);
                mAction = STATE_WAIT_FOR_LAYOUT;
            } else if (mAction == STATE_REQUEST_FOCUS) {
                final int childIndex = mPosition - mFirstPosition;
                final View child = getChildAt(childIndex);
                if (child != null) {
                    child.requestFocus();
                }
                mAction = -1;
            }
        }
        /*
        如果 pos 有效,并且 mAction 不是 STATE_WAIT_FOR_LAYOUT, 就把 mAction 设置为 STATE_REQUEST_FOCUS。并返回自己
        */
        @Nullable Runnable setupFocusIfValid(int position) {
            if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) {
                return null;
            }
            mAction = STATE_REQUEST_FOCUS;
            return this;
        }

        /*
        布局完成后,mAction 设置成 -1
        */
        void onLayoutComplete() {
            if (mAction == STATE_WAIT_FOR_LAYOUT) {
                mAction = -1;
            }
        }
    }

内部类 FixedViewInfo

    /**
     * A class that represents a fixed view in a list, for example a header at the top
     * or a footer at the bottom.
     */
    public class FixedViewInfo {
        /** The view to add to the list */
        public View view;
        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
        public Object data;
        /** <code>true</code> if the fixed view should be selectable in the list */
        public boolean isSelectable;
    }

仅一些字段,不解释。

构造函数 ListView(Context, AttributeSet, int, int)

实际调用的构造函数,就是 这个构造函数。

public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ListView, defStyleAttr, defStyleRes);

        final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
        if (entries != null) {
            setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
        }

        final Drawable d = a.getDrawable(R.styleable.ListView_divider);
        if (d != null) {
            // Use an implicit divider height which may be explicitly
            // overridden by android:dividerHeight further down.
            setDivider(d);
        }

        final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
        if (osHeader != null) {
            setOverscrollHeader(osHeader);
        }

        final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
        if (osFooter != null) {
            setOverscrollFooter(osFooter);
        }

        // Use an explicit divider height, if specified.
        if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
            final int dividerHeight = a.getDimensionPixelSize(
                    R.styleable.ListView_dividerHeight, 0);
            if (dividerHeight != 0) {
                setDividerHeight(dividerHeight);
            }
        }

        mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
        mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);

        a.recycle();
    }

构造函数里面就是对自定义属性的获取与赋值,并没有其他的逻辑。不解释。

扫描二维码关注公众号,回复: 3803871 查看本文章

AbsListView的构造函数

    public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initAbsListView();

        mOwnerThread = Thread.currentThread();

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);

        final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
        if (selector != null) {
            setSelector(selector);
        }

        mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);

        setStackFromBottom(a.getBoolean(
                R.styleable.AbsListView_stackFromBottom, false));
        setScrollingCacheEnabled(a.getBoolean(
                R.styleable.AbsListView_scrollingCache, true));
        setTextFilterEnabled(a.getBoolean(
                R.styleable.AbsListView_textFilterEnabled, false));
        setTranscriptMode(a.getInt(
                R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
        setCacheColorHint(a.getColor(
                R.styleable.AbsListView_cacheColorHint, 0));
        setSmoothScrollbarEnabled(a.getBoolean(
                R.styleable.AbsListView_smoothScrollbar, true));
        setChoiceMode(a.getInt(
                R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));

        setFastScrollEnabled(a.getBoolean(
                R.styleable.AbsListView_fastScrollEnabled, false));
        setFastScrollStyle(a.getResourceId(
                R.styleable.AbsListView_fastScrollStyle, 0));
        setFastScrollAlwaysVisible(a.getBoolean(
                R.styleable.AbsListView_fastScrollAlwaysVisible, false));

        a.recycle();

        if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
            setRevealOnFocusHint(false);
        }
    }

    private void initAbsListView() {
        // Setting focusable in touch mode will set the focusable property to true
        setClickable(true);
        setFocusableInTouchMode(true);
        setWillNotDraw(false);
        setAlwaysDrawnWithCacheEnabled(false);
        setScrollingCacheEnabled(true);

        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
        mTouchSlop = configuration.getScaledTouchSlop();
        mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mOverscrollDistance = configuration.getScaledOverscrollDistance();
        mOverflingDistance = configuration.getScaledOverflingDistance();

        mDensityScale = getContext().getResources().getDisplayMetrics().density;
    }

AbsListView的构造函数里面也没有做什么,设置了一些初始值。不过,有一个比较重要的属性,也是这里获取的,就是 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);这个属性就是itemselector。和 button,TextViewselector作用相同。(listViewitem默认是有点击样式的。)

onFinishInflate()

    /*
     * (non-Javadoc)
     *
     * Children specified in XML are assumed to be header views. After we have
     * parsed them move them out of the children list and into mHeaderViews.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        int count = getChildCount();
        if (count > 0) {
            for (int i = 0; i < count; ++i) {
                addHeaderView(getChildAt(i));
            }
            removeAllViews();
        }
    }

在构造方法里面,没有发现 ListView加载过任何布局文件。所以猜测 int count = getChildCount(); == 0 , 而且看一下removeAllViews();这个方法也能确认这一点。如果 count>0,最终会导致直接抛异常。所以,onFinishInflate()就什么都没做。这里的默认文档注释似乎和实际逻辑不相符。

    @Override
    public void removeAllViews() {
        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
    }

onFocusChanged()

ListView第一次加载到界面上的时候,会回调该方法。
先看父类的该方法:

@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
    super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
    if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
        if (!isAttachedToWindow() && mAdapter != null) {
            // Data may have changed while we were detached and it's valid
            // to change focus while detached. Refresh so we don't die.
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
        }
        resurrectSelection();
    }
}

通过log发现 isInTouchMode() == true。。。
所以认为这里面什么逻辑都没有走。

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

        final ListAdapter adapter = mAdapter;
        int closetChildIndex = -1;
        int closestChildTop = 0;
        if (adapter != null && gainFocus && previouslyFocusedRect != null) {
            previouslyFocusedRect.offset(mScrollX, mScrollY);

            // Don't cache the result of getChildCount or mFirstPosition here,
            // it could change in layoutChildren.
            if (adapter.getCount() < getChildCount() + mFirstPosition) {
                mLayoutMode = LAYOUT_NORMAL;
                layoutChildren();
            }

            // figure out which item should be selected based on previously
            // focused rect
            Rect otherRect = mTempRect;
            int minDistance = Integer.MAX_VALUE;
            final int childCount = getChildCount();
            final int firstPosition = mFirstPosition;

            for (int i = 0; i < childCount; i++) {
                // only consider selectable views
                if (!adapter.isEnabled(firstPosition + i)) {
                    continue;
                }

                View other = getChildAt(i);
                other.getDrawingRect(otherRect);
                offsetDescendantRectToMyCoords(other, otherRect);
                int distance = getDistance(previouslyFocusedRect, otherRect, direction);

                if (distance < minDistance) {
                    minDistance = distance;
                    closetChildIndex = i;
                    closestChildTop = other.getTop();
                }
            }
        }

        if (closetChildIndex >= 0) {
            setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
        } else {
            requestLayout();
        }
    }

通过 debug发现 previouslyFocusedRect == null , 所以,最终调用的是requestLayout();

onMeasure()

先看一下父类的。

AbsListView#onMeasure()

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mSelector == null) {
            useDefaultSelector();
        }
        final Rect listPadding = mListPadding;
        listPadding.left = mSelectionLeftPadding + mPaddingLeft;         listPadding.top = mSelectionTopPadding + mPaddingTop;
        listPadding.right = mSelectionRightPadding + mPaddingRight;
        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;

        // Check if our previous measured size was at a point where we should scroll later.
        if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
            final int childCount = getChildCount(); // 获取 child count
            final int listBottom = getHeight() - getPaddingBottom();// 获取自己的高度 == 自己底部-自己顶部
            final View lastChild = getChildAt(childCount - 1); // 最后一个 child
            final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; // 最后一个 child 的底部-自己的顶部
            mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
                    lastBottom <= listBottom;
         // mLastHandledItemCount === 上次数据刷新时,adapter.getCount();
         // mForceTranscriptScroll == 当前要显示的 items 是不是包含最后一个 item
        }
    }

里面涉及到两个方法:

private void useDefaultSelector() {
    setSelector(getContext().getDrawable(
            com.android.internal.R.drawable.list_selector_background));
}
public void setSelector(Drawable sel) {
    if (mSelector != null) {
        mSelector.setCallback(null);
        unscheduleDrawable(mSelector);
    }
    mSelector = sel; // 给 mSelector 赋值了
    Rect padding = new Rect();
    sel.getPadding(padding); // padding --> 0,0,0,0
    mSelectionLeftPadding = padding.left; // 0
    mSelectionTopPadding = padding.top; // 0
    mSelectionRightPadding = padding.right; // 0
    mSelectionBottomPadding = padding.bottom; // 0
    sel.setCallback(this);
    updateSelectorState();
}
// Drawable.java 
public boolean getPadding(@NonNull Rect padding) {
    padding.set(0, 0, 0, 0);
    return false;
}

关于 :【android】ListView 的 transcriptMode 选项

父类里面并没有进行真的测量,只是给一些变量赋值了。

// ListView#onMeasure()
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                || heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);

            // Lay out child directly against the parent measure spec so that
            // we can obtain exected minimum width and height.
            measureScrapChild(child, 0, widthMeasureSpec, heightSize);

            childWidth = child.getMeasuredWidth();
            childHeight = child.getMeasuredHeight();
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
                mRecycler.addScrapView(child, 0);
            }
        }

        if (widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = mListPadding.left + mListPadding.right + childWidth +
                    getVerticalScrollbarWidth();
        } else {
            widthSize |= (childState & MEASURED_STATE_MASK);
        }

        if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

        setMeasuredDimension(widthSize, heightSize);

        mWidthMeasureSpec = widthMeasureSpec;
    }

里面有一个 obtainView(),看一下做了什么:

    View obtainView(int position, boolean[] outMetadata) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        outMetadata[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position); // 从 recycleBin 里面获取对应位置的‘瞬态’ view 
        if (transientView != null) {
            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

            // If the view type hasn't changed, attempt to re-bind the data.
            if (params.viewType == mAdapter.getItemViewType(position)) {
                final View updatedView = mAdapter.getView(position, transientView, this);

                // If we failed to re-bind the data, scrap the obtained view.
                // 获取到了之后,与 adapter 的相同 pos 的 view 进行比较一下
                // 如果不相同,说明之前保存的‘瞬态’view 是不正确的
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }

        final View scrapView = mRecycler.getScrapView(position);
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }

        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }

        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        setItemViewLayoutParams(child, position);

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            if (child.getAccessibilityDelegate() == null) {
                child.setAccessibilityDelegate(mAccessibilityDelegate);
            }
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return child;
    }

obtainView()可以分成两段来看:

第一段是先获取‘瞬态’view, 通过比较:transientView == updateView = adapter.getView(pos) ?, 如果不相同,就把 updateView缓存到RecycleBin里面,不过不一定是缓存到scrapedViews里面。

可以存储的地方有:

private ArrayList<View>[] mScrapViews;
private ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;

具体存储到哪里,需要根据 viewType等属性去决定。

第二段和第一段逻辑差不多,不过比较的不是‘瞬态’的view,而是‘废料’view。通过比较:scrapView == updateView = adapter.getView(pos) ?, 如果不相同,就把 scrapView缓存到RecycleBin里面(更新了对应的pos),不过不一定是缓存到scrapedViews里面。

然后是注意该方法的返回值,如果获取的‘瞬态’view不为空,最终返回的是该瞬态view。如果 获取的废料view不论是不是空,最终返回的是adapter.getView(pos)的值。并且,如果 返回的 view是 瞬态的,mIsScrap[0] = true;

private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) {
    LayoutParams p = (LayoutParams) child.getLayoutParams();
    if (p == null) {
        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
        child.setLayoutParams(p);
    }
    p.viewType = mAdapter.getItemViewType(position);
    p.isEnabled = mAdapter.isEnabled(position);
    p.forceAdd = true;

    final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
            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(heightHint, MeasureSpec.UNSPECIFIED);
    }
    child.measure(childWidthSpec, childHeightSpec);

    // Since this view was measured directly aginst the parent measure
    // spec, we must measure it again before reuse.
    child.forceLayout();
}

这个方法我不知道怎么解释,一知半解的。反正就是调用了child.measure(w,h);

再整体看一下 onMeasure()做了什么:

  1. 获取位置0所在的view , 如果缓存里面没有就从adapter.getView(0,...)里面获取。
  2. 根据获取的view进行view.measure(w,h)
  3. 如果当前 viewviewType>=0, 就缓存到RecycleBin里面:mRecycler.addScrapView(child, 0);
  4. setMeasuredDimension(widthSize, heightSize); 结束。

obtainView() 里面已经调用过mRecycler.addScrapView(child, 0);,onMeasure()的下来又调用,感觉重复了。【有待考证…】

layoutChildren()

第一次 layout

第一次 layout的时候,childCount==0

layoutChildren() 
--> sel = fillFromTop(childrenTop);
--> fillDown(mFirstPosition, nextTop); // #执行填充 view 到 listView的操作
--> View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
--> setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); // 真正调用 viewGroup.addViewInLayout(v) 的地方
// 第一次进入的时候 pos == 0 , nextTop = paddingTop ,一般也就是 0 了
private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop); // listView 的高度。
    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; // 当前 child 的底部+一个分割线的高度【距离 parent top】
        if (selected) {
            selectedView = child;
        }
        pos++;
    }
    // 这个循环的意思很明显了,就是不断的去获取 child, 直到 有 child的底部超出 parent 的底部了;或者, mItemCount 个 child 全部获取了。
    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

–> 去看 makeAndAddView()

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    if (!mDataChanged) {
        // Try to use an existing view for this position.
        final View activeView = mRecycler.getActiveView(position);
        if (activeView != null) {
            // Found it. We're reusing an existing child, so it just needs
            // to be positioned like a scrap view.
            setupChild(activeView, position, y, flow, childrenLeft, selected, true);
            return activeView;
        }
    }

    // Make a new view for this position, or convert an unused view if
    // possible.
    final View child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured.
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
    return child;
}
    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean isAttachedToWindow) {
        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 = !isAttachedToWindow || updateChildSelected
                || child.isLayoutRequested();

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

        // Set up view state before attaching the view, since we may need to
        // rely on the jumpDrawablesToCurrentState() call that occurs as part
        // of view attachment.
        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 ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
                && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            attachViewToParent(child, flowDown ? -1 : 0, p);

            // If the view was previously attached for a different position,
            // then manually jump the drawables.
            if (isAttachedToWindow
                    && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                            != position) {
                child.jumpDrawablesToCurrentState();
            }
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
            // add view in layout will reset the RTL properties. We have to re-resolve them
            child.resolveRtlPropertiesIfNeeded();
        }

        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);
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

这里面的代码很多,但是可以肯定,第一次的时候肯定是走到 addViewInLayout(child, flowDown ? -1 : 0, p, true);这里了,不然怎么把adpter里面的 view加载到 ListView里面。

关于这一块的逻辑,guolin:Android ListView工作原理完全解析,带你从源码的角度彻底理解 里面,写的非常细致,我就不写了。(写不到那么好~)。

不过要注意一下,在第一次进行layoutChildren()的时候,是从 adapter.getView()里面去生成item views的,并调用vg.addViewInlayout(child)去加载并显示的。按理说,加载完成就应该对这些item views进行缓存的操作。但是,实际上,此时并没有进行缓存。只是把 ListView给加载显示到界面了。那么,缓存,到底在合适执行的呢?–是在第二次,以及后续的layoutChildren()的时候去执行的。

不分析了,烦。

猜你喜欢

转载自blog.csdn.net/DucklikeJAVA/article/details/81948954