您的位置:首页 > 其它

20ListView

2016-06-15 15:29 295 查看
在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。ListView可以使用列表的形式来展示内容,超出屏幕部分的内容只需要通过手指滑动就可以移动到屏幕内了。

另外ListView还有一个功能,达到成百上千条甚至更多,ListView都不会发生OOM或者崩溃,而且随着我们手指滑动来浏览更多数据时,程序所占用的内存竟然都不会跟着增长。ListView的代码量比较大,复杂度也很高,很难用文字表达清楚。


RecycleBin机制

RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。其实RecycleBin的代码并不多,只有300行左右,它是写在AbsListView中的一个内部类,所以所有继承自AbsListView的子类,也就是ListView和GridView,都可以使用这个机制。RecycleBin中的主要代码,如下所示:

[java] view
plaincopy

/**

* The RecycleBin facilitates reuse of views across layouts. The RecycleBin

* has two levels of storage: ActiveViews and ScrapViews. ActiveViews are

* those views which were onscreen at the start of a layout. By

* construction, they are displaying current information. At the end of

* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews

* are old views that could potentially be used by the adapter to avoid

* allocating views unnecessarily.

*

* @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)

* @see android.widget.AbsListView.RecyclerListener

*/

class RecycleBin {

private RecyclerListener mRecyclerListener;

/**

* The position of the first view stored in mActiveViews.

*/

private int mFirstActivePosition;

/**

* Views that were on screen at the start of layout. This array is

* populated at the start of layout, and at the end of layout all view

* in mActiveViews are moved to mScrapViews. Views in mActiveViews

* represent a contiguous range of Views, with position of the first

* view store in mFirstActivePosition.

*/

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

/**

* Unsorted views that can be used by the adapter as a convert view.

*/

private ArrayList<View>[] mScrapViews;

private int mViewTypeCount;

private ArrayList<View> mCurrentScrap;

/**

* Fill ActiveViews with all of the children of the AbsListView.

*

* @param childCount

* The minimum number of views mActiveViews should hold

* @param firstActivePosition

* The position of the first view that will be stored in

* mActiveViews

*/

void fillActiveViews(int childCount, int firstActivePosition) {

if (mActiveViews.length < childCount) {

mActiveViews = new View[childCount];

}

mFirstActivePosition = firstActivePosition;

final View[] activeViews = mActiveViews;

for (int i = 0; i < childCount; i++) {

View child = getChildAt(i);

AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();

// Don't put header or footer views into the scrap heap

if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {

// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in

// active views.

// However, we will NOT place them into scrap views.

activeViews[i] = child;

}

}

}

/**

* Get the view corresponding to the specified position. The view will

* be removed from mActiveViews if it is found.

*

* @param position

* The position to look up in mActiveViews

* @return The view if it is found, null otherwise

*/

View getActiveView(int position) {

int index = position - mFirstActivePosition;

final View[] activeViews = mActiveViews;

if (index >= 0 && index < activeViews.length) {

final View match = activeViews[index];

activeViews[index] = null;

return match;

}

return null;

}

/**

* Put a view into the ScapViews list. These views are unordered.

*

* @param scrap

* The view to add

*/

void addScrapView(View scrap) {

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;

}

if (mViewTypeCount == 1) {

dispatchFinishTemporaryDetach(scrap);

mCurrentScrap.add(scrap);

} else {

dispatchFinishTemporaryDetach(scrap);

mScrapViews[viewType].add(scrap);

}

if (mRecyclerListener != null) {

mRecyclerListener.onMovedToScrapHeap(scrap);

}

}

/**

* @return A view from the ScrapViews collection. These are unordered.

*/

View getScrapView(int position) {

ArrayList<View> scrapViews;

if (mViewTypeCount == 1) {

scrapViews = mCurrentScrap;

int size = scrapViews.size();

if (size > 0) {

return scrapViews.remove(size - 1);

} else {

return null;

}

} else {

int whichScrap = mAdapter.getItemViewType(position);

if (whichScrap >= 0 && whichScrap < mScrapViews.length) {

scrapViews = mScrapViews[whichScrap];

int size = scrapViews.size();

if (size > 0) {

return scrapViews.remove(size - 1);

}

}

}

return null;

}

