- 浏览: 469161 次
文章分类
最新评论
-
hxqneuq2012:
感谢博主分享。不过,文章贴了2遍哈。看着怪怪的: ...
Android高手进阶教程(二十一)---Android WebView的缓存!!! -
malen:
不错。试试去
strings.xml文件中提供占位符来格式化数据
android源码解析--ListView(上)
每当自己想要学一点东西的时候,就快要到10点半了,还有十几天就要回家,总是有些兴奋,今天得知娜姐进了澳网的女单决赛,周末的时候可以看一下,希望能在澳网女单决赛之前写好这篇博客,好了,不废话,开始正题。
在一年多以前,写过一篇关于listview方法的文章:listView属性及方法小析,现在再重新看下其源码。
在listview的源码中,在46行有一个小的主意事项:
/* * Implementation Notes: * * Some terminology: * * index - index of the items that are currently visible * position - index of the items in the cursor */
就是简单的说明下index和position这两个变量名,index表示现在可视的部分的索引值,position是指在cursor中的索引值。
看下类说明:
A view that shows items in a vertically scrolling list. The items come from the ListAdapter associated with this view.
一个通过竖直滚动条显示其条目的列表视图,与视图关联的条目来自于ListAdapter。
入门指南:http://developer.android.com/guide/topics/ui/layout/listview.html。
@RemoteView public class ListView extends AbsListView {看一下类,头上有@RemoteView,加上这个标志,就说明该View能作为RemoteView使用,即可以在android widget上面使用listview,在android早起版本中,好像listview是不可以的。
看下其静态变量:
/** * Used to indicate a no preference for a position type. */ static final int NO_POSITION = -1;用于表示一个没有任何偏好的位置类型。
私有静态变量:
/** * When arrow scrolling, ListView will never scroll more than this factor * times the height of the list. */ private static final float MAX_SCROLL_FACTOR = 0.33f; /** * When arrow scrolling, need a certain amount of pixels to preview next * items. This is usually the fading edge, but if that is small enough, * we want to make sure we preview at least this many pixels. */ private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
MAX_SCROLL_FACTOR:当箭头滚动,ListView中永远不会比这个因子乘以列表的高度滚动更多。
MIN_SCROLL_PREVIEW_PIXELS:当箭头滚动,需要有一定的像素预览下一个项目。这通常是衰减的边缘,但如果足够小,我们要确保我们至少预览的像素数。
在看下其内部类:
/** * 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; }
固定视图的实体类,表示一个list中的固定视图,如listview中在顶部的header或者底部的footer。其变量含义view代表要添加到listview中的视图,data:视图中的数据,isSelectable:是否可以选中。
接着看ListView类中的变量:
private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList(); private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList(); Drawable mDivider; int mDividerHeight; Drawable mOverScrollHeader; Drawable mOverScrollFooter; private boolean mIsCacheColorOpaque; private boolean mDividerIsOpaque; private boolean mHeaderDividersEnabled; private boolean mFooterDividersEnabled; private boolean mAreAllItemsSelectable = true; private boolean mItemsCanFocus = false; // used for temporary calculations. private final Rect mTempRect = new Rect(); private Paint mDividerPaint; // the single allocated result per list view; kinda cheesey but avoids // allocating these thingies too often. private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult(); // Keeps focused children visible through resizes private FocusSelector mFocusSelector;
头两个参数是用于存储固定视图的列表。
mDivider:分割线。
mOverScrollHeader:当滑动到顶部,再下拉看到的视图。
mOverScrollFooter:当滑动到底部,再上拉看到的视图。
mIsCacheColorOpaque:缓存颜色是否透明。
mDividerIsOpaque:分割线是否透明。
mHeaderDividersEnabled:header分割线是否可用。
mFooterDividersEnabled:footer分割线是否可用。
AreAllItemsSelectable:是否所有的条目都可以被选中。
mItemsCanFocus:是否所有的条目都可以获取焦点。
mTempRect:一个用于临时技术的矩形。
mDividerPaint:分割线画笔。
mArrowScrollFocusResult:listview中内部类,用于保留箭头滑动时,获取焦点区域,避免焦点切换太频繁。
mFocusSelector:获取焦点的条目(通过调整,使条目保持焦点)
先看下ArrowScrollFocusResult和FocusSelector两个内部类:
/** * Holds results of focus aware arrow scrolling. */ static private class ArrowScrollFocusResult { private int mSelectedPosition; 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; } }
里面有两个参数,一个是现在选中条目位置,一个是滑动的item个数。通过这个类,就可以很方便的获取焦点和切换焦点了。
FocusSelector:
private class FocusSelector implements Runnable { private int mPosition; private int mPositionTop; public FocusSelector setup(int position, int top) { mPosition = position; mPositionTop = top; return this; } public void run() { setSelectionFromTop(mPosition, mPositionTop); } }
一个线程类,通过设置,可以不断的调整其获取焦点的条目。
里面调用到了1905行的setSelectionFromTop这个方法:
/** * Sets the selected item and positions the selection y pixels from the top edge * of the ListView. (If in touch mode, the item will not be selected but it will * still be positioned appropriately.) * * @param position Index (starting at 0) of the data item to be selected. * @param y The distance from the top edge of the ListView (plus padding) that the * item will be positioned. */ public void setSelectionFromTop(int position, int y) { if (mAdapter == null) { return; } if (!isInTouchMode()) { position = lookForSelectablePosition(position, true); if (position >= 0) { setNextSelectedPositionInt(position); } } else { mResurrectToPosition = position; } if (position >= 0) { mLayoutMode = LAYOUT_SPECIFIC; mSpecificTop = mListPadding.top + y; if (mNeedSync) { mSyncPosition = position; mSyncRowId = mAdapter.getItemId(position); } requestLayout(); } }
通过listview顶部边缘的对应y像素和position来设置选中项(在滑动状态下,不能被选中,但是会滑动到合适位置)
再看ListView的构造函数:
public ListView(Context context) { this(context, null); } public ListView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.listViewStyle); } public ListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ListView, defStyle, 0); CharSequence[] entries = a.getTextArray( com.android.internal.R.styleable.ListView_entries); if (entries != null) { setAdapter(new ArrayAdapter<CharSequence>(context, com.android.internal.R.layout.simple_list_item_1, entries)); } final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider); if (d != null) { // If a divider is specified use its intrinsic height for divider height setDivider(d); } final Drawable osHeader = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollHeader); if (osHeader != null) { setOverscrollHeader(osHeader); } final Drawable osFooter = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollFooter); if (osFooter != null) { setOverscrollFooter(osFooter); } // Use the height specified, zero being the default final int dividerHeight = a.getDimensionPixelSize( com.android.internal.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(); }
主要是从TypeArray里面取出各种属性进行设置。
/** * @return The maximum amount a list view will scroll in response to * an arrow event. */ public int getMaxScrollAmount() { return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop)); }相应一个箭头事件,ListView可以滑动的最大距离。关于这个方法,询问高手以后,可以理解为一个箭头((按键事件)非触摸事件(touch)),listivew作出响应,滑动的最大距离。比如说在android电视中,使用遥控器操作listview,按向下箭头,listview滑动距离。
下面是adjustViewsUpOrDown():
/** * Make sure views are touching the top or bottom edge, as appropriate for * our gravity */ private void adjustViewsUpOrDown() { final int childCount = getChildCount(); int delta; if (childCount > 0) { View child; if (!mStackFromBottom) { // Uh-oh -- we came up short. Slide all views up to make them // align with the top child = getChildAt(0); delta = child.getTop() - mListPadding.top; if (mFirstPosition != 0) { // It's OK to have some space above the first item if it is // part of the vertical spacing delta -= mDividerHeight; } if (delta < 0) { // We only are looking to see if we are too low, not too high delta = 0; } } else { // we are too high, slide all views down to align with bottom child = getChildAt(childCount - 1); delta = child.getBottom() - (getHeight() - mListPadding.bottom); if (mFirstPosition + childCount < mItemCount) { // It's OK to have some space below the last item if it is // part of the vertical spacing delta += mDividerHeight; } if (delta > 0) { delta = 0; } } if (delta != 0) { offsetChildrenTopAndBottom(-delta); } } }
调整在ListView顶部或底部的view以适应我们的视觉感应。
mStackFromBottom:这个参数是来自父类AbsListView
/** * Indicates whether the list is stacked from the bottom edge or * the top edge. */
使列表与底部对齐或者与顶部对齐。
里面调用的offsetChildrenTopAndBottom()方法(来自父类ViewGroup):
/** * Offset the vertical location of all children of this view by the specified number of pixels. * * @param offset the number of pixels to offset * * @hide */ public void offsetChildrenTopAndBottom(int offset) { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View v = children[i]; v.mTop += offset; v.mBottom += offset; } }
调整垂直方向上面所有子视图位置。
添加HeaderView:
/** * Add a fixed view to appear at the top of the list. If addHeaderView is * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> * NOTE: Call this before calling setAdapter. This is so ListView can wrap * the supplied cursor with one that will also account for header and footer * views. * * @param v The view to add. * @param data Data to associate with this view * @param isSelectable whether the item is selectable */ public void addHeaderView(View v, Object data, boolean isSelectable) { if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) { throw new IllegalStateException( "Cannot add header view to list -- setAdapter has already been called."); } FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); // in the case of re-adding a header view, or adding one later on, // we need to notify the observer if (mAdapter != null && mDataSetObserver != null) { mDataSetObserver.onChanged(); } } /** * Add a fixed view to appear at the top of the list. If addHeaderView is * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> * NOTE: Call this before calling setAdapter. This is so ListView can wrap * the supplied cursor with one that will also account for header and footer * views. * * @param v The view to add. */ public void addHeaderView(View v) { addHeaderView(v, null, true); }
为ListView顶部添加一个固定的View,如果HeaderView多于一个,会按照添加的顺序进行排列。
287行,获取添加的HeaderView的个数:
@Override public int getHeaderViewsCount() { return mHeaderViewInfos.size(); }
删除在以前添加的HeaderView:
/** * Removes a previously-added header view. * * @param v The view to remove * @return true if the view was removed, false if the view was not a header * view */ public boolean removeHeaderView(View v) { if (mHeaderViewInfos.size() > 0) { boolean result = false; if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) { if (mDataSetObserver != null) { mDataSetObserver.onChanged(); } result = true; } removeFixedViewInfo(v, mHeaderViewInfos); return result; } return false; }
里面调用了removeFixedViewInfo:
private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) { int len = where.size(); for (int i = 0; i < len; ++i) { FixedViewInfo info = where.get(i); if (info.view == v) { where.remove(i); break; } } }
找到对应view,从列表里面去除。
添加FooterView:
/** * Add a fixed view to appear at the bottom of the list. If addFooterView is * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> * NOTE: Call this before calling setAdapter. This is so ListView can wrap * the supplied cursor with one that will also account for header and footer * views. * * @param v The view to add. * @param data Data to associate with this view * @param isSelectable true if the footer view can be selected */ public void addFooterView(View v, Object data, boolean isSelectable) { // NOTE: do not enforce the adapter being null here, since unlike in // addHeaderView, it was never enforced here, and so existing apps are // relying on being able to add a footer and then calling setAdapter to // force creation of the HeaderViewListAdapter wrapper FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mFooterViewInfos.add(info); // in the case of re-adding a footer view, or adding one later on, // we need to notify the observer if (mAdapter != null && mDataSetObserver != null) { mDataSetObserver.onChanged(); } } /** * Add a fixed view to appear at the bottom of the list. If addFooterView is called more * than once, the views will appear in the order they were added. Views added using * this call can take focus if they want. * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied * cursor with one that will also account for header and footer views. * * * @param v The view to add. */ public void addFooterView(View v) { addFooterView(v, null, true); }
可参考上面对addHeaderView的解释。
获取添加的FooterView的个数:
@Override public int getFooterViewsCount() { return mFooterViewInfos.size(); }
删除相对应的FooterView:
/** * Removes a previously-added footer view. * * @param v The view to remove * @return * true if the view was removed, false if the view was not a footer view */ public boolean removeFooterView(View v) { if (mFooterViewInfos.size() > 0) { boolean result = false; if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) { if (mDataSetObserver != null) { mDataSetObserver.onChanged(); } result = true; } removeFixedViewInfo(v, mFooterViewInfos); return result; } return false; }
获取ListView正在使用的adapter:
/** * Returns the adapter currently in use in this ListView. The returned adapter * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but * might be a {@link WrapperListAdapter}. * * @return The adapter currently used to display data in this ListView. * * @see #setAdapter(ListAdapter) */ @Override public ListAdapter getAdapter() { return mAdapter; }
返回的adapter不一定是通过setAdapter方法传入的adapter,有可能是一个WrapperListAdapter。
setRemoteViewAdapter():
/** * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService * through the specified intent. * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to. */ @android.view.RemotableViewMethod public void setRemoteViewsAdapter(Intent intent) { super.setRemoteViewsAdapter(intent); }在上面提到,listview可以作为一个remoteView在widget中使用,该方法是设定一个intent,listview的adapter通过其与RemoteViewService通信。
为listview添加数据:
/** * Sets the data behind this ListView. * * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter}, * depending on the ListView features currently in use. For instance, adding * headers and/or footers will cause the adapter to be wrapped. * * @param adapter The ListAdapter which is responsible for maintaining the * data backing this list and for producing a view to represent an * item in that data set. * * @see #getAdapter() */ @Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position); setNextSelectedPositionInt(position); if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } requestLayout(); }通过setAdapter方法添加的adapter根据当前ListView的使用情况可能被装饰为一个WrapperListAdapter,比如说添加一个HeaderView或者FooterView。
在该方法中,先把以前的数据和观察者去掉,然后再重新设置各种参数。
里面调用到了resetList()方法:
/** * The list is empty. Clear everything out. */ @Override void resetList() { // The parent's resetList() will remove all views from the layout so we need to // cleanup the state of our footers and headers clearRecycledState(mHeaderViewInfos); clearRecycledState(mFooterViewInfos); super.resetList(); mLayoutMode = LAYOUT_NORMAL; }
这里面又调用了clearRecycleState()方法:
private void clearRecycledState(ArrayList<FixedViewInfo> infos) { if (infos != null) { final int count = infos.size(); for (int i = 0; i < count; i++) { final View child = infos.get(i).view; final LayoutParams p = (LayoutParams) child.getLayoutParams(); if (p != null) { p.recycledHeaderFooter = false; } } } }
清除回收状态。
requestChildRectangleOnScreen:
@Override public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { int rectTopWithinChild = rect.top; // offset so rect is in coordinates of the this view rect.offset(child.getLeft(), child.getTop()); rect.offset(-child.getScrollX(), -child.getScrollY()); final int height = getHeight(); int listUnfadedTop = getScrollY(); int listUnfadedBottom = listUnfadedTop + height; final int fadingEdge = getVerticalFadingEdgeLength(); if (showingTopFadingEdge()) { // leave room for top fading edge as long as rect isn't at very top if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) { listUnfadedTop += fadingEdge; } } int childCount = getChildCount(); int bottomOfBottomChild = getChildAt(childCount - 1).getBottom(); if (showingBottomFadingEdge()) { // leave room for bottom fading edge as long as rect isn't at very bottom if ((mSelectedPosition < mItemCount - 1) || (rect.bottom < (bottomOfBottomChild - fadingEdge))) { listUnfadedBottom -= fadingEdge; } } int scrollYDelta = 0; if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) { // need to MOVE DOWN to get it in view: move down just enough so // that the entire rectangle is in view (or at least the first // screen size chunk). if (rect.height() > height) { // just enough to get screen size chunk on scrollYDelta += (rect.top - listUnfadedTop); } else { // get entire rect at bottom of screen scrollYDelta += (rect.bottom - listUnfadedBottom); } // make sure we aren't scrolling beyond the end of our children int distanceToBottom = bottomOfBottomChild - listUnfadedBottom; scrollYDelta = Math.min(scrollYDelta, distanceToBottom); } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) { // need to MOVE UP to get it in view: move up just enough so that // entire rectangle is in view (or at least the first screen // size chunk of it). if (rect.height() > height) { // screen size chunk scrollYDelta -= (listUnfadedBottom - rect.bottom); } else { // entire rect at top scrollYDelta -= (listUnfadedTop - rect.top); } // make sure we aren't scrolling any further than the top our children int top = getChildAt(0).getTop(); int deltaToTop = top - listUnfadedTop; scrollYDelta = Math.max(scrollYDelta, deltaToTop); } final boolean scroll = scrollYDelta != 0; if (scroll) { scrollListItemsBy(-scrollYDelta); positionSelector(INVALID_POSITION, child); mSelectedTop = child.getTop(); invalidate(); } return scroll; }
当组里的某个子项需要被定位在屏幕的某个矩形范围时,调用此方法。
里面调用了showingTopFadingEdge()和showingBottomFadingEdge():
/** * @return Whether the list needs to show the top fading edge */ private boolean showingTopFadingEdge() { final int listTop = mScrollY + mListPadding.top; return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop); } /** * @return Whether the list needs to show the bottom fading edge */ private boolean showingBottomFadingEdge() { final int childCount = getChildCount(); final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom(); final int lastVisiblePosition = mFirstPosition + childCount - 1; final int listBottom = mScrollY + getHeight() - mListPadding.bottom; return (lastVisiblePosition < mItemCount - 1) || (bottomOfBottomChild < listBottom); }
是否显示上边缘或者下边缘。
下面这个方法是默认访问修饰符(同一包中类可以访问):
/** * {@inheritDoc} */ @Override void fillGap(boolean down) { final int count = getChildCount(); if (down) { int paddingTop = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingTop = getListPaddingTop(); } final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : paddingTop; fillDown(mFirstPosition + count, startOffset); correctTooHigh(getChildCount()); } else { int paddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingBottom = getListPaddingBottom(); } final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - paddingBottom; fillUp(mFirstPosition - 1, startOffset); correctTooLow(getChildCount()); } }
根据猜测,该方法也是用于调整listView中item的位置,如果滑动到第一个item或最后一个item时会出现反弹,调用了fillDown()、fillUp()、correctTooHigh()和correctTooLow()方法。
fillDown:
/** * 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; }
填充从pos到list底部所有的item。里面调用到了makeAndAddView方法:
/** * 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 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; }
获取视图填充到列表的item中去,视图可以是从未使用过的视图转换过来,也可以使从回收站复用的视图。在该方法中,先从查找是否有可重用视图,如果有,使用可重用视图。然后
// Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap);
通过obtainView方法获取一个view(有可能是从未使用视图转换过来(obtainView方法是在AbsListView方法中定义)),再重新测量和定位View。调用到了setupChild方法:
/** * 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 (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 (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); } if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition) != position) { child.jumpDrawablesToCurrentState(); } }
添加视图作为一个子视图,确保测量(如果需要的话)和视图在父视图中正确定位。
再接回到fillUp:
/** * Fills the list from pos up to the top of the list view. * * @param pos The first position to put in the list * * @param nextBottom The location where the bottom of the item associated * with pos should be drawn * * @return The view that is currently selected */ private View fillUp(int pos, int nextBottom) { View selectedView = null; int end = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end = mListPadding.top; } while (nextBottom > end && pos >= 0) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected); nextBottom = child.getTop() - mDividerHeight; if (selected) { selectedView = child; } pos--; } mFirstPosition = pos + 1; return selectedView; }
其填充原理同上面fillDown。
还有用到的correctTooHigh():
/** * Check if we have dragged the bottom of the list too high (we have pushed the * top element off the top of the screen when we did not need to). Correct by sliding * everything back down. * * @param childCount Number of children */ private void correctTooHigh(int childCount) { // First see if the last item is visible. If it is not, it is OK for the // top of the list to be pushed up. int lastPosition = mFirstPosition + childCount - 1; if (lastPosition == mItemCount - 1 && childCount > 0) { // Get the last child ... final View lastChild = getChildAt(childCount - 1); // ... and its bottom edge final int lastBottom = lastChild.getBottom(); // This is bottom of our drawable area final int end = (mBottom - mTop) - mListPadding.bottom; // This is how far the bottom edge of the last view is from the bottom of the // drawable area int bottomOffset = end - lastBottom; View firstChild = getChildAt(0); final int firstTop = firstChild.getTop(); // Make sure we are 1) Too high, and 2) Either there are more rows above the // first row or the first row is scrolled off the top of the drawable area if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) { if (mFirstPosition == 0) { // Don't pull the top too far down bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop); } // Move everything down offsetChildrenTopAndBottom(bottomOffset); if (mFirstPosition > 0) { // Fill the gap that was opened above mFirstPosition with more rows, if // possible fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight); // Close up the remaining gap adjustViewsUpOrDown(); } } } }
该方法的作用是如果拖动listview底部过高,使其能够正确的反弹回来。里面调用到了前面讲到的fillup和adjustViewUpOrDown方法。
与其对应的correctTooLow,不做过多解释。
/** * Check if we have dragged the bottom of the list too low (we have pushed the * bottom element off the bottom of the screen when we did not need to). Correct by sliding * everything back up. * * @param childCount Number of children */ private void correctTooLow(int childCount) { // First see if the first item is visible. If it is not, it is OK for the // bottom of the list to be pushed down. if (mFirstPosition == 0 && childCount > 0) { // Get the first child ... final View firstChild = getChildAt(0); // ... and its top edge final int firstTop = firstChild.getTop(); // This is top of our drawable area final int start = mListPadding.top; // This is bottom of our drawable area final int end = (mBottom - mTop) - mListPadding.bottom; // This is how far the top edge of the first view is from the top of the // drawable area int topOffset = firstTop - start; View lastChild = getChildAt(childCount - 1); final int lastBottom = lastChild.getBottom(); int lastPosition = mFirstPosition + childCount - 1; // Make sure we are 1) Too low, and 2) Either there are more rows below the // last row or the last row is scrolled off the bottom of the drawable area if (topOffset > 0) { if (lastPosition < mItemCount - 1 || lastBottom > end) { if (lastPosition == mItemCount - 1) { // Don't pull the bottom too far up topOffset = Math.min(topOffset, lastBottom - end); } // Move everything up offsetChildrenTopAndBottom(-topOffset); if (lastPosition < mItemCount - 1) { // Fill the gap that was opened below the last position with more rows, if // possible fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight); // Close up the remaining gap adjustViewsUpOrDown(); } } else if (lastPosition == mItemCount - 1) { adjustViewsUpOrDown(); } } } }
再重新返回到718行:
/** * Fills the list from top to bottom, starting with mFirstPosition * * @param nextTop The location where the top of the first item should be * drawn * * @return The view that is currently selected */ private View fillFromTop(int nextTop) { mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); if (mFirstPosition < 0) { mFirstPosition = 0; } return fillDown(mFirstPosition, nextTop); }
填充listview从头部到底部,方法内主要是确定MFirstPosition的值,然后调用fillDown方法。
fillFromMiddle:跳转list,是其选中item居中。
/** * Put mSelectedPosition in the middle of the screen and then build up and * down from there. This method forces mSelectedPosition to the center. * * @param childrenTop Top of the area in which children can be drawn, as * measured in pixels * @param childrenBottom Bottom of the area in which children can be drawn, * as measured in pixels * @return Currently selected view */ private View fillFromMiddle(int childrenTop, int childrenBottom) { int height = childrenBottom - childrenTop; int position = reconcileSelectedPosition(); View sel = makeAndAddView(position, childrenTop, true, mListPadding.left, true); mFirstPosition = position; int selHeight = sel.getMeasuredHeight(); if (selHeight <= height) { sel.offsetTopAndBottom((height - selHeight) / 2); } fillAboveAndBelow(sel, position); if (!mStackFromBottom) { correctTooHigh(getChildCount()); } else { correctTooLow(getChildCount()); } return sel; }里面调用的reconcileSelectedPosition也是来自父类AbsListView,将在该类中详解。
里面调用的fillAboveAndBelow:填充选中项的上下区域(即上下视图)
/** * Once the selected view as been placed, fill up the visible area above and * below it. * * @param sel The selected view * @param position The position corresponding to sel */ private void fillAboveAndBelow(View sel, int position) { final int dividerHeight = mDividerHeight; if (!mStackFromBottom) { fillUp(position - 1, sel.getTop() - dividerHeight); adjustViewsUpOrDown(); fillDown(position + 1, sel.getBottom() + dividerHeight); } else { fillDown(position + 1, sel.getBottom() + dividerHeight); adjustViewsUpOrDown(); fillUp(position - 1, sel.getTop() - dividerHeight); } }
fillFromSelection方法:
/** * Fills the grid based on positioning the new selection at a specific * location. The selection may be moved so that it does not intersect the * faded edges. The grid is then filled upwards and downwards from there. * * @param selectedTop Where the selected item should be * @param childrenTop Where to start drawing children * @param childrenBottom Last pixel where children can be drawn * @return The view that currently has selection */ private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) { int fadingEdgeLength = getVerticalFadingEdgeLength(); final int selectedPosition = mSelectedPosition; View sel; final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, selectedPosition); final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength, selectedPosition); sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true); // Some of the newly selected item extends below the bottom of the list if (sel.getBottom() > bottomSelectionPixel) { // Find space available above the selection into which we can scroll // upwards final int spaceAbove = sel.getTop() - topSelectionPixel; // Find space required to bring the bottom of the selected item // fully into view final int spaceBelow = sel.getBottom() - bottomSelectionPixel; final int offset = Math.min(spaceAbove, spaceBelow); // Now offset the selected item to get it into view sel.offsetTopAndBottom(-offset); } else if (sel.getTop() < topSelectionPixel) { // Find space required to bring the top of the selected item fully // into view final int spaceAbove = topSelectionPixel - sel.getTop(); // Find space available below the selection into which we can scroll // downwards final int spaceBelow = bottomSelectionPixel - sel.getBottom(); final int offset = Math.min(spaceAbove, spaceBelow); // Offset the selected item to get it into view sel.offsetTopAndBottom(offset); } // Fill in views above and below fillAboveAndBelow(sel, selectedPosition); if (!mStackFromBottom) { correctTooHigh(getChildCount()); } else { correctTooLow(getChildCount()); } return sel; }
从一个特定的位置(传入的参数selectedTop)使用一个网格状填充,选中项有可能移动而不与边界相交,然后网格填充上至childrenTop,下至childrenBottom的区域。里面调用getTopSelectionPixel和getBottomSelectionPixel方法。
/** * Calculate the bottom-most pixel we can draw the selection into * * @param childrenBottom Bottom pixel were children can be drawn * @param fadingEdgeLength Length of the fading edge in pixels, if present * @param selectedPosition The position that will be selected * @return The bottom-most pixel we can draw the selection into */ private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength, int selectedPosition) { int bottomSelectionPixel = childrenBottom; if (selectedPosition != mItemCount - 1) { bottomSelectionPixel -= fadingEdgeLength; } return bottomSelectionPixel; }
计算可以在底部最多绘制多少像素。
getTopSelectionPixel:计算可以在底部最多绘制多少像素。
/** * Smoothly scroll to the specified adapter position. The view will * scroll such that the indicated position is displayed. * @param position Scroll to this adapter position. */ @android.view.RemotableViewMethod public void smoothScrollToPosition(int position) { super.smoothScrollToPosition(position); }
平滑的滑动到指定位置,继承自父类,将在父类AbsListView中进行说明。
/** * Smoothly scroll to the specified adapter position offset. The view will * scroll such that the indicated position is displayed. * @param offset The amount to offset from the adapter position to scroll to. */ @android.view.RemotableViewMethod public void smoothScrollByOffset(int offset) { super.smoothScrollByOffset(offset); }
平滑的滚动offset像素的偏移。
moveSelection:从一个旧的选中项移动到新的选中项。
/** * Fills the list based on positioning the new selection relative to the old * selection. The new selection will be placed at, above, or below the * location of the new selection depending on how the selection is moving. * The selection will then be pinned to the visible part of the screen, * excluding the edges that are faded. The list is then filled upwards and * downwards from there. * * @param oldSel The old selected view. Useful for trying to put the new * selection in the same place * @param newSel The view that is to become selected. Useful for trying to * put the new selection in the same place * @param delta Which way we are moving * @param childrenTop Where to start drawing children * @param childrenBottom Last pixel where children can be drawn * @return The view that currently has selection */ private View moveSelection(View oldSel, View newSel, int delta, int childrenTop, int childrenBottom) { int fadingEdgeLength = getVerticalFadingEdgeLength(); final int selectedPosition = mSelectedPosition; View sel; final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, selectedPosition); final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength, selectedPosition); if (delta > 0) { /* * Case 1: Scrolling down. */ /* * Before After * | | | | * +-------+ +-------+ * | A | | A | * | 1 | => +-------+ * +-------+ | B | * | B | | 2 | * +-------+ +-------+ * | | | | * * Try to keep the top of the previously selected item where it was. * oldSel = A * sel = B */ // Put oldSel (A) where it belongs oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true, mListPadding.left, false); final int dividerHeight = mDividerHeight; // Now put the new selection (B) below that sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true, mListPadding.left, true); // Some of the newly selected item extends below the bottom of the list if (sel.getBottom() > bottomSelectionPixel) { // Find space available above the selection into which we can scroll upwards int spaceAbove = sel.getTop() - topSelectionPixel; // Find space required to bring the bottom of the selected item fully into view int spaceBelow = sel.getBottom() - bottomSelectionPixel; // Don't scroll more than half the height of the list int halfVerticalSpace = (childrenBottom - childrenTop) / 2; int offset = Math.min(spaceAbove, spaceBelow); offset = Math.min(offset, halfVerticalSpace); // We placed oldSel, so offset that item oldSel.offsetTopAndBottom(-offset); // Now offset the selected item to get it into view sel.offsetTopAndBottom(-offset); } // Fill in views above and below if (!mStackFromBottom) { fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight); adjustViewsUpOrDown(); fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight); } else { fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight); adjustViewsUpOrDown(); fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight); } } else if (delta < 0) { /* * Case 2: Scrolling up. */ /* * Before After * | | | | * +-------+ +-------+ * | A | | A | * +-------+ => | 1 | * | B | +-------+ * | 2 | | B | * +-------+ +-------+ * | | | | * * Try to keep the top of the item about to become selected where it was. * newSel = A * olSel = B */ if (newSel != null) { // Try to position the top of newSel (A) where it was before it was selected sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left, true); } else { // If (A) was not on screen and so did not have a view, position // it above the oldSel (B) sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left, true); } // Some of the newly selected item extends above the top of the list if (sel.getTop() < topSelectionPixel) { // Find space required to bring the top of the selected item fully into view int spaceAbove = topSelectionPixel - sel.getTop(); // Find space available below the selection into which we can scroll downwards int spaceBelow = bottomSelectionPixel - sel.getBottom(); // Don't scroll more than half the height of the list int halfVerticalSpace = (childrenBottom - childrenTop) / 2; int offset = Math.min(spaceAbove, spaceBelow); offset = Math.min(offset, halfVerticalSpace); // Offset the selected item to get it into view sel.offsetTopAndBottom(offset); } // Fill in views above and below fillAboveAndBelow(sel, selectedPosition); } else { int oldTop = oldSel.getTop(); /* * Case 3: Staying still */ sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true); // We're staying still... if (oldTop < childrenTop) { // ... but the top of the old selection was off screen. // (This can happen if the data changes size out from under us) int newBottom = sel.getBottom(); if (newBottom < childrenTop + 20) { // Not enough visible -- bring it onscreen sel.offsetTopAndBottom(childrenTop - sel.getTop()); } } // Fill in views above and below fillAboveAndBelow(sel, selectedPosition); } return sel; }
当Size变化时:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (getChildCount() > 0) { View focusedChild = getFocusedChild(); if (focusedChild != null) { final int childPosition = mFirstPosition + indexOfChild(focusedChild); final int childBottom = focusedChild.getBottom(); final int offset = Math.max(0, childBottom - (h - mPaddingTop)); final int top = focusedChild.getTop() - offset; if (mFocusSelector == null) { mFocusSelector = new FocusSelector(); } post(mFocusSelector.setup(childPosition, top)); } } super.onSizeChanged(w, h, oldw, oldh); }
测量控件大小位置,为绘制View做准备:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Sets up mListPadding super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); 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); measureScrapChild(child, 0, widthMeasureSpec); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (recycleOnMeasure() && mRecycler.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { mRecycler.addScrapView(child, -1); } } 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; }
里面调用到measureScrapChild,测量废弃的子View:
private void measureScrapChild(View child, int position, int widthMeasureSpec) { LayoutParams p = (LayoutParams) child.getLayoutParams(); if (p == null) { p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); child.setLayoutParams(p); } p.viewType = mAdapter.getItemViewType(position); p.forceAdd = true; int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, 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); }
测量时是否回收资源:
/** * @return True to recycle the views used to measure this ListView in * UNSPECIFIED/AT_MOST modes, false otherwise. * @hide */ @ViewDebug.ExportedProperty(category = "list") protected boolean recycleOnMeasure() { return true; }
在UNSPECIFIED或者AT_MOST模式下返回true(即回收),其他模式下返回false。
里面还调用了measureHeightOfChildren:测量子控件的高度
/** * Measures the height of the given range of children (inclusive) and * returns the height with this ListView's padding and divider heights * included. If maxHeight is provided, the measuring will stop when the * current height reaches maxHeight. * * @param widthMeasureSpec The width measure spec to be given to a child's * {@link View#measure(int, int)}. * @param startPosition The position of the first child to be shown. * @param endPosition The (inclusive) position of the last child to be * shown. Specify {@link #NO_POSITION} if the last child should be * the last available child from the adapter. * @param maxHeight The maximum height that will be returned (if all the * children don't fit in this value, this value will be * returned). * @param disallowPartialChildPosition In general, whether the returned * height should only contain entire children. This is more * powerful--it is the first inclusive position at which partial * children will not be allowed. Example: it looks nice to have * at least 3 completely visible children, and in portrait this * will most likely fit; but in landscape there could be times * when even 2 children can not be completely shown, so a value * of 2 (remember, inclusive) would be good (assuming * startPosition is 0). * @return The height of this ListView with the given children. */ final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, final int maxHeight, int disallowPartialChildPosition) { final ListAdapter adapter = mAdapter; if (adapter == null) { return mListPadding.top + mListPadding.bottom; } // Include the padding of the list int returnedHeight = mListPadding.top + mListPadding.bottom; final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0; // The previous height value that was less than maxHeight and contained // no partial children int prevHeightWithoutPartialChild = 0; int i; View child; // mItemCount - 1 since endPosition parameter is inclusive endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; final AbsListView.RecycleBin recycleBin = mRecycler; final boolean recyle = recycleOnMeasure(); final boolean[] isScrap = mIsScrap; for (i = startPosition; i <= endPosition; ++i) { child = obtainView(i, isScrap); measureScrapChild(child, i, widthMeasureSpec); if (i > 0) { // Count the divider for all but one child returnedHeight += dividerHeight; } // Recycle the view before we possibly return from the method if (recyle && recycleBin.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { recycleBin.addScrapView(child, -1); } returnedHeight += child.getMeasuredHeight(); if (returnedHeight >= maxHeight) { // We went over, figure out which height to return. If returnedHeight > maxHeight, // then the i'th position did not fit completely. return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) && (i > disallowPartialChildPosition) // We've past the min pos && (prevHeightWithoutPartialChild > 0) // We have a prev height && (returnedHeight != maxHeight) // i'th child did not fit completely ? prevHeightWithoutPartialChild : maxHeight; } if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { prevHeightWithoutPartialChild = returnedHeight; } } // At this point, we went through the range of children, and they each // completely fit, so return the returnedHeight return returnedHeight; }
测量给定的子控件高度,返回的高度中包括ListView距离顶部和分割线高度,如果计算到的高度等于最大高度,则停止测量。
找到移动到的位置:
@Override int findMotionRow(int y) { int childCount = getChildCount(); if (childCount > 0) { if (!mStackFromBottom) { for (int i = 0; i < childCount; i++) { View v = getChildAt(i); if (y <= v.getBottom()) { return mFirstPosition + i; } } } else { for (int i = childCount - 1; i >= 0; i--) { View v = getChildAt(i); if (y >= v.getTop()) { return mFirstPosition + i; } } } } return INVALID_POSITION; }
/** * Put a specific item at a specific location on the screen and then build * up and down from there. * * @param position The reference view to use as the starting point * @param top Pixel offset from the top of this view to the top of the * reference view. * * @return The selected view, or null if the selected view is outside the * visible area. */ private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above; View below; final int dividerHeight = mDividerHeight; if (!mStackFromBottom) { above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } else if (above != null) { return above; } else { return below; } }
参数
position:指定位置,
top:从特定View顶部到ListView顶部距离
返回:选中的view,如果view不在可视范围内,则返回空。
layoutChild:为每个子控件布局。
@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 = mBottom - mTop - 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), firstPosition+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 ViewAncestor 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(INVALID_POSITION, sel); } else { sel.setSelected(false); mSelectorRect.setEmpty(); } } else { positionSelector(INVALID_POSITION, sel); } mSelectedTop = sel.getTop(); } else { if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null) positionSelector(mMotionPosition, 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; } } }
里面调用到了isDirectChildHeaderOrFooter方法:判断传入View是否是HeaderView或者FooterView。
/** * @param child a direct child of this list. * @return Whether child is a header or footer view. */ private boolean isDirectChildHeaderOrFooter(View child) { final ArrayList<FixedViewInfo> headers = mHeaderViewInfos; final int numHeaders = headers.size(); for (int i = 0; i < numHeaders; i++) { if (child == headers.get(i).view) { return true; } } final ArrayList<FixedViewInfo> footers = mFooterViewInfos; final int numFooters = footers.size(); for (int i = 0; i < numFooters; i++) { if (child == footers.get(i).view) { return true; } } return false; }
设置选中项:setSelection
/** * Sets the currently selected item. If in touch mode, the item will not be selected * but it will still be positioned appropriately. If the specified selection position * is less than 0, then the item at position 0 will be selected. * * @param position Index (starting at 0) of the data item to be selected. */ @Override public void setSelection(int position) { setSelectionFromTop(position, 0); }里面调用setSelectionFromTop方法:
/** * Sets the selected item and positions the selection y pixels from the top edge * of the ListView. (If in touch mode, the item will not be selected but it will * still be positioned appropriately.) * * @param position Index (starting at 0) of the data item to be selected. * @param y The distance from the top edge of the ListView (plus padding) that the * item will be positioned. */ public void setSelectionFromTop(int position, int y) { if (mAdapter == null) { return; } if (!isInTouchMode()) { position = lookForSelectablePosition(position, true); if (position >= 0) { setNextSelectedPositionInt(position); } } else { mResurrectToPosition = position; } if (position >= 0) { mLayoutMode = LAYOUT_SPECIFIC; mSpecificTop = mListPadding.top + y; if (mNeedSync) { mSyncPosition = position; mSyncRowId = mAdapter.getItemId(position); } requestLayout(); } }
重新设置选中项,并且重新布局。
设置第几条被选中,SelectionInt:
/** * Makes the item at the supplied position selected. * * @param position the position of the item to select */ @Override void setSelectionInt(int position) { setNextSelectedPositionInt(position); boolean awakeScrollbars = false; final int selectedPosition = mSelectedPosition; if (selectedPosition >= 0) { if (position == selectedPosition - 1) { awakeScrollbars = true; } else if (position == selectedPosition + 1) { awakeScrollbars = true; } } layoutChildren(); if (awakeScrollbars) { awakenScrollBars(); } }
寻找可供选中的Position(最大值或最小值):
/** * Find a position that can be selected (i.e., is not a separator). * * @param position The starting position to look at. * @param lookDown Whether to look down for other positions. * @return The next selectable position starting at position and then searching either up or * down. Returns {@link #INVALID_POSITION} if nothing can be found. */ @Override int lookForSelectablePosition(int position, boolean lookDown) { final ListAdapter adapter = mAdapter; if (adapter == null || isInTouchMode()) { return INVALID_POSITION; } final int count = adapter.getCount(); if (!mAreAllItemsSelectable) { if (lookDown) { position = Math.max(0, position); while (position < count && !adapter.isEnabled(position)) { position++; } } else { position = Math.min(position, count - 1); while (position >= 0 && !adapter.isEnabled(position)) { position--; } } if (position < 0 || position >= count) { return INVALID_POSITION; } return position; } else { if (position < 0 || position >= count) { return INVALID_POSITION; } return position; } }
lookDown:是否往下选择。
PS:3600行的代码,在一篇文章里面偏多,所以把本文分为了上下两篇,分析的比较简陋,但会在以后慢慢补充。
相关推荐
codeKK专注于开源项目源码解析、开源项目分享、Android 职位推荐。 微信公众号:codekk,二维码如下: 我们的网站: 我们的微博: 欢迎大家推荐好的 Android 开源项目,可直接,欢迎Star、Fork :) 关于我,欢迎关注 ...
开源分享、源码解析、框架设计、Android 内推。 我们的网站: #### #### #### 欢迎大家推荐好的 Android 开源项目,开源项目添加到 ,可以得到更多朋友的关注和反馈,欢迎Star、Fork :) 关于我,欢迎关注 微博: ...
Android高级应用源码-通过httpclient获取到JSON数据,展示到ListView.zip
codeKK专注于开源项目源码解析、开源项目分享、Android 职位推荐。 我们的网站: 我们的微博: 微信公众号:codekk,二维码如下: 欢迎大家推荐好的Android开源项目,可直接,欢迎Star、Fork :) 关于我,欢迎关注 ...
zxing.java源码解析 Android 开源项目分类汇总 #### 微信公众号:littleRich,二维码如下: 我们的网站: 我们的微博: 欢迎大家推荐好的 Android 开源项目,可直接,欢迎Star、Fork ) 关于我,欢迎关注 微博: ...
源码解析 自定义View详解 Activity界面绘制过程详解 Activity启动过程 Android Touch事件分发详解 AsyncTask详解 butterknife源码详解 InstantRun详解 ListView源码分析 VideoView源码分析 View绘制过程详解 网络...
zxing.java源码解析 目前包括: - - - - - 开源项目分类汇总 第一部分个性化控件(View) 主要介绍那些不错个性化的 View,包括 ListView、ActionBar、Menu、ViewPager、Gallery、GridView、ImageView、ProgressBar、...
安卓源码包android web应用Dialog对话框OCR图像识别listview相关EditText输入框Launcher 桌面45个合集: [四次元]Android Launcher 桌面分页滑动代码.rar [四次元]Android Launcher 源码修改可编译.rar [四次元]...
|--xml文件的pull解析与序列化写入 |--xml的封装序列化 |--任务循环之只在Activity显示时执行 |--修改文件的最后修改时间 |--偏好设置(回显) |--内存优化之各种方法 |--内容提供者之短信的序列化对象读写 |--内容...
传智播客 Android 视频教程 课程源码 课程安排 第一天 1>搭建Android开发环境 2> 创建与启动手机模拟器 3> 学习使用ANDROID操作系统 4> 开发与运行(卸载)第一个ANDROID应用 5> 项目的目录结构 6> 项目清单文件...
codeKK专注于开源项目源码解析、开源项目分享、Android 职位推荐。 我们的网站: 我们的微博: 微信公众号:codekk,二维码如下: 欢迎大家推荐好的Android开源项目,可直接,欢迎Star、Fork :) 关于我,欢迎关注 ...
smack源码 优秀推荐:、 本着开源的精神,无私奉献自己的代码好的优秀博客添加也会陆续更新中.... Android 开发中的日常积累(一) 自定义View + 动画 ListView 输入法 适配相关 EventBus AndroidStudio 网络请求 ...
这是一个包含异步加载、网络编程、JSON解析、LruCache图片缓存的简易的ListView图文混排Demo.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
Android项目源码24小时今日资讯新闻客户端 最新鲜的时事新闻,最时尚的手机科技,最好玩的热门游戏,还有各式各样的微博热点。 第一时间听到今日的最新资讯,24小时和你分享生活的每一分钟! 主要是模仿今日头条这样...
15_采用Pull解析器解析和生成XML内容.avi 所在项目:xml 16_采用SharedPreferences保存用户偏好设置参数.avi 所在项目:SharedPreferences 17_创建数据库与完成数据添删改查.avi 所在项目:db 18_在SQLite中使用事务...
这篇android客户端源码我觉得写的非常好,因为这里面有很多线程处理,activity处理,以及有动态的listview 中adapter怎么去处理解析,觉得比mars老师写的代码好很多,思路好很多,mars老师写的东西只能适合初学者...
2.4.9 列表视图(ListView和 ListActivity) 95 2.4.10 可展开的列表组件(ExpandableListView) 101 2.4.11 网格视图(GridView)和 图像切换器(ImageSwitcher) 功能和用法 104 2.4.12 画廊视图...
3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发详解 4.1.1 用户界面简介 4.1.2 事件处理 4.2 常用控件应用 4.2.1 文本框(Textiew) 4.2.2 列表...
Android学习实例包,包含8个实例,是学习...4、Android仿iPhone滚动控件源码 5、Android自定义锁屏实现----仿正点闹钟 6、android 漂亮的UI界面 完整的界面设计 7、Java Android 解析html的demo 8、SlidingMenu滑动菜单