Android面试题目之(13) ListView的设计原理

[size=xx-large]1.ListView的目标[/size]

ListView希望通过复用看不见的View来达到目的。其中最最重要的就是这个

[size=xx-large]2.RecycleBin--回收站.[/size]

回收站并不是垃圾站。
最重要的是两个成员变量 mScrapViews 和mActiveViews, mScrapViews 表示回收站的View. mActiveViews表示激活的View,激活的View是可以直接拿来显示的,不想要重新调用getView填充数据。激活的View(mActiveViews)和废弃的View(mScrapViews) 不同,激活的View不想要调用getView填充数据,而废弃的View需要。

 /**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews;

private View[] mActiveViews = new View[0];


看不见的View都会放进这里面。主要是通过以下方法 addScrapView 加进去的
    /**
* Put a view into the ScapViews list. These views are unordered.
*
* @param scrap The view to add
*/
void addScrapView(View scrap, int position) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}

// Don't put header or footer views or views that should be ignored
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
removeDetachedView(scrap, false);
}
return;
}

lp.scrappedFromPosition = position;

if (mViewTypeCount == 1) {
scrap.dispatchStartTemporaryDetach();
mCurrentScrap.add(scrap);
} else {
scrap.dispatchStartTemporaryDetach();
mScrapViews[viewType].add(scrap);
}

if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}


[size=x-large]3.回收站算法[/size]

makeAndAddView 这个方法显示了回收站的使用算法。如果没有dadachange,那么就直接从getActiveView 方法取得那个View,如果成功,就调整其位置。如果datachange,就需要重新绑定数据到这个View上。这里的 重新绑定数据 方法obtainView 同时考虑是否使用回收站的View.

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

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



[size=x-large]4.通过ObtainView方法绑定数据[/size]

其实也很简单。主要任务是复用mScrapViews。

View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;

scrapView = mRecycler.getScrapView(position);

View child;
if (scrapView != null) {
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) {
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 {
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;
}

[size=x-large]5.布局listView [/size]

fillDown 方法用得最多,其目的就是从pos这个index和nextTop这个View top坐标,向下填充Views直到底部。

/**
* 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++;
}

return selectedView;
}

[size=x-large]6.触发fillDown的切入点[/size]

layoutChildren
trackMotionScroll


[size=x-large]7.不得不说的LayoutParams [/size]
    /**
* AbsListView extends LayoutParams to provide a place to hold the view type.
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* View type for this view, as returned by
* {@link android.widget.Adapter#getItemViewType(int) }
*/
@ViewDebug.ExportedProperty(category = "list", mapping = {
@ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
@ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
})
int viewType;

/**
* When this boolean is set, the view has been added to the AbsListView
* at least once. It is used to know whether headers/footers have already
* been added to the list view and whether they should be treated as
* recycled views or not.
*/
@ViewDebug.ExportedProperty(category = "list")
boolean recycledHeaderFooter;

/**
* When an AbsListView is measured with an AT_MOST measure spec, it needs
* to obtain children views to measure itself. When doing so, the children
* are not attached to the window, but put in the recycler which assumes
* they've been attached before. Setting this flag will force the reused
* view to be attached to the window rather than just attached to the
* parent.
*/
@ViewDebug.ExportedProperty(category = "list")
boolean forceAdd;

/**
* The position the view was removed from when pulled out of the
* scrap heap.
* @hide
*/
int scrappedFromPosition;

public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}

public LayoutParams(int w, int h) {
super(w, h);
}

public LayoutParams(int w, int h, int viewType) {
super(w, h);
this.viewType = viewType;
}

public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}


[size=x-large]8.提高布局性能的函数 offsetLeftAndRight和 offsetTopAndBottom[/size]

在setupChild函数 中可以看到如下两行
      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());
}

[size=xx-large]9. 提高性能的函数addViewInLayout[/size]

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值