public void setViewTypeCount(int viewTypeCount) {

if (viewTypeCount < 1) {

throw new IllegalArgumentException("Can't have a viewTypeCount < 1");

}

// noinspection unchecked

ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];

for (int i = 0; i < viewTypeCount; i++) {

scrapViews[i] = new ArrayList<View>();

}

mViewTypeCount = viewTypeCount;

mCurrentScrap = scrapViews[0];

mScrapViews = scrapViews;

}

}

view
plaincopy

/**

* Subclasses should NOT override this method but {@link #layoutChildren()}

* instead.

*/

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

mInLayout = true;

if (changed) {

int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

getChildAt(i).forceLayout();

}

mRecycler.markChildrenDirty();

}

layoutChildren();

mInLayout = false;

}

ListView的layoutChildren()方法,代码如下所示:

[java] view
plaincopy

@Override

protected void layoutChildren() {

final boolean blockLayoutRequests = mBlockLayoutRequests;

if (!blockLayoutRequests) {

mBlockLayoutRequests = true;

} else {

return;

}

try {

super.layoutChildren();

invalidate();

if (mAdapter == null) {

resetList();

invokeOnItemScrollListener();

return;

}

int childrenTop = mListPadding.top;

int childrenBottom = getBottom() - getTop() - mListPadding.bottom;

int childCount = getChildCount();

int index = 0;

int delta = 0;

View sel;

View oldSel = null;

View oldFirst = null;

View newSel = null;

View focusLayoutRestoreView = null;

// Remember stuff we will need down below

switch (mLayoutMode) {

case LAYOUT_SET_SELECTION:

index = mNextSelectedPosition - mFirstPosition;

if (index >= 0 && index < childCount) {

newSel = getChildAt(index);

}

break;

case LAYOUT_FORCE_TOP:

case LAYOUT_FORCE_BOTTOM:

case LAYOUT_SPECIFIC:

case LAYOUT_SYNC:

break;

case LAYOUT_MOVE_SELECTION:

default:

// Remember the previously selected view

index = mSelectedPosition - mFirstPosition;

if (index >= 0 && index < childCount) {

oldSel = getChildAt(index);

}

// Remember the previous first child

oldFirst = getChildAt(0);

if (mNextSelectedPosition >= 0) {

delta = mNextSelectedPosition - mSelectedPosition;

}

// Caution: newSel might be null

newSel = getChildAt(index + delta);

}

boolean dataChanged = mDataChanged;

if (dataChanged) {

handleDataChanged();

}

// Handle the empty set by removing all views that are visible

// and calling it a day

if (mItemCount == 0) {

resetList();

invokeOnItemScrollListener();

return;

} else if (mItemCount != mAdapter.getCount()) {

throw new IllegalStateException("The content of the adapter has changed but "

+ "ListView did not receive a notification. Make sure the content of "

+ "your adapter is not modified from a background thread, but only "

+ "from the UI thread. [in ListView(" + getId() + ", " + getClass()

+ ") with Adapter(" + mAdapter.getClass() + ")]");

}

setSelectedPositionInt(mNextSelectedPosition);

// Pull all children into the RecycleBin.

// These views will be reused if possible

final int firstPosition = mFirstPosition;

final RecycleBin recycleBin = mRecycler;

// reset the focus restoration

View focusLayoutRestoreDirectChild = null;

// Don't put header or footer views into the Recycler. Those are

// already cached in mHeaderViews;

if (dataChanged) {

for (int i = 0; i < childCount; i++) {

recycleBin.addScrapView(getChildAt(i));

if (ViewDebug.TRACE_RECYCLER) {

ViewDebug.trace(getChildAt(i),

ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);

}

}

} else {

recycleBin.fillActiveViews(childCount, firstPosition);

}

// take focus back to us temporarily to avoid the eventual

// call to clear focus when removing the focused child below

// from messing things up when ViewRoot assigns focus back

// to someone else

final View focusedChild = getFocusedChild();

if (focusedChild != null) {

// TODO: in some cases focusedChild.getParent() == null

// we can remember the focused view to restore after relayout if the

// data hasn't changed, or if the focused position is a header or footer

if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {

focusLayoutRestoreDirectChild = focusedChild;

// remember the specific view that had focus

focusLayoutRestoreView = findFocus();

if (focusLayoutRestoreView != null) {

// tell it we are going to mess with it

focusLayoutRestoreView.onStartTemporaryDetach();

}

}

requestFocus();

}

// Clear out old views

detachAllViewsFromParent();

switch (mLayoutMode) {

case LAYOUT_SET_SELECTION:

if (newSel != null) {

sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);

} else {

sel = fillFromMiddle(childrenTop, childrenBottom);

}

break;

case LAYOUT_SYNC:

sel = fillSpecific(mSyncPosition, mSpecificTop);

break;

case LAYOUT_FORCE_BOTTOM:

sel = fillUp(mItemCount - 1, childrenBottom);

adjustViewsUpOrDown();

break;

case LAYOUT_FORCE_TOP:

mFirstPosition = 0;

sel = fillFromTop(childrenTop);

adjustViewsUpOrDown();

break;

case LAYOUT_SPECIFIC:

sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);

break;

case LAYOUT_MOVE_SELECTION:

sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);

