ListView
完全解析
在着手分析之前,并不知道 guolin 大神之前分析过。然后忽然发现了。我只能评价4个字,叹为观止。
大神分析:guolin- Android ListView工作原理完全解析,带你从源码的角度彻底理解
ListView
源码的代码量非常大,而且还有很多父类,接口,辅助类等等。代码可谓是“浩如烟海”(相对于自己写的代码而言)。里面的部分分析参考了上面给出的大神分析。
~_~ ❗️
那么,这么复杂的东西,要怎么看?
- 首先,大体浏览一下全部的类,接口,辅助类。但是不用看里面任何一个方法实现,变量定义。(走马观花即可,甚至可以不要这一步)
- 【精要❗️️】看
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();
}
构造函数里面就是对自定义属性的获取与赋值,并没有其他的逻辑。不解释。
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);
这个属性就是item
的selector
。和button
,TextView
的selector
作用相同。(listView
的item
默认是有点击样式的。)
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()
做了什么:
- 获取位置
0
所在的view
, 如果缓存里面没有就从adapter.getView(0,...)
里面获取。 - 根据获取的
view
进行view.measure(w,h)
。 - 如果当前
view
的viewType>=0
, 就缓存到RecycleBin
里面:mRecycler.addScrapView(child, 0);
。 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()
的时候去执行的。
不分析了,烦。