深入了解ListView(3) — 一天一点源码
来源:互联网 发布:淘宝宝贝涨价被降权了 编辑:程序博客网 时间:2024/06/15 08:49
总述
了解AdapterView以及AbsListView之后,再看ListView中功能的具体实现。
前言
/** * A view that shows items in a vertically scrolling list. The items * come from the {@link ListAdapter} associated with this view. * * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a> * guide.</p> * * @attr ref android.R.styleable#ListView_entries * @attr ref android.R.styleable#ListView_divider * @attr ref android.R.styleable#ListView_dividerHeight * @attr ref android.R.styleable#ListView_headerDividersEnabled * @attr ref android.R.styleable#ListView_footerDividersEnabled */
这是一个将子视图显示在垂直的滚动的列表中的视图。这些子视图来自于ListAdapter的关联视图。
方法
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;} 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; } } }
这是一个内部类,该类表示该列表(listView)中的一个固定视图。
view表示添加到list中的视图;
data表示这个视图绑定的数据;
isSelectable表示这个固定视图是否可选中。
removeFixedViewInfo方法实现的是对固定视图的移除。
public ListView(Context context) { this(context, null); } public ListView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.listViewStyle); } public ListView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } 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(); }
ListView的四个构造函数,初始化构造ListView的一些参数。
public int getMaxScrollAmount() { return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop)); }
获得最大滑动参数。
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的上下界,确保ListView处于正确的位置。
public void addHeaderView(View v, Object data, boolean isSelectable) { final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { if (!(mAdapter instanceof HeaderViewListAdapter)) { wrapHeaderListAdapterInternal(); } // In the case of re-adding a header view, or adding one later on, // we need to notify the observer. if (mDataSetObserver != null) { mDataSetObserver.onChanged(); } } } public void addHeaderView(View v) { addHeaderView(v, null, true); }
添加一个头视图。必须在设置适配器之前添加。
此外还有removeHeader(View v)方法移除头视图。
removeHeader方法中调用的是removeFixedViewInfo(View v, ArrayList where)方法,进行对固定视图的移除。
public void addFooterView(View v, Object data, boolean isSelectable) { final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mFooterViewInfos.add(info); mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { if (!(mAdapter instanceof HeaderViewListAdapter)) { wrapHeaderListAdapterInternal(); } // In the case of re-adding a footer view, or adding one later on, // we need to notify the observer. if (mDataSetObserver != null) { mDataSetObserver.onChanged(); } }} public void addFooterView(View v) { addFooterView(v, null, true);}public boolean removeFooterView(View v) {…}
添加足视图和移除足视图。同样只能在设置adapter之前调用。
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; } } } }
清除回收状态。在resetList()中调用。(该方法为ListView重写的AbsListView中的方法,当列表为空时调用,用于清除列表中所有视图)
private boolean showingTopFadingEdge() { final int listTop = mScrollY + mListPadding.top; return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop); } 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); }
显示顶部边缘;显示底部边缘。
private View fillDown(int pos, int nextTop) { View selectedView = null; int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }
在指定位置向下填充列表,返回的是我们在绘制过程被中选中的视图。
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; setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }
在指定位置向上填充列表,返回的是我们在绘制过程被中选中的视图。
此外还有fillFromTop从顶部开始填充,fillFromMiddle从屏幕中部开始填充。
fillAboveAndBelow(View sel, int position),如果被选中的视图已经被放置了,将填充该视图以下的可见区域。
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; }
fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) 根据新选择的特定位置填充网格。可以移动位置,使其不与已褪色的边缘相交。然后将网格向上和向下填充。
private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength, int selectedPosition) { int bottomSelectionPixel = childrenBottom; if (selectedPosition != mItemCount - 1) { bottomSelectionPixel -= fadingEdgeLength; } return bottomSelectionPixel; } private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) { // first pixel we can draw the selection into int topSelectionPixel = childrenTop; if (selectedPosition > 0) { topSelectionPixel += fadingEdgeLength; } return topSelectionPixel; }
得到通过计算获得的底部(顶部)绘制位置。
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; }
根据向上或者向下滑动,根据新的被选择的视图填充列表。
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; } 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; } } @Nullable Runnable setupFocusIfValid(int position) { if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) { return null; } mAction = STATE_REQUEST_FOCUS; return this; } void onLayoutComplete() { if (mAction == STATE_WAIT_FOR_LAYOUT) { mAction = -1; } } }
一个内部类,定义了一个焦点选择器。
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(); }
测量失效的子视图。
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; } }
填充一个具体的子视图到屏幕具体的位置上。
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(); } } } }
检测是否将底部滑动太高(当没有这个需求是),即触底反弹。当回复正常底部显示时,将所有视图正确的显示。还有一个correctTooLow(int childCount)检测是否将底部拖的过低。
@Overrideprotected void layoutChildren() {…}
重写了AbsListView的layoutChildren方法。将子布局填充了进去,并实现了listView的回收等。
private void removeUnusedFixedViews(@Nullable List<FixedViewInfo> infoList) { if (infoList == null) { return; } for (int i = infoList.size() - 1; i >= 0; i--) { final FixedViewInfo fixedViewInfo = infoList.get(i); final View view = fixedViewInfo.view; final LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (view.getParent() == null && lp != null && lp.recycledHeaderFooter) { removeDetachedView(view, false); lp.recycledHeaderFooter = false; } } }
移除不被使用的固定视图。当头视图/足视图没有像其他视图一样已失效或缓存,但他们确实接触了与这个ViewGroup的绑定(当前listView)时,在布局被操作后调用此方法对其移除。
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; }
创建一个视图对象,并将其添加到我们list的子布局中。这个视图可以是新建的,也可以是不被使用的视图转换的,以及在回收站中的视图的复用。
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); }
添加一个视图作为子视图,确认其已经被测量(如果要求),以及其具体位置。
int lookForSelectablePositionAfter(int current, int position, boolean lookDown) { final ListAdapter adapter = mAdapter; if (adapter == null || isInTouchMode()) { return INVALID_POSITION; } // First check after the starting position in the specified direction. final int after = lookForSelectablePosition(position, lookDown); if (after != INVALID_POSITION) { return after; } // Then check between the starting position and the current position. final int count = adapter.getCount(); current = MathUtils.constrain(current, -1, count - 1); if (lookDown) { position = Math.min(position - 1, count - 1); while ((position > current) && !adapter.isEnabled(position)) { position--; } if (position <= current) { return INVALID_POSITION; } } else { position = Math.max(0, position + 1); while ((position < current) && !adapter.isEnabled(position)) { position++; } if (position >= current) { return INVALID_POSITION; } } return position; }
找到一个可以点击的位置。如果从起始位置的一个确定的方向上没有可点击的位置,就从起始位置相反的方向查找到当前位置。
public void setSelectionAfterHeaderView() { final int count = getHeaderViewsCount(); if (count > 0) { mNextSelectedPosition = 0; return; } if (mAdapter != null) { setSelection(count); } else { mNextSelectedPosition = count; mLayoutMode = LAYOUT_SET_SELECTION; } }````给头视图后的,列表的第一个条目设置可选状态。<div class="se-preview-section-delimiter"></div>
private boolean commonKey(int keyCode, int count, KeyEvent event) { if (mAdapter == null || !isAttachedToWindow()) { return false; } if (mDataChanged) { layoutChildren(); } boolean handled = false; int action = event.getAction(); if (KeyEvent.isConfirmKey(keyCode) && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) { handled = resurrectSelectionIfNeeded(); if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) { keyPressed(); handled = true; } }
判断键状态,并重写android.view.KeyEvent.Callback的onKeyDown、onKeyUp和onKeyMultiple将键的状态进行返回。(接口回调)<div class="se-preview-section-delimiter"></div>
boolean pageScroll(int direction) { final int nextPage; final boolean down; if (direction == FOCUS_UP) { nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1); down = false; } else if (direction == FOCUS_DOWN) { nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1); down = true; } else { return false; } if (nextPage >= 0) { final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down); if (position >= 0) { mLayoutMode = LAYOUT_SPECIFIC; mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength(); if (down && (position > (mItemCount - getChildCount()))) { mLayoutMode = LAYOUT_FORCE_BOTTOM; } if (!down && (position < getChildCount())) { mLayoutMode = LAYOUT_FORCE_TOP; } setSelectionInt(position); invokeOnItemScrollListener(); if (!awakenScrollBars()) { invalidate(); } return true; } } return false;}
需要传入一个int类型的方向,将屏幕上的子视图向这个方向滑动。<div class="se-preview-section-delimiter"></div>
boolean fullScroll(int direction) { boolean moved = false; if (direction == FOCUS_UP) { if (mSelectedPosition != 0) { final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true); if (position >= 0) { mLayoutMode = LAYOUT_FORCE_TOP; setSelectionInt(position); invokeOnItemScrollListener(); } moved = true; } } else if (direction == FOCUS_DOWN) { final int lastItem = (mItemCount - 1); if (mSelectedPosition < lastItem) { final int position = lookForSelectablePositionAfter( mSelectedPosition, lastItem, false); if (position >= 0) { mLayoutMode = LAYOUT_FORCE_BOTTOM; setSelectionInt(position); invokeOnItemScrollListener(); } moved = true; } } if (moved && !awakenScrollBars()) { awakenScrollBars(); invalidate(); } return moved;}
传入一个int类型的方向,移动到第一个或者最后一个子视图位置。<div class="se-preview-section-delimiter"></div>
private boolean handleHorizontalFocusWithinListItem(int direction) { if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) { throw new IllegalArgumentException("direction must be one of" + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}"); } final int numChildren = getChildCount(); if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) { final View selectedView = getSelectedView(); if (selectedView != null && selectedView.hasFocus() && selectedView instanceof ViewGroup) { final View currentFocus = selectedView.findFocus(); final View nextFocus = FocusFinder.getInstance().findNextFocus( (ViewGroup) selectedView, currentFocus, direction); if (nextFocus != null) { // do the math to get interesting rect in next focus' coordinates Rect focusedRect = mTempRect; if (currentFocus != null) { currentFocus.getFocusedRect(focusedRect); offsetDescendantRectToMyCoords(currentFocus, focusedRect); offsetRectIntoDescendantCoords(nextFocus, focusedRect); } else { focusedRect = null; } if (nextFocus.requestFocus(direction, focusedRect)) { return true; } } // we are blocking the key from being handled (by returning true) // if the global result is going to be some other view within this // list. this is to acheive the overall goal of having // horizontal d-pad navigation remain in the current item. final View globalNextFocus = FocusFinder.getInstance().findNextFocus( (ViewGroup) getRootView(), currentFocus, direction); if (globalNextFocus != null) { return isViewAncestorOf(globalNextFocus, this); } } } return false;}
避免横向使子视图被选中的状态发生改变。<div class="se-preview-section-delimiter"></div>
boolean arrowScroll(int direction) { try { mInLayout = true; final boolean handled = arrowScrollImpl(direction); if (handled) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); } return handled; } finally { mInLayout = false; }}
滑动到上一个或者下一个子视图,根据传入的direction确定方向。<div class="se-preview-section-delimiter"></div>
private final int nextSelectedPositionForDirection( View selectedView, int selectedPos, int direction) { int nextSelected; if (direction == View.FOCUS_DOWN) { final int listBottom = getHeight() - mListPadding.bottom; if (selectedView != null && selectedView.getBottom() <= listBottom) { nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ? selectedPos + 1 : mFirstPosition; } else { return INVALID_POSITION; } } else { final int listTop = mListPadding.top; if (selectedView != null && selectedView.getTop() >= listTop) { final int lastPos = mFirstPosition + getChildCount() - 1; nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ? selectedPos - 1 : lastPos; } else { return INVALID_POSITION; } } if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) { return INVALID_POSITION; } return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);}/** * Handle an arrow scroll going up or down. Take into account whether items are selectable, * whether there are focusable items etc. * * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}. * @return Whether any scrolling, selection or focus change occured. */private boolean arrowScrollImpl(int direction) { if (getChildCount() <= 0) { return false; } View selectedView = getSelectedView(); int selectedPos = mSelectedPosition; int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction); int amountToScroll = amountToScroll(direction, nextSelectedPosition); // if we are moving focus, we may OVERRIDE the default behavior final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null; if (focusResult != null) { nextSelectedPosition = focusResult.getSelectedPosition(); amountToScroll = focusResult.getAmountToScroll(); } boolean needToRedraw = focusResult != null; if (nextSelectedPosition != INVALID_POSITION) { handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null); setSelectedPositionInt(nextSelectedPosition); setNextSelectedPositionInt(nextSelectedPosition); selectedView = getSelectedView(); selectedPos = nextSelectedPosition; if (mItemsCanFocus && focusResult == null) { // there was no new view found to take focus, make sure we // don't leave focus with the old selection final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } } needToRedraw = true; checkSelectionChanged(); } if (amountToScroll > 0) { scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll); needToRedraw = true; } // if we didn't find a new focusable, make sure any existing focused // item that was panned off screen gives up focus. if (mItemsCanFocus && (focusResult == null) && selectedView != null && selectedView.hasFocus()) { final View focused = selectedView.findFocus(); if (focused != null) { if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) { focused.clearFocus(); } } } // if the current selection is panned off, we need to remove the selection if (nextSelectedPosition == INVALID_POSITION && selectedView != null && !isViewAncestorOf(selectedView, this)) { selectedView = null; hideSelector(); // but we don't want to set the ressurect position (that would make subsequent // unhandled key events bring back the item we just scrolled off!) mResurrectToPosition = INVALID_POSITION; } if (needToRedraw) { if (selectedView != null) { positionSelectorLikeFocus(selectedPos, selectedView); mSelectedTop = selectedView.getTop(); } if (!awakenScrollBars()) { invalidate(); } invokeOnItemScrollListener(); return true; } return false;}
这两个方法是对移动一个位置的实现,将子视图的所有属性状态也进行传递。<div class="se-preview-section-delimiter"></div>
private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition, boolean newFocusAssigned) { if (newSelectedPosition == INVALID_POSITION) { throw new IllegalArgumentException("newSelectedPosition needs to be valid"); } // whether or not we are moving down or up, we want to preserve the // top of whatever view is on top: // - moving down: the view that had selection // - moving up: the view that is getting selection View topView; View bottomView; int topViewIndex, bottomViewIndex; boolean topSelected = false; final int selectedIndex = mSelectedPosition - mFirstPosition; final int nextSelectedIndex = newSelectedPosition - mFirstPosition; if (direction == View.FOCUS_UP) { topViewIndex = nextSelectedIndex; bottomViewIndex = selectedIndex; topView = getChildAt(topViewIndex); bottomView = selectedView; topSelected = true; } else { topViewIndex = selectedIndex; bottomViewIndex = nextSelectedIndex; topView = selectedView; bottomView = getChildAt(bottomViewIndex); } final int numChildren = getChildCount(); // start with top view: is it changing size? if (topView != null) { topView.setSelected(!newFocusAssigned && topSelected); measureAndAdjustDown(topView, topViewIndex, numChildren); } // is the bottom view changing size? if (bottomView != null) { bottomView.setSelected(!newFocusAssigned && !topSelected); measureAndAdjustDown(bottomView, bottomViewIndex, numChildren); }}
“`
当选择的视图改变时,可能造成视图大小的改变。如果造成了大小的改变,我们需要取消一些视图的显示,然后重新绘制适当的布局。
- 深入了解ListView(3) — 一天一点源码
- 深入理解ListView (1)— 一天一点源码
- 深入理解ListView(2) — 一天一点源码
- 一天读一点:《深入理解计算机系统》
- 第一天深入了解 指针 引用吧
- 一天一点做项目(3)
- 关于EditText的一点深入的了解
- 关于EditText的一点深入的了解
- 关于EditText的一点深入的了解
- 关于EditText的一点深入的了解
- 深入了解Canvas标签(3)——使用图像
- 深入了解canvas标签(3)——使用图像
- Android ArrayAdapter 源码查看 深入了解
- 深入了解Bitmap源码解析及经验总结
- 继Gilde之后深入了解源码构造
- 深入了解mediaserver-3
- 深入了解mediaserver-3
- js的一点,深入了解,方法还可以这样用
- 题目1000:计算a+b
- CF 238E Meeting Her
- TCP/IP学习笔记<二>传输层协议(UDP TCP SCTP)
- 用程序给加水印要用组件
- PAT甲级1103
- 深入了解ListView(3) — 一天一点源码
- [杜教筛] 51Nod 1237 最大公约数之和 V3
- 内核移植问题集锦
- delete 和 delete [] 的真正区别
- hdu 1166 敌兵布阵(单点更新)
- POJ-3411-Paid Roads
- [杜教筛] 51Nod 1238 最小公倍数之和 V3
- CentOS 使用ifconfig没有显示ip
- Flink运行时之合久必分的特定任务