This page looks best with JavaScript enabled

ListView原理分析

 ·  ☕ 3 min read

先上张图

ListView 的视图循环使用机制主要实现自内部的 RecyclerBin。


ListView继承关系

通过 ListView 的继承关系,可以看出它继承自 AbsListView,是个 ViewGroup。ViewGroup 的绘制流程无非就是 measure -> layout -> draw。

ListView 中实现了自己的 onMeasure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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) {
    heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

没什么特别的,把各种尺寸和子View的measure求和。然后在 ListView 中没有发现 onLayout,进入 AbsListView 查看。

1
2
3
4
5
6
7
8
9
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);

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

会调用 layoutChildren,ListView 中实现了这个方法。直接到核心步骤。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
......
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
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) {
......
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;
}

recycleBin.scrapActiveViews();
  • childCount
  • mStackFromBottom: AbsListView 中的属性,默认为 false,表示列表将会从上到下的填充,反之从下到上填充

不单独调用 setStackFromBottom(true)的话, ListView 就会从上到下开始填充,调用 fillFromTop,接着调用 fillDown

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 里循环进行了 子View 的填充。

  • end: ListView 的高度
  • nextTop: 表示单个 子View的顶部 相对于 ListView 的位置高度
  • pos: 进入 ListView 的第一个位置,默认为 0,没填充一个子 View 就 +1

循环条件 nextTop < end && pos < mItemCount:ListView 的高度要大于下一个子 View 的顶部位置 && pos 要小于 adapter 里返回的 ItemCount。这样就确保 ListView 只会填充一定数量,它自身高度能够显示完全的子 View。

进入 makeAndAddView 方法中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}

如果数据没有改变,会先从 recycler 中获取与指定位置对应的视图,如果找到了就直接设置然后返回;没找到就会调用 AbsListView 的 obtainView 获取一个 View,然后设置。

未完待续

Support the author with
alipay QR Code
wechat QR Code

Yang
WRITTEN BY
Yang
Developer