break;

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;

}

// Flush any cached views that did not get reused above

recycleBin.scrapActiveViews();

if (sel != null) {

// the current selected item should get focus if items

// are focusable

if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {

final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&

focusLayoutRestoreView.requestFocus()) || sel.requestFocus();

if (!focusWasTaken) {

// selected item didn't take focus, fine, but still want

// to make sure something else outside of the selected view

// has focus

final View focused = getFocusedChild();

if (focused != null) {

focused.clearFocus();

}

positionSelector(sel);

} else {

sel.setSelected(false);

mSelectorRect.setEmpty();

}

} else {

positionSelector(sel);

}

mSelectedTop = sel.getTop();

} else {

if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {

View child = getChildAt(mMotionPosition - mFirstPosition);

if (child != null) positionSelector(child);

} else {

mSelectedTop = 0;

mSelectorRect.setEmpty();

}

// even if there is not selected position, we may need to restore

// focus (i.e. something focusable in touch mode)

if (hasFocus() && focusLayoutRestoreView != null) {

focusLayoutRestoreView.requestFocus();

}

}

// tell focus view we are done mucking with it, if it is still in

// our view hierarchy.

if (focusLayoutRestoreView != null

&& focusLayoutRestoreView.getWindowToken() != null) {

focusLayoutRestoreView.onFinishTemporaryDetach();

}

mLayoutMode = LAYOUT_NORMAL;

mDataChanged = false;

mNeedSync = false;

setNextSelectedPositionInt(mSelectedPosition);

updateScrollIndicators();

if (mItemCount > 0) {

checkSelectionChanged();

}

invokeOnItemScrollListener();

} finally {

if (!blockLayoutRequests) {

mBlockLayoutRequests = false;

}

}

}

fillDown()方法,代码如下所示:

[java] view
plaincopy

/**

* Fills the list from pos down to the 
1bdba
;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 = (getBottom() - getTop()) - 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;

}

makeAndAddView()方法代码如下所示:

[java] view
plaincopy

/**

* 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;

if (!mDataChanged) {

// Try to use an exsiting view for this position

child = mRecycler.getActiveView(position);

if (child != null) {

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

}

getView()方法接受的三个参数,第一个参数position代表当前子元素的的位置,我们可以通过具体的位置来获取与其相关的数据。第二个参数convertView,刚才传入的是null,说明没有convertView可以利用,因此我们会调用LayoutInflater的inflate()方法来去加载一个布局。接下来会对这个view进行一些属性和值的设定,最后将view返回。

那么这个View也会作为obtainView()的结果进行返回,并最终传入到setupChild()方法当中。其实也就是说,第一次layout过程当中,所有的子View都是调用LayoutInflater的inflate()方法加载出来的,这样就会相对比较耗时,但是不用担心,后面就不会再有这种情况了,那么我们继续往下看:

[java] view
plaincopy

/**

* 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 (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);

}

}

在编写过程中,也有出现乱码,错误,后来在不断试验下改正错误。最后结果还是可以运行的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: