• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import com.google.android.collect.Lists;
20 
21 import com.android.internal.R;
22 import com.android.internal.util.Predicate;
23 
24 import android.annotation.IdRes;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.res.TypedArray;
30 import android.graphics.Canvas;
31 import android.graphics.Paint;
32 import android.graphics.PixelFormat;
33 import android.graphics.Rect;
34 import android.graphics.drawable.Drawable;
35 import android.os.Bundle;
36 import android.os.Trace;
37 import android.util.AttributeSet;
38 import android.util.MathUtils;
39 import android.util.SparseBooleanArray;
40 import android.view.FocusFinder;
41 import android.view.KeyEvent;
42 import android.view.SoundEffectConstants;
43 import android.view.View;
44 import android.view.ViewDebug;
45 import android.view.ViewGroup;
46 import android.view.ViewHierarchyEncoder;
47 import android.view.ViewParent;
48 import android.view.ViewRootImpl;
49 import android.view.accessibility.AccessibilityNodeInfo;
50 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
51 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
52 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
53 import android.view.accessibility.AccessibilityNodeProvider;
54 import android.widget.RemoteViews.RemoteView;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 
59 /*
60  * Implementation Notes:
61  *
62  * Some terminology:
63  *
64  *     index    - index of the items that are currently visible
65  *     position - index of the items in the cursor
66  */
67 
68 
69 /**
70  * A view that shows items in a vertically scrolling list. The items
71  * come from the {@link ListAdapter} associated with this view.
72  *
73  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
74  * guide.</p>
75  *
76  * @attr ref android.R.styleable#ListView_entries
77  * @attr ref android.R.styleable#ListView_divider
78  * @attr ref android.R.styleable#ListView_dividerHeight
79  * @attr ref android.R.styleable#ListView_headerDividersEnabled
80  * @attr ref android.R.styleable#ListView_footerDividersEnabled
81  */
82 @RemoteView
83 public class ListView extends AbsListView {
84     /**
85      * Used to indicate a no preference for a position type.
86      */
87     static final int NO_POSITION = -1;
88 
89     /**
90      * When arrow scrolling, ListView will never scroll more than this factor
91      * times the height of the list.
92      */
93     private static final float MAX_SCROLL_FACTOR = 0.33f;
94 
95     /**
96      * When arrow scrolling, need a certain amount of pixels to preview next
97      * items.  This is usually the fading edge, but if that is small enough,
98      * we want to make sure we preview at least this many pixels.
99      */
100     private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
101 
102     /**
103      * A class that represents a fixed view in a list, for example a header at the top
104      * or a footer at the bottom.
105      */
106     public class FixedViewInfo {
107         /** The view to add to the list */
108         public View view;
109         /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
110         public Object data;
111         /** <code>true</code> if the fixed view should be selectable in the list */
112         public boolean isSelectable;
113     }
114 
115     ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
116     ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
117 
118     Drawable mDivider;
119     int mDividerHeight;
120 
121     Drawable mOverScrollHeader;
122     Drawable mOverScrollFooter;
123 
124     private boolean mIsCacheColorOpaque;
125     private boolean mDividerIsOpaque;
126 
127     private boolean mHeaderDividersEnabled;
128     private boolean mFooterDividersEnabled;
129 
130     private boolean mAreAllItemsSelectable = true;
131 
132     private boolean mItemsCanFocus = false;
133 
134     // used for temporary calculations.
135     private final Rect mTempRect = new Rect();
136     private Paint mDividerPaint;
137 
138     // the single allocated result per list view; kinda cheesey but avoids
139     // allocating these thingies too often.
140     private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
141 
142     // Keeps focused children visible through resizes
143     private FocusSelector mFocusSelector;
144 
ListView(Context context)145     public ListView(Context context) {
146         this(context, null);
147     }
148 
ListView(Context context, AttributeSet attrs)149     public ListView(Context context, AttributeSet attrs) {
150         this(context, attrs, R.attr.listViewStyle);
151     }
152 
ListView(Context context, AttributeSet attrs, int defStyleAttr)153     public ListView(Context context, AttributeSet attrs, int defStyleAttr) {
154         this(context, attrs, defStyleAttr, 0);
155     }
156 
ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)157     public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
158         super(context, attrs, defStyleAttr, defStyleRes);
159 
160         final TypedArray a = context.obtainStyledAttributes(
161                 attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
162 
163         final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
164         if (entries != null) {
165             setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
166         }
167 
168         final Drawable d = a.getDrawable(R.styleable.ListView_divider);
169         if (d != null) {
170             // Use an implicit divider height which may be explicitly
171             // overridden by android:dividerHeight further down.
172             setDivider(d);
173         }
174 
175         final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
176         if (osHeader != null) {
177             setOverscrollHeader(osHeader);
178         }
179 
180         final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
181         if (osFooter != null) {
182             setOverscrollFooter(osFooter);
183         }
184 
185         // Use an explicit divider height, if specified.
186         if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
187             final int dividerHeight = a.getDimensionPixelSize(
188                     R.styleable.ListView_dividerHeight, 0);
189             if (dividerHeight != 0) {
190                 setDividerHeight(dividerHeight);
191             }
192         }
193 
194         mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
195         mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
196 
197         a.recycle();
198     }
199 
200     /**
201      * @return The maximum amount a list view will scroll in response to
202      *   an arrow event.
203      */
getMaxScrollAmount()204     public int getMaxScrollAmount() {
205         return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
206     }
207 
208     /**
209      * Make sure views are touching the top or bottom edge, as appropriate for
210      * our gravity
211      */
adjustViewsUpOrDown()212     private void adjustViewsUpOrDown() {
213         final int childCount = getChildCount();
214         int delta;
215 
216         if (childCount > 0) {
217             View child;
218 
219             if (!mStackFromBottom) {
220                 // Uh-oh -- we came up short. Slide all views up to make them
221                 // align with the top
222                 child = getChildAt(0);
223                 delta = child.getTop() - mListPadding.top;
224                 if (mFirstPosition != 0) {
225                     // It's OK to have some space above the first item if it is
226                     // part of the vertical spacing
227                     delta -= mDividerHeight;
228                 }
229                 if (delta < 0) {
230                     // We only are looking to see if we are too low, not too high
231                     delta = 0;
232                 }
233             } else {
234                 // we are too high, slide all views down to align with bottom
235                 child = getChildAt(childCount - 1);
236                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
237 
238                 if (mFirstPosition + childCount < mItemCount) {
239                     // It's OK to have some space below the last item if it is
240                     // part of the vertical spacing
241                     delta += mDividerHeight;
242                 }
243 
244                 if (delta > 0) {
245                     delta = 0;
246                 }
247             }
248 
249             if (delta != 0) {
250                 offsetChildrenTopAndBottom(-delta);
251             }
252         }
253     }
254 
255     /**
256      * Add a fixed view to appear at the top of the list. If this method is
257      * called more than once, the views will appear in the order they were
258      * added. Views added using this call can take focus if they want.
259      * <p>
260      * Note: When first introduced, this method could only be called before
261      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
262      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
263      * called at any time. If the ListView's adapter does not extend
264      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
265      * instance of {@link WrapperListAdapter}.
266      *
267      * @param v The view to add.
268      * @param data Data to associate with this view
269      * @param isSelectable whether the item is selectable
270      */
addHeaderView(View v, Object data, boolean isSelectable)271     public void addHeaderView(View v, Object data, boolean isSelectable) {
272         final FixedViewInfo info = new FixedViewInfo();
273         info.view = v;
274         info.data = data;
275         info.isSelectable = isSelectable;
276         mHeaderViewInfos.add(info);
277         mAreAllItemsSelectable &= isSelectable;
278 
279         // Wrap the adapter if it wasn't already wrapped.
280         if (mAdapter != null) {
281             if (!(mAdapter instanceof HeaderViewListAdapter)) {
282                 wrapHeaderListAdapterInternal();
283             }
284 
285             // In the case of re-adding a header view, or adding one later on,
286             // we need to notify the observer.
287             if (mDataSetObserver != null) {
288                 mDataSetObserver.onChanged();
289             }
290         }
291     }
292 
293     /**
294      * Add a fixed view to appear at the top of the list. If addHeaderView is
295      * called more than once, the views will appear in the order they were
296      * added. Views added using this call can take focus if they want.
297      * <p>
298      * Note: When first introduced, this method could only be called before
299      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
300      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
301      * called at any time. If the ListView's adapter does not extend
302      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
303      * instance of {@link WrapperListAdapter}.
304      *
305      * @param v The view to add.
306      */
addHeaderView(View v)307     public void addHeaderView(View v) {
308         addHeaderView(v, null, true);
309     }
310 
311     @Override
getHeaderViewsCount()312     public int getHeaderViewsCount() {
313         return mHeaderViewInfos.size();
314     }
315 
316     /**
317      * Removes a previously-added header view.
318      *
319      * @param v The view to remove
320      * @return true if the view was removed, false if the view was not a header
321      *         view
322      */
removeHeaderView(View v)323     public boolean removeHeaderView(View v) {
324         if (mHeaderViewInfos.size() > 0) {
325             boolean result = false;
326             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
327                 if (mDataSetObserver != null) {
328                     mDataSetObserver.onChanged();
329                 }
330                 result = true;
331             }
332             removeFixedViewInfo(v, mHeaderViewInfos);
333             return result;
334         }
335         return false;
336     }
337 
removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where)338     private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
339         int len = where.size();
340         for (int i = 0; i < len; ++i) {
341             FixedViewInfo info = where.get(i);
342             if (info.view == v) {
343                 where.remove(i);
344                 break;
345             }
346         }
347     }
348 
349     /**
350      * Add a fixed view to appear at the bottom of the list. If addFooterView is
351      * called more than once, the views will appear in the order they were
352      * added. Views added using this call can take focus if they want.
353      * <p>
354      * Note: When first introduced, this method could only be called before
355      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
356      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
357      * called at any time. If the ListView's adapter does not extend
358      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
359      * instance of {@link WrapperListAdapter}.
360      *
361      * @param v The view to add.
362      * @param data Data to associate with this view
363      * @param isSelectable true if the footer view can be selected
364      */
addFooterView(View v, Object data, boolean isSelectable)365     public void addFooterView(View v, Object data, boolean isSelectable) {
366         final FixedViewInfo info = new FixedViewInfo();
367         info.view = v;
368         info.data = data;
369         info.isSelectable = isSelectable;
370         mFooterViewInfos.add(info);
371         mAreAllItemsSelectable &= isSelectable;
372 
373         // Wrap the adapter if it wasn't already wrapped.
374         if (mAdapter != null) {
375             if (!(mAdapter instanceof HeaderViewListAdapter)) {
376                 wrapHeaderListAdapterInternal();
377             }
378 
379             // In the case of re-adding a footer view, or adding one later on,
380             // we need to notify the observer.
381             if (mDataSetObserver != null) {
382                 mDataSetObserver.onChanged();
383             }
384         }
385     }
386 
387     /**
388      * Add a fixed view to appear at the bottom of the list. If addFooterView is
389      * called more than once, the views will appear in the order they were
390      * added. Views added using this call can take focus if they want.
391      * <p>
392      * Note: When first introduced, this method could only be called before
393      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
394      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
395      * called at any time. If the ListView's adapter does not extend
396      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
397      * instance of {@link WrapperListAdapter}.
398      *
399      * @param v The view to add.
400      */
addFooterView(View v)401     public void addFooterView(View v) {
402         addFooterView(v, null, true);
403     }
404 
405     @Override
getFooterViewsCount()406     public int getFooterViewsCount() {
407         return mFooterViewInfos.size();
408     }
409 
410     /**
411      * Removes a previously-added footer view.
412      *
413      * @param v The view to remove
414      * @return
415      * true if the view was removed, false if the view was not a footer view
416      */
removeFooterView(View v)417     public boolean removeFooterView(View v) {
418         if (mFooterViewInfos.size() > 0) {
419             boolean result = false;
420             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
421                 if (mDataSetObserver != null) {
422                     mDataSetObserver.onChanged();
423                 }
424                 result = true;
425             }
426             removeFixedViewInfo(v, mFooterViewInfos);
427             return result;
428         }
429         return false;
430     }
431 
432     /**
433      * Returns the adapter currently in use in this ListView. The returned adapter
434      * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
435      * might be a {@link WrapperListAdapter}.
436      *
437      * @return The adapter currently used to display data in this ListView.
438      *
439      * @see #setAdapter(ListAdapter)
440      */
441     @Override
getAdapter()442     public ListAdapter getAdapter() {
443         return mAdapter;
444     }
445 
446     /**
447      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
448      * through the specified intent.
449      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
450      */
451     @android.view.RemotableViewMethod
setRemoteViewsAdapter(Intent intent)452     public void setRemoteViewsAdapter(Intent intent) {
453         super.setRemoteViewsAdapter(intent);
454     }
455 
456     /**
457      * Sets the data behind this ListView.
458      *
459      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
460      * depending on the ListView features currently in use. For instance, adding
461      * headers and/or footers will cause the adapter to be wrapped.
462      *
463      * @param adapter The ListAdapter which is responsible for maintaining the
464      *        data backing this list and for producing a view to represent an
465      *        item in that data set.
466      *
467      * @see #getAdapter()
468      */
469     @Override
setAdapter(ListAdapter adapter)470     public void setAdapter(ListAdapter adapter) {
471         if (mAdapter != null && mDataSetObserver != null) {
472             mAdapter.unregisterDataSetObserver(mDataSetObserver);
473         }
474 
475         resetList();
476         mRecycler.clear();
477 
478         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
479             mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
480         } else {
481             mAdapter = adapter;
482         }
483 
484         mOldSelectedPosition = INVALID_POSITION;
485         mOldSelectedRowId = INVALID_ROW_ID;
486 
487         // AbsListView#setAdapter will update choice mode states.
488         super.setAdapter(adapter);
489 
490         if (mAdapter != null) {
491             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
492             mOldItemCount = mItemCount;
493             mItemCount = mAdapter.getCount();
494             checkFocus();
495 
496             mDataSetObserver = new AdapterDataSetObserver();
497             mAdapter.registerDataSetObserver(mDataSetObserver);
498 
499             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
500 
501             int position;
502             if (mStackFromBottom) {
503                 position = lookForSelectablePosition(mItemCount - 1, false);
504             } else {
505                 position = lookForSelectablePosition(0, true);
506             }
507             setSelectedPositionInt(position);
508             setNextSelectedPositionInt(position);
509 
510             if (mItemCount == 0) {
511                 // Nothing selected
512                 checkSelectionChanged();
513             }
514         } else {
515             mAreAllItemsSelectable = true;
516             checkFocus();
517             // Nothing selected
518             checkSelectionChanged();
519         }
520 
521         requestLayout();
522     }
523 
524     /**
525      * The list is empty. Clear everything out.
526      */
527     @Override
resetList()528     void resetList() {
529         // The parent's resetList() will remove all views from the layout so we need to
530         // cleanup the state of our footers and headers
531         clearRecycledState(mHeaderViewInfos);
532         clearRecycledState(mFooterViewInfos);
533 
534         super.resetList();
535 
536         mLayoutMode = LAYOUT_NORMAL;
537     }
538 
clearRecycledState(ArrayList<FixedViewInfo> infos)539     private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
540         if (infos != null) {
541             final int count = infos.size();
542 
543             for (int i = 0; i < count; i++) {
544                 final View child = infos.get(i).view;
545                 final LayoutParams p = (LayoutParams) child.getLayoutParams();
546                 if (p != null) {
547                     p.recycledHeaderFooter = false;
548                 }
549             }
550         }
551     }
552 
553     /**
554      * @return Whether the list needs to show the top fading edge
555      */
showingTopFadingEdge()556     private boolean showingTopFadingEdge() {
557         final int listTop = mScrollY + mListPadding.top;
558         return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
559     }
560 
561     /**
562      * @return Whether the list needs to show the bottom fading edge
563      */
showingBottomFadingEdge()564     private boolean showingBottomFadingEdge() {
565         final int childCount = getChildCount();
566         final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
567         final int lastVisiblePosition = mFirstPosition + childCount - 1;
568 
569         final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
570 
571         return (lastVisiblePosition < mItemCount - 1)
572                          || (bottomOfBottomChild < listBottom);
573     }
574 
575 
576     @Override
requestChildRectangleOnScreen(View child, Rect rect, boolean immediate)577     public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
578 
579         int rectTopWithinChild = rect.top;
580 
581         // offset so rect is in coordinates of the this view
582         rect.offset(child.getLeft(), child.getTop());
583         rect.offset(-child.getScrollX(), -child.getScrollY());
584 
585         final int height = getHeight();
586         int listUnfadedTop = getScrollY();
587         int listUnfadedBottom = listUnfadedTop + height;
588         final int fadingEdge = getVerticalFadingEdgeLength();
589 
590         if (showingTopFadingEdge()) {
591             // leave room for top fading edge as long as rect isn't at very top
592             if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
593                 listUnfadedTop += fadingEdge;
594             }
595         }
596 
597         int childCount = getChildCount();
598         int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
599 
600         if (showingBottomFadingEdge()) {
601             // leave room for bottom fading edge as long as rect isn't at very bottom
602             if ((mSelectedPosition < mItemCount - 1)
603                     || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
604                 listUnfadedBottom -= fadingEdge;
605             }
606         }
607 
608         int scrollYDelta = 0;
609 
610         if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
611             // need to MOVE DOWN to get it in view: move down just enough so
612             // that the entire rectangle is in view (or at least the first
613             // screen size chunk).
614 
615             if (rect.height() > height) {
616                 // just enough to get screen size chunk on
617                 scrollYDelta += (rect.top - listUnfadedTop);
618             } else {
619                 // get entire rect at bottom of screen
620                 scrollYDelta += (rect.bottom - listUnfadedBottom);
621             }
622 
623             // make sure we aren't scrolling beyond the end of our children
624             int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
625             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
626         } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
627             // need to MOVE UP to get it in view: move up just enough so that
628             // entire rectangle is in view (or at least the first screen
629             // size chunk of it).
630 
631             if (rect.height() > height) {
632                 // screen size chunk
633                 scrollYDelta -= (listUnfadedBottom - rect.bottom);
634             } else {
635                 // entire rect at top
636                 scrollYDelta -= (listUnfadedTop - rect.top);
637             }
638 
639             // make sure we aren't scrolling any further than the top our children
640             int top = getChildAt(0).getTop();
641             int deltaToTop = top - listUnfadedTop;
642             scrollYDelta = Math.max(scrollYDelta, deltaToTop);
643         }
644 
645         final boolean scroll = scrollYDelta != 0;
646         if (scroll) {
647             scrollListItemsBy(-scrollYDelta);
648             positionSelector(INVALID_POSITION, child);
649             mSelectedTop = child.getTop();
650             invalidate();
651         }
652         return scroll;
653     }
654 
655     /**
656      * {@inheritDoc}
657      */
658     @Override
fillGap(boolean down)659     void fillGap(boolean down) {
660         final int count = getChildCount();
661         if (down) {
662             int paddingTop = 0;
663             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
664                 paddingTop = getListPaddingTop();
665             }
666             final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
667                     paddingTop;
668             fillDown(mFirstPosition + count, startOffset);
669             correctTooHigh(getChildCount());
670         } else {
671             int paddingBottom = 0;
672             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
673                 paddingBottom = getListPaddingBottom();
674             }
675             final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
676                     getHeight() - paddingBottom;
677             fillUp(mFirstPosition - 1, startOffset);
678             correctTooLow(getChildCount());
679         }
680     }
681 
682     /**
683      * Fills the list from pos down to the end of the list view.
684      *
685      * @param pos The first position to put in the list
686      *
687      * @param nextTop The location where the top of the item associated with pos
688      *        should be drawn
689      *
690      * @return The view that is currently selected, if it happens to be in the
691      *         range that we draw.
692      */
fillDown(int pos, int nextTop)693     private View fillDown(int pos, int nextTop) {
694         View selectedView = null;
695 
696         int end = (mBottom - mTop);
697         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
698             end -= mListPadding.bottom;
699         }
700 
701         while (nextTop < end && pos < mItemCount) {
702             // is this the selected item?
703             boolean selected = pos == mSelectedPosition;
704             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
705 
706             nextTop = child.getBottom() + mDividerHeight;
707             if (selected) {
708                 selectedView = child;
709             }
710             pos++;
711         }
712 
713         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
714         return selectedView;
715     }
716 
717     /**
718      * Fills the list from pos up to the top of the list view.
719      *
720      * @param pos The first position to put in the list
721      *
722      * @param nextBottom The location where the bottom of the item associated
723      *        with pos should be drawn
724      *
725      * @return The view that is currently selected
726      */
fillUp(int pos, int nextBottom)727     private View fillUp(int pos, int nextBottom) {
728         View selectedView = null;
729 
730         int end = 0;
731         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
732             end = mListPadding.top;
733         }
734 
735         while (nextBottom > end && pos >= 0) {
736             // is this the selected item?
737             boolean selected = pos == mSelectedPosition;
738             View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
739             nextBottom = child.getTop() - mDividerHeight;
740             if (selected) {
741                 selectedView = child;
742             }
743             pos--;
744         }
745 
746         mFirstPosition = pos + 1;
747         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
748         return selectedView;
749     }
750 
751     /**
752      * Fills the list from top to bottom, starting with mFirstPosition
753      *
754      * @param nextTop The location where the top of the first item should be
755      *        drawn
756      *
757      * @return The view that is currently selected
758      */
fillFromTop(int nextTop)759     private View fillFromTop(int nextTop) {
760         mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
761         mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
762         if (mFirstPosition < 0) {
763             mFirstPosition = 0;
764         }
765         return fillDown(mFirstPosition, nextTop);
766     }
767 
768 
769     /**
770      * Put mSelectedPosition in the middle of the screen and then build up and
771      * down from there. This method forces mSelectedPosition to the center.
772      *
773      * @param childrenTop Top of the area in which children can be drawn, as
774      *        measured in pixels
775      * @param childrenBottom Bottom of the area in which children can be drawn,
776      *        as measured in pixels
777      * @return Currently selected view
778      */
fillFromMiddle(int childrenTop, int childrenBottom)779     private View fillFromMiddle(int childrenTop, int childrenBottom) {
780         int height = childrenBottom - childrenTop;
781 
782         int position = reconcileSelectedPosition();
783 
784         View sel = makeAndAddView(position, childrenTop, true,
785                 mListPadding.left, true);
786         mFirstPosition = position;
787 
788         int selHeight = sel.getMeasuredHeight();
789         if (selHeight <= height) {
790             sel.offsetTopAndBottom((height - selHeight) / 2);
791         }
792 
793         fillAboveAndBelow(sel, position);
794 
795         if (!mStackFromBottom) {
796             correctTooHigh(getChildCount());
797         } else {
798             correctTooLow(getChildCount());
799         }
800 
801         return sel;
802     }
803 
804     /**
805      * Once the selected view as been placed, fill up the visible area above and
806      * below it.
807      *
808      * @param sel The selected view
809      * @param position The position corresponding to sel
810      */
fillAboveAndBelow(View sel, int position)811     private void fillAboveAndBelow(View sel, int position) {
812         final int dividerHeight = mDividerHeight;
813         if (!mStackFromBottom) {
814             fillUp(position - 1, sel.getTop() - dividerHeight);
815             adjustViewsUpOrDown();
816             fillDown(position + 1, sel.getBottom() + dividerHeight);
817         } else {
818             fillDown(position + 1, sel.getBottom() + dividerHeight);
819             adjustViewsUpOrDown();
820             fillUp(position - 1, sel.getTop() - dividerHeight);
821         }
822     }
823 
824 
825     /**
826      * Fills the grid based on positioning the new selection at a specific
827      * location. The selection may be moved so that it does not intersect the
828      * faded edges. The grid is then filled upwards and downwards from there.
829      *
830      * @param selectedTop Where the selected item should be
831      * @param childrenTop Where to start drawing children
832      * @param childrenBottom Last pixel where children can be drawn
833      * @return The view that currently has selection
834      */
fillFromSelection(int selectedTop, int childrenTop, int childrenBottom)835     private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
836         int fadingEdgeLength = getVerticalFadingEdgeLength();
837         final int selectedPosition = mSelectedPosition;
838 
839         View sel;
840 
841         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
842                 selectedPosition);
843         final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
844                 selectedPosition);
845 
846         sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
847 
848 
849         // Some of the newly selected item extends below the bottom of the list
850         if (sel.getBottom() > bottomSelectionPixel) {
851             // Find space available above the selection into which we can scroll
852             // upwards
853             final int spaceAbove = sel.getTop() - topSelectionPixel;
854 
855             // Find space required to bring the bottom of the selected item
856             // fully into view
857             final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
858             final int offset = Math.min(spaceAbove, spaceBelow);
859 
860             // Now offset the selected item to get it into view
861             sel.offsetTopAndBottom(-offset);
862         } else if (sel.getTop() < topSelectionPixel) {
863             // Find space required to bring the top of the selected item fully
864             // into view
865             final int spaceAbove = topSelectionPixel - sel.getTop();
866 
867             // Find space available below the selection into which we can scroll
868             // downwards
869             final int spaceBelow = bottomSelectionPixel - sel.getBottom();
870             final int offset = Math.min(spaceAbove, spaceBelow);
871 
872             // Offset the selected item to get it into view
873             sel.offsetTopAndBottom(offset);
874         }
875 
876         // Fill in views above and below
877         fillAboveAndBelow(sel, selectedPosition);
878 
879         if (!mStackFromBottom) {
880             correctTooHigh(getChildCount());
881         } else {
882             correctTooLow(getChildCount());
883         }
884 
885         return sel;
886     }
887 
888     /**
889      * Calculate the bottom-most pixel we can draw the selection into
890      *
891      * @param childrenBottom Bottom pixel were children can be drawn
892      * @param fadingEdgeLength Length of the fading edge in pixels, if present
893      * @param selectedPosition The position that will be selected
894      * @return The bottom-most pixel we can draw the selection into
895      */
getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength, int selectedPosition)896     private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
897             int selectedPosition) {
898         int bottomSelectionPixel = childrenBottom;
899         if (selectedPosition != mItemCount - 1) {
900             bottomSelectionPixel -= fadingEdgeLength;
901         }
902         return bottomSelectionPixel;
903     }
904 
905     /**
906      * Calculate the top-most pixel we can draw the selection into
907      *
908      * @param childrenTop Top pixel were children can be drawn
909      * @param fadingEdgeLength Length of the fading edge in pixels, if present
910      * @param selectedPosition The position that will be selected
911      * @return The top-most pixel we can draw the selection into
912      */
getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition)913     private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
914         // first pixel we can draw the selection into
915         int topSelectionPixel = childrenTop;
916         if (selectedPosition > 0) {
917             topSelectionPixel += fadingEdgeLength;
918         }
919         return topSelectionPixel;
920     }
921 
922     /**
923      * Smoothly scroll to the specified adapter position. The view will
924      * scroll such that the indicated position is displayed.
925      * @param position Scroll to this adapter position.
926      */
927     @android.view.RemotableViewMethod
smoothScrollToPosition(int position)928     public void smoothScrollToPosition(int position) {
929         super.smoothScrollToPosition(position);
930     }
931 
932     /**
933      * Smoothly scroll to the specified adapter position offset. The view will
934      * scroll such that the indicated position is displayed.
935      * @param offset The amount to offset from the adapter position to scroll to.
936      */
937     @android.view.RemotableViewMethod
smoothScrollByOffset(int offset)938     public void smoothScrollByOffset(int offset) {
939         super.smoothScrollByOffset(offset);
940     }
941 
942     /**
943      * Fills the list based on positioning the new selection relative to the old
944      * selection. The new selection will be placed at, above, or below the
945      * location of the new selection depending on how the selection is moving.
946      * The selection will then be pinned to the visible part of the screen,
947      * excluding the edges that are faded. The list is then filled upwards and
948      * downwards from there.
949      *
950      * @param oldSel The old selected view. Useful for trying to put the new
951      *        selection in the same place
952      * @param newSel The view that is to become selected. Useful for trying to
953      *        put the new selection in the same place
954      * @param delta Which way we are moving
955      * @param childrenTop Where to start drawing children
956      * @param childrenBottom Last pixel where children can be drawn
957      * @return The view that currently has selection
958      */
moveSelection(View oldSel, View newSel, int delta, int childrenTop, int childrenBottom)959     private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
960             int childrenBottom) {
961         int fadingEdgeLength = getVerticalFadingEdgeLength();
962         final int selectedPosition = mSelectedPosition;
963 
964         View sel;
965 
966         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
967                 selectedPosition);
968         final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
969                 selectedPosition);
970 
971         if (delta > 0) {
972             /*
973              * Case 1: Scrolling down.
974              */
975 
976             /*
977              *     Before           After
978              *    |       |        |       |
979              *    +-------+        +-------+
980              *    |   A   |        |   A   |
981              *    |   1   |   =>   +-------+
982              *    +-------+        |   B   |
983              *    |   B   |        |   2   |
984              *    +-------+        +-------+
985              *    |       |        |       |
986              *
987              *    Try to keep the top of the previously selected item where it was.
988              *    oldSel = A
989              *    sel = B
990              */
991 
992             // Put oldSel (A) where it belongs
993             oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
994                     mListPadding.left, false);
995 
996             final int dividerHeight = mDividerHeight;
997 
998             // Now put the new selection (B) below that
999             sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
1000                     mListPadding.left, true);
1001 
1002             // Some of the newly selected item extends below the bottom of the list
1003             if (sel.getBottom() > bottomSelectionPixel) {
1004 
1005                 // Find space available above the selection into which we can scroll upwards
1006                 int spaceAbove = sel.getTop() - topSelectionPixel;
1007 
1008                 // Find space required to bring the bottom of the selected item fully into view
1009                 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
1010 
1011                 // Don't scroll more than half the height of the list
1012                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1013                 int offset = Math.min(spaceAbove, spaceBelow);
1014                 offset = Math.min(offset, halfVerticalSpace);
1015 
1016                 // We placed oldSel, so offset that item
1017                 oldSel.offsetTopAndBottom(-offset);
1018                 // Now offset the selected item to get it into view
1019                 sel.offsetTopAndBottom(-offset);
1020             }
1021 
1022             // Fill in views above and below
1023             if (!mStackFromBottom) {
1024                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1025                 adjustViewsUpOrDown();
1026                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1027             } else {
1028                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1029                 adjustViewsUpOrDown();
1030                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1031             }
1032         } else if (delta < 0) {
1033             /*
1034              * Case 2: Scrolling up.
1035              */
1036 
1037             /*
1038              *     Before           After
1039              *    |       |        |       |
1040              *    +-------+        +-------+
1041              *    |   A   |        |   A   |
1042              *    +-------+   =>   |   1   |
1043              *    |   B   |        +-------+
1044              *    |   2   |        |   B   |
1045              *    +-------+        +-------+
1046              *    |       |        |       |
1047              *
1048              *    Try to keep the top of the item about to become selected where it was.
1049              *    newSel = A
1050              *    olSel = B
1051              */
1052 
1053             if (newSel != null) {
1054                 // Try to position the top of newSel (A) where it was before it was selected
1055                 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1056                         true);
1057             } else {
1058                 // If (A) was not on screen and so did not have a view, position
1059                 // it above the oldSel (B)
1060                 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1061                         true);
1062             }
1063 
1064             // Some of the newly selected item extends above the top of the list
1065             if (sel.getTop() < topSelectionPixel) {
1066                 // Find space required to bring the top of the selected item fully into view
1067                 int spaceAbove = topSelectionPixel - sel.getTop();
1068 
1069                // Find space available below the selection into which we can scroll downwards
1070                 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1071 
1072                 // Don't scroll more than half the height of the list
1073                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1074                 int offset = Math.min(spaceAbove, spaceBelow);
1075                 offset = Math.min(offset, halfVerticalSpace);
1076 
1077                 // Offset the selected item to get it into view
1078                 sel.offsetTopAndBottom(offset);
1079             }
1080 
1081             // Fill in views above and below
1082             fillAboveAndBelow(sel, selectedPosition);
1083         } else {
1084 
1085             int oldTop = oldSel.getTop();
1086 
1087             /*
1088              * Case 3: Staying still
1089              */
1090             sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1091 
1092             // We're staying still...
1093             if (oldTop < childrenTop) {
1094                 // ... but the top of the old selection was off screen.
1095                 // (This can happen if the data changes size out from under us)
1096                 int newBottom = sel.getBottom();
1097                 if (newBottom < childrenTop + 20) {
1098                     // Not enough visible -- bring it onscreen
1099                     sel.offsetTopAndBottom(childrenTop - sel.getTop());
1100                 }
1101             }
1102 
1103             // Fill in views above and below
1104             fillAboveAndBelow(sel, selectedPosition);
1105         }
1106 
1107         return sel;
1108     }
1109 
1110     private class FocusSelector implements Runnable {
1111         // the selector is waiting to set selection on the list view
1112         private static final int STATE_SET_SELECTION = 1;
1113         // the selector set the selection on the list view, waiting for a layoutChildren pass
1114         private static final int STATE_WAIT_FOR_LAYOUT = 2;
1115         // the selector's selection has been honored and it is waiting to request focus on the
1116         // target child.
1117         private static final int STATE_REQUEST_FOCUS = 3;
1118 
1119         private int mAction;
1120         private int mPosition;
1121         private int mPositionTop;
1122 
setupForSetSelection(int position, int top)1123         FocusSelector setupForSetSelection(int position, int top) {
1124             mPosition = position;
1125             mPositionTop = top;
1126             mAction = STATE_SET_SELECTION;
1127             return this;
1128         }
1129 
run()1130         public void run() {
1131             if (mAction == STATE_SET_SELECTION) {
1132                 setSelectionFromTop(mPosition, mPositionTop);
1133                 mAction = STATE_WAIT_FOR_LAYOUT;
1134             } else if (mAction == STATE_REQUEST_FOCUS) {
1135                 final int childIndex = mPosition - mFirstPosition;
1136                 final View child = getChildAt(childIndex);
1137                 if (child != null) {
1138                     child.requestFocus();
1139                 }
1140                 mAction = -1;
1141             }
1142         }
1143 
setupFocusIfValid(int position)1144         @Nullable Runnable setupFocusIfValid(int position) {
1145             if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) {
1146                 return null;
1147             }
1148             mAction = STATE_REQUEST_FOCUS;
1149             return this;
1150         }
1151 
onLayoutComplete()1152         void onLayoutComplete() {
1153             if (mAction == STATE_WAIT_FOR_LAYOUT) {
1154                 mAction = -1;
1155             }
1156         }
1157     }
1158 
1159     @Override
onDetachedFromWindow()1160     protected void onDetachedFromWindow() {
1161         if (mFocusSelector != null) {
1162             removeCallbacks(mFocusSelector);
1163             mFocusSelector = null;
1164         }
1165         super.onDetachedFromWindow();
1166     }
1167 
1168     @Override
onSizeChanged(int w, int h, int oldw, int oldh)1169     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1170         if (getChildCount() > 0) {
1171             View focusedChild = getFocusedChild();
1172             if (focusedChild != null) {
1173                 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1174                 final int childBottom = focusedChild.getBottom();
1175                 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1176                 final int top = focusedChild.getTop() - offset;
1177                 if (mFocusSelector == null) {
1178                     mFocusSelector = new FocusSelector();
1179                 }
1180                 post(mFocusSelector.setupForSetSelection(childPosition, top));
1181             }
1182         }
1183         super.onSizeChanged(w, h, oldw, oldh);
1184     }
1185 
1186     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1187     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1188         // Sets up mListPadding
1189         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1190 
1191         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1192         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1193         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1194         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1195 
1196         int childWidth = 0;
1197         int childHeight = 0;
1198         int childState = 0;
1199 
1200         mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1201         if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
1202                 || heightMode == MeasureSpec.UNSPECIFIED)) {
1203             final View child = obtainView(0, mIsScrap);
1204 
1205             // Lay out child directly against the parent measure spec so that
1206             // we can obtain exected minimum width and height.
1207             measureScrapChild(child, 0, widthMeasureSpec, heightSize);
1208 
1209             childWidth = child.getMeasuredWidth();
1210             childHeight = child.getMeasuredHeight();
1211             childState = combineMeasuredStates(childState, child.getMeasuredState());
1212 
1213             if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1214                     ((LayoutParams) child.getLayoutParams()).viewType)) {
1215                 mRecycler.addScrapView(child, 0);
1216             }
1217         }
1218 
1219         if (widthMode == MeasureSpec.UNSPECIFIED) {
1220             widthSize = mListPadding.left + mListPadding.right + childWidth +
1221                     getVerticalScrollbarWidth();
1222         } else {
1223             widthSize |= (childState & MEASURED_STATE_MASK);
1224         }
1225 
1226         if (heightMode == MeasureSpec.UNSPECIFIED) {
1227             heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1228                     getVerticalFadingEdgeLength() * 2;
1229         }
1230 
1231         if (heightMode == MeasureSpec.AT_MOST) {
1232             // TODO: after first layout we should maybe start at the first visible position, not 0
1233             heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1234         }
1235 
1236         setMeasuredDimension(widthSize, heightSize);
1237 
1238         mWidthMeasureSpec = widthMeasureSpec;
1239     }
1240 
measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint)1241     private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) {
1242         LayoutParams p = (LayoutParams) child.getLayoutParams();
1243         if (p == null) {
1244             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
1245             child.setLayoutParams(p);
1246         }
1247         p.viewType = mAdapter.getItemViewType(position);
1248         p.isEnabled = mAdapter.isEnabled(position);
1249         p.forceAdd = true;
1250 
1251         final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
1252                 mListPadding.left + mListPadding.right, p.width);
1253         final int lpHeight = p.height;
1254         final int childHeightSpec;
1255         if (lpHeight > 0) {
1256             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1257         } else {
1258             childHeightSpec = MeasureSpec.makeSafeMeasureSpec(heightHint, MeasureSpec.UNSPECIFIED);
1259         }
1260         child.measure(childWidthSpec, childHeightSpec);
1261 
1262         // Since this view was measured directly aginst the parent measure
1263         // spec, we must measure it again before reuse.
1264         child.forceLayout();
1265     }
1266 
1267     /**
1268      * @return True to recycle the views used to measure this ListView in
1269      *         UNSPECIFIED/AT_MOST modes, false otherwise.
1270      * @hide
1271      */
1272     @ViewDebug.ExportedProperty(category = "list")
recycleOnMeasure()1273     protected boolean recycleOnMeasure() {
1274         return true;
1275     }
1276 
1277     /**
1278      * Measures the height of the given range of children (inclusive) and
1279      * returns the height with this ListView's padding and divider heights
1280      * included. If maxHeight is provided, the measuring will stop when the
1281      * current height reaches maxHeight.
1282      *
1283      * @param widthMeasureSpec The width measure spec to be given to a child's
1284      *            {@link View#measure(int, int)}.
1285      * @param startPosition The position of the first child to be shown.
1286      * @param endPosition The (inclusive) position of the last child to be
1287      *            shown. Specify {@link #NO_POSITION} if the last child should be
1288      *            the last available child from the adapter.
1289      * @param maxHeight The maximum height that will be returned (if all the
1290      *            children don't fit in this value, this value will be
1291      *            returned).
1292      * @param disallowPartialChildPosition In general, whether the returned
1293      *            height should only contain entire children. This is more
1294      *            powerful--it is the first inclusive position at which partial
1295      *            children will not be allowed. Example: it looks nice to have
1296      *            at least 3 completely visible children, and in portrait this
1297      *            will most likely fit; but in landscape there could be times
1298      *            when even 2 children can not be completely shown, so a value
1299      *            of 2 (remember, inclusive) would be good (assuming
1300      *            startPosition is 0).
1301      * @return The height of this ListView with the given children.
1302      */
measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, int maxHeight, int disallowPartialChildPosition)1303     final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
1304             int maxHeight, int disallowPartialChildPosition) {
1305         final ListAdapter adapter = mAdapter;
1306         if (adapter == null) {
1307             return mListPadding.top + mListPadding.bottom;
1308         }
1309 
1310         // Include the padding of the list
1311         int returnedHeight = mListPadding.top + mListPadding.bottom;
1312         final int dividerHeight = mDividerHeight;
1313         // The previous height value that was less than maxHeight and contained
1314         // no partial children
1315         int prevHeightWithoutPartialChild = 0;
1316         int i;
1317         View child;
1318 
1319         // mItemCount - 1 since endPosition parameter is inclusive
1320         endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1321         final AbsListView.RecycleBin recycleBin = mRecycler;
1322         final boolean recyle = recycleOnMeasure();
1323         final boolean[] isScrap = mIsScrap;
1324 
1325         for (i = startPosition; i <= endPosition; ++i) {
1326             child = obtainView(i, isScrap);
1327 
1328             measureScrapChild(child, i, widthMeasureSpec, maxHeight);
1329 
1330             if (i > 0) {
1331                 // Count the divider for all but one child
1332                 returnedHeight += dividerHeight;
1333             }
1334 
1335             // Recycle the view before we possibly return from the method
1336             if (recyle && recycleBin.shouldRecycleViewType(
1337                     ((LayoutParams) child.getLayoutParams()).viewType)) {
1338                 recycleBin.addScrapView(child, -1);
1339             }
1340 
1341             returnedHeight += child.getMeasuredHeight();
1342 
1343             if (returnedHeight >= maxHeight) {
1344                 // We went over, figure out which height to return.  If returnedHeight > maxHeight,
1345                 // then the i'th position did not fit completely.
1346                 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1347                             && (i > disallowPartialChildPosition) // We've past the min pos
1348                             && (prevHeightWithoutPartialChild > 0) // We have a prev height
1349                             && (returnedHeight != maxHeight) // i'th child did not fit completely
1350                         ? prevHeightWithoutPartialChild
1351                         : maxHeight;
1352             }
1353 
1354             if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1355                 prevHeightWithoutPartialChild = returnedHeight;
1356             }
1357         }
1358 
1359         // At this point, we went through the range of children, and they each
1360         // completely fit, so return the returnedHeight
1361         return returnedHeight;
1362     }
1363 
1364     @Override
findMotionRow(int y)1365     int findMotionRow(int y) {
1366         int childCount = getChildCount();
1367         if (childCount > 0) {
1368             if (!mStackFromBottom) {
1369                 for (int i = 0; i < childCount; i++) {
1370                     View v = getChildAt(i);
1371                     if (y <= v.getBottom()) {
1372                         return mFirstPosition + i;
1373                     }
1374                 }
1375             } else {
1376                 for (int i = childCount - 1; i >= 0; i--) {
1377                     View v = getChildAt(i);
1378                     if (y >= v.getTop()) {
1379                         return mFirstPosition + i;
1380                     }
1381                 }
1382             }
1383         }
1384         return INVALID_POSITION;
1385     }
1386 
1387     /**
1388      * Put a specific item at a specific location on the screen and then build
1389      * up and down from there.
1390      *
1391      * @param position The reference view to use as the starting point
1392      * @param top Pixel offset from the top of this view to the top of the
1393      *        reference view.
1394      *
1395      * @return The selected view, or null if the selected view is outside the
1396      *         visible area.
1397      */
fillSpecific(int position, int top)1398     private View fillSpecific(int position, int top) {
1399         boolean tempIsSelected = position == mSelectedPosition;
1400         View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1401         // Possibly changed again in fillUp if we add rows above this one.
1402         mFirstPosition = position;
1403 
1404         View above;
1405         View below;
1406 
1407         final int dividerHeight = mDividerHeight;
1408         if (!mStackFromBottom) {
1409             above = fillUp(position - 1, temp.getTop() - dividerHeight);
1410             // This will correct for the top of the first view not touching the top of the list
1411             adjustViewsUpOrDown();
1412             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1413             int childCount = getChildCount();
1414             if (childCount > 0) {
1415                 correctTooHigh(childCount);
1416             }
1417         } else {
1418             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1419             // This will correct for the bottom of the last view not touching the bottom of the list
1420             adjustViewsUpOrDown();
1421             above = fillUp(position - 1, temp.getTop() - dividerHeight);
1422             int childCount = getChildCount();
1423             if (childCount > 0) {
1424                  correctTooLow(childCount);
1425             }
1426         }
1427 
1428         if (tempIsSelected) {
1429             return temp;
1430         } else if (above != null) {
1431             return above;
1432         } else {
1433             return below;
1434         }
1435     }
1436 
1437     /**
1438      * Check if we have dragged the bottom of the list too high (we have pushed the
1439      * top element off the top of the screen when we did not need to). Correct by sliding
1440      * everything back down.
1441      *
1442      * @param childCount Number of children
1443      */
correctTooHigh(int childCount)1444     private void correctTooHigh(int childCount) {
1445         // First see if the last item is visible. If it is not, it is OK for the
1446         // top of the list to be pushed up.
1447         int lastPosition = mFirstPosition + childCount - 1;
1448         if (lastPosition == mItemCount - 1 && childCount > 0) {
1449 
1450             // Get the last child ...
1451             final View lastChild = getChildAt(childCount - 1);
1452 
1453             // ... and its bottom edge
1454             final int lastBottom = lastChild.getBottom();
1455 
1456             // This is bottom of our drawable area
1457             final int end = (mBottom - mTop) - mListPadding.bottom;
1458 
1459             // This is how far the bottom edge of the last view is from the bottom of the
1460             // drawable area
1461             int bottomOffset = end - lastBottom;
1462             View firstChild = getChildAt(0);
1463             final int firstTop = firstChild.getTop();
1464 
1465             // Make sure we are 1) Too high, and 2) Either there are more rows above the
1466             // first row or the first row is scrolled off the top of the drawable area
1467             if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
1468                 if (mFirstPosition == 0) {
1469                     // Don't pull the top too far down
1470                     bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1471                 }
1472                 // Move everything down
1473                 offsetChildrenTopAndBottom(bottomOffset);
1474                 if (mFirstPosition > 0) {
1475                     // Fill the gap that was opened above mFirstPosition with more rows, if
1476                     // possible
1477                     fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1478                     // Close up the remaining gap
1479                     adjustViewsUpOrDown();
1480                 }
1481 
1482             }
1483         }
1484     }
1485 
1486     /**
1487      * Check if we have dragged the bottom of the list too low (we have pushed the
1488      * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1489      * everything back up.
1490      *
1491      * @param childCount Number of children
1492      */
correctTooLow(int childCount)1493     private void correctTooLow(int childCount) {
1494         // First see if the first item is visible. If it is not, it is OK for the
1495         // bottom of the list to be pushed down.
1496         if (mFirstPosition == 0 && childCount > 0) {
1497 
1498             // Get the first child ...
1499             final View firstChild = getChildAt(0);
1500 
1501             // ... and its top edge
1502             final int firstTop = firstChild.getTop();
1503 
1504             // This is top of our drawable area
1505             final int start = mListPadding.top;
1506 
1507             // This is bottom of our drawable area
1508             final int end = (mBottom - mTop) - mListPadding.bottom;
1509 
1510             // This is how far the top edge of the first view is from the top of the
1511             // drawable area
1512             int topOffset = firstTop - start;
1513             View lastChild = getChildAt(childCount - 1);
1514             final int lastBottom = lastChild.getBottom();
1515             int lastPosition = mFirstPosition + childCount - 1;
1516 
1517             // Make sure we are 1) Too low, and 2) Either there are more rows below the
1518             // last row or the last row is scrolled off the bottom of the drawable area
1519             if (topOffset > 0) {
1520                 if (lastPosition < mItemCount - 1 || lastBottom > end)  {
1521                     if (lastPosition == mItemCount - 1) {
1522                         // Don't pull the bottom too far up
1523                         topOffset = Math.min(topOffset, lastBottom - end);
1524                     }
1525                     // Move everything up
1526                     offsetChildrenTopAndBottom(-topOffset);
1527                     if (lastPosition < mItemCount - 1) {
1528                         // Fill the gap that was opened below the last position with more rows, if
1529                         // possible
1530                         fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1531                         // Close up the remaining gap
1532                         adjustViewsUpOrDown();
1533                     }
1534                 } else if (lastPosition == mItemCount - 1) {
1535                     adjustViewsUpOrDown();
1536                 }
1537             }
1538         }
1539     }
1540 
1541     @Override
layoutChildren()1542     protected void layoutChildren() {
1543         final boolean blockLayoutRequests = mBlockLayoutRequests;
1544         if (blockLayoutRequests) {
1545             return;
1546         }
1547 
1548         mBlockLayoutRequests = true;
1549 
1550         try {
1551             super.layoutChildren();
1552 
1553             invalidate();
1554 
1555             if (mAdapter == null) {
1556                 resetList();
1557                 invokeOnItemScrollListener();
1558                 return;
1559             }
1560 
1561             final int childrenTop = mListPadding.top;
1562             final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1563             final int childCount = getChildCount();
1564 
1565             int index = 0;
1566             int delta = 0;
1567 
1568             View sel;
1569             View oldSel = null;
1570             View oldFirst = null;
1571             View newSel = null;
1572 
1573             // Remember stuff we will need down below
1574             switch (mLayoutMode) {
1575             case LAYOUT_SET_SELECTION:
1576                 index = mNextSelectedPosition - mFirstPosition;
1577                 if (index >= 0 && index < childCount) {
1578                     newSel = getChildAt(index);
1579                 }
1580                 break;
1581             case LAYOUT_FORCE_TOP:
1582             case LAYOUT_FORCE_BOTTOM:
1583             case LAYOUT_SPECIFIC:
1584             case LAYOUT_SYNC:
1585                 break;
1586             case LAYOUT_MOVE_SELECTION:
1587             default:
1588                 // Remember the previously selected view
1589                 index = mSelectedPosition - mFirstPosition;
1590                 if (index >= 0 && index < childCount) {
1591                     oldSel = getChildAt(index);
1592                 }
1593 
1594                 // Remember the previous first child
1595                 oldFirst = getChildAt(0);
1596 
1597                 if (mNextSelectedPosition >= 0) {
1598                     delta = mNextSelectedPosition - mSelectedPosition;
1599                 }
1600 
1601                 // Caution: newSel might be null
1602                 newSel = getChildAt(index + delta);
1603             }
1604 
1605 
1606             boolean dataChanged = mDataChanged;
1607             if (dataChanged) {
1608                 handleDataChanged();
1609             }
1610 
1611             // Handle the empty set by removing all views that are visible
1612             // and calling it a day
1613             if (mItemCount == 0) {
1614                 resetList();
1615                 invokeOnItemScrollListener();
1616                 return;
1617             } else if (mItemCount != mAdapter.getCount()) {
1618                 throw new IllegalStateException("The content of the adapter has changed but "
1619                         + "ListView did not receive a notification. Make sure the content of "
1620                         + "your adapter is not modified from a background thread, but only from "
1621                         + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
1622                         + "when its content changes. [in ListView(" + getId() + ", " + getClass()
1623                         + ") with Adapter(" + mAdapter.getClass() + ")]");
1624             }
1625 
1626             setSelectedPositionInt(mNextSelectedPosition);
1627 
1628             AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
1629             View accessibilityFocusLayoutRestoreView = null;
1630             int accessibilityFocusPosition = INVALID_POSITION;
1631 
1632             // Remember which child, if any, had accessibility focus. This must
1633             // occur before recycling any views, since that will clear
1634             // accessibility focus.
1635             final ViewRootImpl viewRootImpl = getViewRootImpl();
1636             if (viewRootImpl != null) {
1637                 final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
1638                 if (focusHost != null) {
1639                     final View focusChild = getAccessibilityFocusedChild(focusHost);
1640                     if (focusChild != null) {
1641                         if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
1642                                 || focusChild.hasTransientState() || mAdapterHasStableIds) {
1643                             // The views won't be changing, so try to maintain
1644                             // focus on the current host and virtual view.
1645                             accessibilityFocusLayoutRestoreView = focusHost;
1646                             accessibilityFocusLayoutRestoreNode = viewRootImpl
1647                                     .getAccessibilityFocusedVirtualView();
1648                         }
1649 
1650                         // If all else fails, maintain focus at the same
1651                         // position.
1652                         accessibilityFocusPosition = getPositionForView(focusChild);
1653                     }
1654                 }
1655             }
1656 
1657             View focusLayoutRestoreDirectChild = null;
1658             View focusLayoutRestoreView = null;
1659 
1660             // Take focus back to us temporarily to avoid the eventual call to
1661             // clear focus when removing the focused child below from messing
1662             // things up when ViewAncestor assigns focus back to someone else.
1663             final View focusedChild = getFocusedChild();
1664             if (focusedChild != null) {
1665                 // TODO: in some cases focusedChild.getParent() == null
1666 
1667                 // We can remember the focused view to restore after re-layout
1668                 // if the data hasn't changed, or if the focused position is a
1669                 // header or footer.
1670                 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
1671                         || focusedChild.hasTransientState() || mAdapterHasStableIds) {
1672                     focusLayoutRestoreDirectChild = focusedChild;
1673                     // Remember the specific view that had focus.
1674                     focusLayoutRestoreView = findFocus();
1675                     if (focusLayoutRestoreView != null) {
1676                         // Tell it we are going to mess with it.
1677                         focusLayoutRestoreView.dispatchStartTemporaryDetach();
1678                     }
1679                 }
1680                 requestFocus();
1681             }
1682 
1683             // Pull all children into the RecycleBin.
1684             // These views will be reused if possible
1685             final int firstPosition = mFirstPosition;
1686             final RecycleBin recycleBin = mRecycler;
1687             if (dataChanged) {
1688                 for (int i = 0; i < childCount; i++) {
1689                     recycleBin.addScrapView(getChildAt(i), firstPosition+i);
1690                 }
1691             } else {
1692                 recycleBin.fillActiveViews(childCount, firstPosition);
1693             }
1694 
1695             // Clear out old views
1696             detachAllViewsFromParent();
1697             recycleBin.removeSkippedScrap();
1698 
1699             switch (mLayoutMode) {
1700             case LAYOUT_SET_SELECTION:
1701                 if (newSel != null) {
1702                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1703                 } else {
1704                     sel = fillFromMiddle(childrenTop, childrenBottom);
1705                 }
1706                 break;
1707             case LAYOUT_SYNC:
1708                 sel = fillSpecific(mSyncPosition, mSpecificTop);
1709                 break;
1710             case LAYOUT_FORCE_BOTTOM:
1711                 sel = fillUp(mItemCount - 1, childrenBottom);
1712                 adjustViewsUpOrDown();
1713                 break;
1714             case LAYOUT_FORCE_TOP:
1715                 mFirstPosition = 0;
1716                 sel = fillFromTop(childrenTop);
1717                 adjustViewsUpOrDown();
1718                 break;
1719             case LAYOUT_SPECIFIC:
1720                 final int selectedPosition = reconcileSelectedPosition();
1721                 sel = fillSpecific(selectedPosition, mSpecificTop);
1722                 /**
1723                  * When ListView is resized, FocusSelector requests an async selection for the
1724                  * previously focused item to make sure it is still visible. If the item is not
1725                  * selectable, it won't regain focus so instead we call FocusSelector
1726                  * to directly request focus on the view after it is visible.
1727                  */
1728                 if (sel == null && mFocusSelector != null) {
1729                     final Runnable focusRunnable = mFocusSelector
1730                             .setupFocusIfValid(selectedPosition);
1731                     if (focusRunnable != null) {
1732                         post(focusRunnable);
1733                     }
1734                 }
1735                 break;
1736             case LAYOUT_MOVE_SELECTION:
1737                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1738                 break;
1739             default:
1740                 if (childCount == 0) {
1741                     if (!mStackFromBottom) {
1742                         final int position = lookForSelectablePosition(0, true);
1743                         setSelectedPositionInt(position);
1744                         sel = fillFromTop(childrenTop);
1745                     } else {
1746                         final int position = lookForSelectablePosition(mItemCount - 1, false);
1747                         setSelectedPositionInt(position);
1748                         sel = fillUp(mItemCount - 1, childrenBottom);
1749                     }
1750                 } else {
1751                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1752                         sel = fillSpecific(mSelectedPosition,
1753                                 oldSel == null ? childrenTop : oldSel.getTop());
1754                     } else if (mFirstPosition < mItemCount) {
1755                         sel = fillSpecific(mFirstPosition,
1756                                 oldFirst == null ? childrenTop : oldFirst.getTop());
1757                     } else {
1758                         sel = fillSpecific(0, childrenTop);
1759                     }
1760                 }
1761                 break;
1762             }
1763 
1764             // Flush any cached views that did not get reused above
1765             recycleBin.scrapActiveViews();
1766 
1767             // remove any header/footer that has been temp detached and not re-attached
1768             removeUnusedFixedViews(mHeaderViewInfos);
1769             removeUnusedFixedViews(mFooterViewInfos);
1770 
1771             if (sel != null) {
1772                 // The current selected item should get focus if items are
1773                 // focusable.
1774                 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1775                     final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1776                             focusLayoutRestoreView != null &&
1777                             focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1778                     if (!focusWasTaken) {
1779                         // Selected item didn't take focus, but we still want to
1780                         // make sure something else outside of the selected view
1781                         // has focus.
1782                         final View focused = getFocusedChild();
1783                         if (focused != null) {
1784                             focused.clearFocus();
1785                         }
1786                         positionSelector(INVALID_POSITION, sel);
1787                     } else {
1788                         sel.setSelected(false);
1789                         mSelectorRect.setEmpty();
1790                     }
1791                 } else {
1792                     positionSelector(INVALID_POSITION, sel);
1793                 }
1794                 mSelectedTop = sel.getTop();
1795             } else {
1796                 final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
1797                         || mTouchMode == TOUCH_MODE_DONE_WAITING;
1798                 if (inTouchMode) {
1799                     // If the user's finger is down, select the motion position.
1800                     final View child = getChildAt(mMotionPosition - mFirstPosition);
1801                     if (child != null) {
1802                         positionSelector(mMotionPosition, child);
1803                     }
1804                 } else if (mSelectorPosition != INVALID_POSITION) {
1805                     // If we had previously positioned the selector somewhere,
1806                     // put it back there. It might not match up with the data,
1807                     // but it's transitioning out so it's not a big deal.
1808                     final View child = getChildAt(mSelectorPosition - mFirstPosition);
1809                     if (child != null) {
1810                         positionSelector(mSelectorPosition, child);
1811                     }
1812                 } else {
1813                     // Otherwise, clear selection.
1814                     mSelectedTop = 0;
1815                     mSelectorRect.setEmpty();
1816                 }
1817 
1818                 // Even if there is not selected position, we may need to
1819                 // restore focus (i.e. something focusable in touch mode).
1820                 if (hasFocus() && focusLayoutRestoreView != null) {
1821                     focusLayoutRestoreView.requestFocus();
1822                 }
1823             }
1824 
1825             // Attempt to restore accessibility focus, if necessary.
1826             if (viewRootImpl != null) {
1827                 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1828                 if (newAccessibilityFocusedView == null) {
1829                     if (accessibilityFocusLayoutRestoreView != null
1830                             && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
1831                         final AccessibilityNodeProvider provider =
1832                                 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1833                         if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
1834                             final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1835                                     accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1836                             provider.performAction(virtualViewId,
1837                                     AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1838                         } else {
1839                             accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1840                         }
1841                     } else if (accessibilityFocusPosition != INVALID_POSITION) {
1842                         // Bound the position within the visible children.
1843                         final int position = MathUtils.constrain(
1844                                 accessibilityFocusPosition - mFirstPosition, 0,
1845                                 getChildCount() - 1);
1846                         final View restoreView = getChildAt(position);
1847                         if (restoreView != null) {
1848                             restoreView.requestAccessibilityFocus();
1849                         }
1850                     }
1851                 }
1852             }
1853 
1854             // Tell focus view we are done mucking with it, if it is still in
1855             // our view hierarchy.
1856             if (focusLayoutRestoreView != null
1857                     && focusLayoutRestoreView.getWindowToken() != null) {
1858                 focusLayoutRestoreView.dispatchFinishTemporaryDetach();
1859             }
1860 
1861             mLayoutMode = LAYOUT_NORMAL;
1862             mDataChanged = false;
1863             if (mPositionScrollAfterLayout != null) {
1864                 post(mPositionScrollAfterLayout);
1865                 mPositionScrollAfterLayout = null;
1866             }
1867             mNeedSync = false;
1868             setNextSelectedPositionInt(mSelectedPosition);
1869 
1870             updateScrollIndicators();
1871 
1872             if (mItemCount > 0) {
1873                 checkSelectionChanged();
1874             }
1875 
1876             invokeOnItemScrollListener();
1877         } finally {
1878             if (mFocusSelector != null) {
1879                 mFocusSelector.onLayoutComplete();
1880             }
1881             if (!blockLayoutRequests) {
1882                 mBlockLayoutRequests = false;
1883             }
1884         }
1885     }
1886 
1887     @Override
trackMotionScroll(int deltaY, int incrementalDeltaY)1888     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
1889         final boolean result = super.trackMotionScroll(deltaY, incrementalDeltaY);
1890         removeUnusedFixedViews(mHeaderViewInfos);
1891         removeUnusedFixedViews(mFooterViewInfos);
1892         return result;
1893     }
1894 
1895     /**
1896      * Header and Footer views are not scrapped / recycled like other views but they are still
1897      * detached from the ViewGroup. After a layout operation, call this method to remove such views.
1898      *
1899      * @param infoList The info list to be traversed
1900      */
removeUnusedFixedViews(@ullable List<FixedViewInfo> infoList)1901     private void removeUnusedFixedViews(@Nullable List<FixedViewInfo> infoList) {
1902         if (infoList == null) {
1903             return;
1904         }
1905         for (int i = infoList.size() - 1; i >= 0; i--) {
1906             final FixedViewInfo fixedViewInfo = infoList.get(i);
1907             final View view = fixedViewInfo.view;
1908             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1909             if (view.getParent() == null && lp != null && lp.recycledHeaderFooter) {
1910                 removeDetachedView(view, false);
1911                 lp.recycledHeaderFooter = false;
1912             }
1913 
1914         }
1915     }
1916 
1917     /**
1918      * @param child a direct child of this list.
1919      * @return Whether child is a header or footer view.
1920      */
isDirectChildHeaderOrFooter(View child)1921     private boolean isDirectChildHeaderOrFooter(View child) {
1922         final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1923         final int numHeaders = headers.size();
1924         for (int i = 0; i < numHeaders; i++) {
1925             if (child == headers.get(i).view) {
1926                 return true;
1927             }
1928         }
1929 
1930         final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1931         final int numFooters = footers.size();
1932         for (int i = 0; i < numFooters; i++) {
1933             if (child == footers.get(i).view) {
1934                 return true;
1935             }
1936         }
1937 
1938         return false;
1939     }
1940 
1941     /**
1942      * Obtains the view and adds it to our list of children. The view can be
1943      * made fresh, converted from an unused view, or used as is if it was in
1944      * the recycle bin.
1945      *
1946      * @param position logical position in the list
1947      * @param y top or bottom edge of the view to add
1948      * @param flow {@code true} to align top edge to y, {@code false} to align
1949      *             bottom edge to y
1950      * @param childrenLeft left edge where children should be positioned
1951      * @param selected {@code true} if the position is selected, {@code false}
1952      *                 otherwise
1953      * @return the view that was added
1954      */
makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected)1955     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1956             boolean selected) {
1957         if (!mDataChanged) {
1958             // Try to use an existing view for this position.
1959             final View activeView = mRecycler.getActiveView(position);
1960             if (activeView != null) {
1961                 // Found it. We're reusing an existing child, so it just needs
1962                 // to be positioned like a scrap view.
1963                 setupChild(activeView, position, y, flow, childrenLeft, selected, true);
1964                 return activeView;
1965             }
1966         }
1967 
1968         // Make a new view for this position, or convert an unused view if
1969         // possible.
1970         final View child = obtainView(position, mIsScrap);
1971 
1972         // This needs to be positioned and measured.
1973         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
1974 
1975         return child;
1976     }
1977 
1978     /**
1979      * Adds a view as a child and make sure it is measured (if necessary) and
1980      * positioned properly.
1981      *
1982      * @param child the view to add
1983      * @param position the position of this child
1984      * @param y the y position relative to which this view will be positioned
1985      * @param flowDown {@code true} to align top edge to y, {@code false} to
1986      *                 align bottom edge to y
1987      * @param childrenLeft left edge where children should be positioned
1988      * @param selected {@code true} if the position is selected, {@code false}
1989      *                 otherwise
1990      * @param isAttachedToWindow {@code true} if the view is already attached
1991      *                           to the window, e.g. whether it was reused, or
1992      *                           {@code false} otherwise
1993      */
setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean isAttachedToWindow)1994     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1995             boolean selected, boolean isAttachedToWindow) {
1996         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
1997 
1998         final boolean isSelected = selected && shouldShowSelector();
1999         final boolean updateChildSelected = isSelected != child.isSelected();
2000         final int mode = mTouchMode;
2001         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
2002                 && mMotionPosition == position;
2003         final boolean updateChildPressed = isPressed != child.isPressed();
2004         final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
2005                 || child.isLayoutRequested();
2006 
2007         // Respect layout params that are already in the view. Otherwise make
2008         // some up...
2009         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
2010         if (p == null) {
2011             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
2012         }
2013         p.viewType = mAdapter.getItemViewType(position);
2014         p.isEnabled = mAdapter.isEnabled(position);
2015 
2016         // Set up view state before attaching the view, since we may need to
2017         // rely on the jumpDrawablesToCurrentState() call that occurs as part
2018         // of view attachment.
2019         if (updateChildSelected) {
2020             child.setSelected(isSelected);
2021         }
2022 
2023         if (updateChildPressed) {
2024             child.setPressed(isPressed);
2025         }
2026 
2027         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
2028             if (child instanceof Checkable) {
2029                 ((Checkable) child).setChecked(mCheckStates.get(position));
2030             } else if (getContext().getApplicationInfo().targetSdkVersion
2031                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
2032                 child.setActivated(mCheckStates.get(position));
2033             }
2034         }
2035 
2036         if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
2037                 && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
2038             attachViewToParent(child, flowDown ? -1 : 0, p);
2039 
2040             // If the view was previously attached for a different position,
2041             // then manually jump the drawables.
2042             if (isAttachedToWindow
2043                     && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
2044                             != position) {
2045                 child.jumpDrawablesToCurrentState();
2046             }
2047         } else {
2048             p.forceAdd = false;
2049             if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
2050                 p.recycledHeaderFooter = true;
2051             }
2052             addViewInLayout(child, flowDown ? -1 : 0, p, true);
2053             // add view in layout will reset the RTL properties. We have to re-resolve them
2054             child.resolveRtlPropertiesIfNeeded();
2055         }
2056 
2057         if (needToMeasure) {
2058             final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2059                     mListPadding.left + mListPadding.right, p.width);
2060             final int lpHeight = p.height;
2061             final int childHeightSpec;
2062             if (lpHeight > 0) {
2063                 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2064             } else {
2065                 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2066                         MeasureSpec.UNSPECIFIED);
2067             }
2068             child.measure(childWidthSpec, childHeightSpec);
2069         } else {
2070             cleanupLayoutState(child);
2071         }
2072 
2073         final int w = child.getMeasuredWidth();
2074         final int h = child.getMeasuredHeight();
2075         final int childTop = flowDown ? y : y - h;
2076 
2077         if (needToMeasure) {
2078             final int childRight = childrenLeft + w;
2079             final int childBottom = childTop + h;
2080             child.layout(childrenLeft, childTop, childRight, childBottom);
2081         } else {
2082             child.offsetLeftAndRight(childrenLeft - child.getLeft());
2083             child.offsetTopAndBottom(childTop - child.getTop());
2084         }
2085 
2086         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
2087             child.setDrawingCacheEnabled(true);
2088         }
2089 
2090         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2091     }
2092 
2093     @Override
canAnimate()2094     protected boolean canAnimate() {
2095         return super.canAnimate() && mItemCount > 0;
2096     }
2097 
2098     /**
2099      * Sets the currently selected item. If in touch mode, the item will not be selected
2100      * but it will still be positioned appropriately. If the specified selection position
2101      * is less than 0, then the item at position 0 will be selected.
2102      *
2103      * @param position Index (starting at 0) of the data item to be selected.
2104      */
2105     @Override
setSelection(int position)2106     public void setSelection(int position) {
2107         setSelectionFromTop(position, 0);
2108     }
2109 
2110     /**
2111      * Makes the item at the supplied position selected.
2112      *
2113      * @param position the position of the item to select
2114      */
2115     @Override
setSelectionInt(int position)2116     void setSelectionInt(int position) {
2117         setNextSelectedPositionInt(position);
2118         boolean awakeScrollbars = false;
2119 
2120         final int selectedPosition = mSelectedPosition;
2121 
2122         if (selectedPosition >= 0) {
2123             if (position == selectedPosition - 1) {
2124                 awakeScrollbars = true;
2125             } else if (position == selectedPosition + 1) {
2126                 awakeScrollbars = true;
2127             }
2128         }
2129 
2130         if (mPositionScroller != null) {
2131             mPositionScroller.stop();
2132         }
2133 
2134         layoutChildren();
2135 
2136         if (awakeScrollbars) {
2137             awakenScrollBars();
2138         }
2139     }
2140 
2141     /**
2142      * Find a position that can be selected (i.e., is not a separator).
2143      *
2144      * @param position The starting position to look at.
2145      * @param lookDown Whether to look down for other positions.
2146      * @return The next selectable position starting at position and then searching either up or
2147      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
2148      */
2149     @Override
lookForSelectablePosition(int position, boolean lookDown)2150     int lookForSelectablePosition(int position, boolean lookDown) {
2151         final ListAdapter adapter = mAdapter;
2152         if (adapter == null || isInTouchMode()) {
2153             return INVALID_POSITION;
2154         }
2155 
2156         final int count = adapter.getCount();
2157         if (!mAreAllItemsSelectable) {
2158             if (lookDown) {
2159                 position = Math.max(0, position);
2160                 while (position < count && !adapter.isEnabled(position)) {
2161                     position++;
2162                 }
2163             } else {
2164                 position = Math.min(position, count - 1);
2165                 while (position >= 0 && !adapter.isEnabled(position)) {
2166                     position--;
2167                 }
2168             }
2169         }
2170 
2171         if (position < 0 || position >= count) {
2172             return INVALID_POSITION;
2173         }
2174 
2175         return position;
2176     }
2177 
2178     /**
2179      * Find a position that can be selected (i.e., is not a separator). If there
2180      * are no selectable positions in the specified direction from the starting
2181      * position, searches in the opposite direction from the starting position
2182      * to the current position.
2183      *
2184      * @param current the current position
2185      * @param position the starting position
2186      * @param lookDown whether to look down for other positions
2187      * @return the next selectable position, or {@link #INVALID_POSITION} if
2188      *         nothing can be found
2189      */
lookForSelectablePositionAfter(int current, int position, boolean lookDown)2190     int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2191         final ListAdapter adapter = mAdapter;
2192         if (adapter == null || isInTouchMode()) {
2193             return INVALID_POSITION;
2194         }
2195 
2196         // First check after the starting position in the specified direction.
2197         final int after = lookForSelectablePosition(position, lookDown);
2198         if (after != INVALID_POSITION) {
2199             return after;
2200         }
2201 
2202         // Then check between the starting position and the current position.
2203         final int count = adapter.getCount();
2204         current = MathUtils.constrain(current, -1, count - 1);
2205         if (lookDown) {
2206             position = Math.min(position - 1, count - 1);
2207             while ((position > current) && !adapter.isEnabled(position)) {
2208                 position--;
2209             }
2210             if (position <= current) {
2211                 return INVALID_POSITION;
2212             }
2213         } else {
2214             position = Math.max(0, position + 1);
2215             while ((position < current) && !adapter.isEnabled(position)) {
2216                 position++;
2217             }
2218             if (position >= current) {
2219                 return INVALID_POSITION;
2220             }
2221         }
2222 
2223         return position;
2224     }
2225 
2226     /**
2227      * setSelectionAfterHeaderView set the selection to be the first list item
2228      * after the header views.
2229      */
setSelectionAfterHeaderView()2230     public void setSelectionAfterHeaderView() {
2231         final int count = getHeaderViewsCount();
2232         if (count > 0) {
2233             mNextSelectedPosition = 0;
2234             return;
2235         }
2236 
2237         if (mAdapter != null) {
2238             setSelection(count);
2239         } else {
2240             mNextSelectedPosition = count;
2241             mLayoutMode = LAYOUT_SET_SELECTION;
2242         }
2243 
2244     }
2245 
2246     @Override
dispatchKeyEvent(KeyEvent event)2247     public boolean dispatchKeyEvent(KeyEvent event) {
2248         // Dispatch in the normal way
2249         boolean handled = super.dispatchKeyEvent(event);
2250         if (!handled) {
2251             // If we didn't handle it...
2252             View focused = getFocusedChild();
2253             if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2254                 // ... and our focused child didn't handle it
2255                 // ... give it to ourselves so we can scroll if necessary
2256                 handled = onKeyDown(event.getKeyCode(), event);
2257             }
2258         }
2259         return handled;
2260     }
2261 
2262     @Override
onKeyDown(int keyCode, KeyEvent event)2263     public boolean onKeyDown(int keyCode, KeyEvent event) {
2264         return commonKey(keyCode, 1, event);
2265     }
2266 
2267     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)2268     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2269         return commonKey(keyCode, repeatCount, event);
2270     }
2271 
2272     @Override
onKeyUp(int keyCode, KeyEvent event)2273     public boolean onKeyUp(int keyCode, KeyEvent event) {
2274         return commonKey(keyCode, 1, event);
2275     }
2276 
commonKey(int keyCode, int count, KeyEvent event)2277     private boolean commonKey(int keyCode, int count, KeyEvent event) {
2278         if (mAdapter == null || !isAttachedToWindow()) {
2279             return false;
2280         }
2281 
2282         if (mDataChanged) {
2283             layoutChildren();
2284         }
2285 
2286         boolean handled = false;
2287         int action = event.getAction();
2288         if (KeyEvent.isConfirmKey(keyCode)
2289                 && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
2290             handled = resurrectSelectionIfNeeded();
2291             if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
2292                 keyPressed();
2293                 handled = true;
2294             }
2295         }
2296 
2297 
2298         if (!handled && action != KeyEvent.ACTION_UP) {
2299             switch (keyCode) {
2300             case KeyEvent.KEYCODE_DPAD_UP:
2301                 if (event.hasNoModifiers()) {
2302                     handled = resurrectSelectionIfNeeded();
2303                     if (!handled) {
2304                         while (count-- > 0) {
2305                             if (arrowScroll(FOCUS_UP)) {
2306                                 handled = true;
2307                             } else {
2308                                 break;
2309                             }
2310                         }
2311                     }
2312                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2313                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2314                 }
2315                 break;
2316 
2317             case KeyEvent.KEYCODE_DPAD_DOWN:
2318                 if (event.hasNoModifiers()) {
2319                     handled = resurrectSelectionIfNeeded();
2320                     if (!handled) {
2321                         while (count-- > 0) {
2322                             if (arrowScroll(FOCUS_DOWN)) {
2323                                 handled = true;
2324                             } else {
2325                                 break;
2326                             }
2327                         }
2328                     }
2329                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2330                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2331                 }
2332                 break;
2333 
2334             case KeyEvent.KEYCODE_DPAD_LEFT:
2335                 if (event.hasNoModifiers()) {
2336                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2337                 }
2338                 break;
2339 
2340             case KeyEvent.KEYCODE_DPAD_RIGHT:
2341                 if (event.hasNoModifiers()) {
2342                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2343                 }
2344                 break;
2345 
2346             case KeyEvent.KEYCODE_PAGE_UP:
2347                 if (event.hasNoModifiers()) {
2348                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
2349                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2350                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2351                 }
2352                 break;
2353 
2354             case KeyEvent.KEYCODE_PAGE_DOWN:
2355                 if (event.hasNoModifiers()) {
2356                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
2357                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2358                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2359                 }
2360                 break;
2361 
2362             case KeyEvent.KEYCODE_MOVE_HOME:
2363                 if (event.hasNoModifiers()) {
2364                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2365                 }
2366                 break;
2367 
2368             case KeyEvent.KEYCODE_MOVE_END:
2369                 if (event.hasNoModifiers()) {
2370                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2371                 }
2372                 break;
2373 
2374             case KeyEvent.KEYCODE_TAB:
2375                 // This creates an asymmetry in TAB navigation order. At some
2376                 // point in the future we may decide that it's preferable to
2377                 // force the list selection to the top or bottom when receiving
2378                 // TAB focus from another widget, but for now this is adequate.
2379                 if (event.hasNoModifiers()) {
2380                     handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2381                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2382                     handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
2383                 }
2384                 break;
2385             }
2386         }
2387 
2388         if (handled) {
2389             return true;
2390         }
2391 
2392         if (sendToTextFilter(keyCode, count, event)) {
2393             return true;
2394         }
2395 
2396         switch (action) {
2397             case KeyEvent.ACTION_DOWN:
2398                 return super.onKeyDown(keyCode, event);
2399 
2400             case KeyEvent.ACTION_UP:
2401                 return super.onKeyUp(keyCode, event);
2402 
2403             case KeyEvent.ACTION_MULTIPLE:
2404                 return super.onKeyMultiple(keyCode, count, event);
2405 
2406             default: // shouldn't happen
2407                 return false;
2408         }
2409     }
2410 
2411     /**
2412      * Scrolls up or down by the number of items currently present on screen.
2413      *
2414      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2415      * @return whether selection was moved
2416      */
pageScroll(int direction)2417     boolean pageScroll(int direction) {
2418         final int nextPage;
2419         final boolean down;
2420 
2421         if (direction == FOCUS_UP) {
2422             nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2423             down = false;
2424         } else if (direction == FOCUS_DOWN) {
2425             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2426             down = true;
2427         } else {
2428             return false;
2429         }
2430 
2431         if (nextPage >= 0) {
2432             final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
2433             if (position >= 0) {
2434                 mLayoutMode = LAYOUT_SPECIFIC;
2435                 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2436 
2437                 if (down && (position > (mItemCount - getChildCount()))) {
2438                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
2439                 }
2440 
2441                 if (!down && (position < getChildCount())) {
2442                     mLayoutMode = LAYOUT_FORCE_TOP;
2443                 }
2444 
2445                 setSelectionInt(position);
2446                 invokeOnItemScrollListener();
2447                 if (!awakenScrollBars()) {
2448                     invalidate();
2449                 }
2450 
2451                 return true;
2452             }
2453         }
2454 
2455         return false;
2456     }
2457 
2458     /**
2459      * Go to the last or first item if possible (not worrying about panning
2460      * across or navigating within the internal focus of the currently selected
2461      * item.)
2462      *
2463      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2464      * @return whether selection was moved
2465      */
fullScroll(int direction)2466     boolean fullScroll(int direction) {
2467         boolean moved = false;
2468         if (direction == FOCUS_UP) {
2469             if (mSelectedPosition != 0) {
2470                 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
2471                 if (position >= 0) {
2472                     mLayoutMode = LAYOUT_FORCE_TOP;
2473                     setSelectionInt(position);
2474                     invokeOnItemScrollListener();
2475                 }
2476                 moved = true;
2477             }
2478         } else if (direction == FOCUS_DOWN) {
2479             final int lastItem = (mItemCount - 1);
2480             if (mSelectedPosition < lastItem) {
2481                 final int position = lookForSelectablePositionAfter(
2482                         mSelectedPosition, lastItem, false);
2483                 if (position >= 0) {
2484                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
2485                     setSelectionInt(position);
2486                     invokeOnItemScrollListener();
2487                 }
2488                 moved = true;
2489             }
2490         }
2491 
2492         if (moved && !awakenScrollBars()) {
2493             awakenScrollBars();
2494             invalidate();
2495         }
2496 
2497         return moved;
2498     }
2499 
2500     /**
2501      * To avoid horizontal focus searches changing the selected item, we
2502      * manually focus search within the selected item (as applicable), and
2503      * prevent focus from jumping to something within another item.
2504      * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2505      * @return Whether this consumes the key event.
2506      */
handleHorizontalFocusWithinListItem(int direction)2507     private boolean handleHorizontalFocusWithinListItem(int direction) {
2508         if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
2509             throw new IllegalArgumentException("direction must be one of"
2510                     + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
2511         }
2512 
2513         final int numChildren = getChildCount();
2514         if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2515             final View selectedView = getSelectedView();
2516             if (selectedView != null && selectedView.hasFocus() &&
2517                     selectedView instanceof ViewGroup) {
2518 
2519                 final View currentFocus = selectedView.findFocus();
2520                 final View nextFocus = FocusFinder.getInstance().findNextFocus(
2521                         (ViewGroup) selectedView, currentFocus, direction);
2522                 if (nextFocus != null) {
2523                     // do the math to get interesting rect in next focus' coordinates
2524                     Rect focusedRect = mTempRect;
2525                     if (currentFocus != null) {
2526                         currentFocus.getFocusedRect(focusedRect);
2527                         offsetDescendantRectToMyCoords(currentFocus, focusedRect);
2528                         offsetRectIntoDescendantCoords(nextFocus, focusedRect);
2529                     } else {
2530                         focusedRect = null;
2531                     }
2532                     if (nextFocus.requestFocus(direction, focusedRect)) {
2533                         return true;
2534                     }
2535                 }
2536                 // we are blocking the key from being handled (by returning true)
2537                 // if the global result is going to be some other view within this
2538                 // list.  this is to acheive the overall goal of having
2539                 // horizontal d-pad navigation remain in the current item.
2540                 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2541                         (ViewGroup) getRootView(), currentFocus, direction);
2542                 if (globalNextFocus != null) {
2543                     return isViewAncestorOf(globalNextFocus, this);
2544                 }
2545             }
2546         }
2547         return false;
2548     }
2549 
2550     /**
2551      * Scrolls to the next or previous item if possible.
2552      *
2553      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2554      *
2555      * @return whether selection was moved
2556      */
arrowScroll(int direction)2557     boolean arrowScroll(int direction) {
2558         try {
2559             mInLayout = true;
2560             final boolean handled = arrowScrollImpl(direction);
2561             if (handled) {
2562                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2563             }
2564             return handled;
2565         } finally {
2566             mInLayout = false;
2567         }
2568     }
2569 
2570     /**
2571      * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
2572      * to move to. This return a position in the direction given if the selected item
2573      * is fully visible.
2574      *
2575      * @param selectedView Current selected view to move from
2576      * @param selectedPos Current selected position to move from
2577      * @param direction Direction to move in
2578      * @return Desired selected position after moving in the given direction
2579      */
nextSelectedPositionForDirection( View selectedView, int selectedPos, int direction)2580     private final int nextSelectedPositionForDirection(
2581             View selectedView, int selectedPos, int direction) {
2582         int nextSelected;
2583 
2584         if (direction == View.FOCUS_DOWN) {
2585             final int listBottom = getHeight() - mListPadding.bottom;
2586             if (selectedView != null && selectedView.getBottom() <= listBottom) {
2587                 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2588                         selectedPos + 1 :
2589                         mFirstPosition;
2590             } else {
2591                 return INVALID_POSITION;
2592             }
2593         } else {
2594             final int listTop = mListPadding.top;
2595             if (selectedView != null && selectedView.getTop() >= listTop) {
2596                 final int lastPos = mFirstPosition + getChildCount() - 1;
2597                 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2598                         selectedPos - 1 :
2599                         lastPos;
2600             } else {
2601                 return INVALID_POSITION;
2602             }
2603         }
2604 
2605         if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2606             return INVALID_POSITION;
2607         }
2608         return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2609     }
2610 
2611     /**
2612      * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
2613      * whether there are focusable items etc.
2614      *
2615      * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2616      * @return Whether any scrolling, selection or focus change occured.
2617      */
arrowScrollImpl(int direction)2618     private boolean arrowScrollImpl(int direction) {
2619         if (getChildCount() <= 0) {
2620             return false;
2621         }
2622 
2623         View selectedView = getSelectedView();
2624         int selectedPos = mSelectedPosition;
2625 
2626         int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
2627         int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2628 
2629         // if we are moving focus, we may OVERRIDE the default behavior
2630         final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2631         if (focusResult != null) {
2632             nextSelectedPosition = focusResult.getSelectedPosition();
2633             amountToScroll = focusResult.getAmountToScroll();
2634         }
2635 
2636         boolean needToRedraw = focusResult != null;
2637         if (nextSelectedPosition != INVALID_POSITION) {
2638             handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2639             setSelectedPositionInt(nextSelectedPosition);
2640             setNextSelectedPositionInt(nextSelectedPosition);
2641             selectedView = getSelectedView();
2642             selectedPos = nextSelectedPosition;
2643             if (mItemsCanFocus && focusResult == null) {
2644                 // there was no new view found to take focus, make sure we
2645                 // don't leave focus with the old selection
2646                 final View focused = getFocusedChild();
2647                 if (focused != null) {
2648                     focused.clearFocus();
2649                 }
2650             }
2651             needToRedraw = true;
2652             checkSelectionChanged();
2653         }
2654 
2655         if (amountToScroll > 0) {
2656             scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2657             needToRedraw = true;
2658         }
2659 
2660         // if we didn't find a new focusable, make sure any existing focused
2661         // item that was panned off screen gives up focus.
2662         if (mItemsCanFocus && (focusResult == null)
2663                 && selectedView != null && selectedView.hasFocus()) {
2664             final View focused = selectedView.findFocus();
2665             if (focused != null) {
2666                 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2667                     focused.clearFocus();
2668                 }
2669             }
2670         }
2671 
2672         // if  the current selection is panned off, we need to remove the selection
2673         if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2674                 && !isViewAncestorOf(selectedView, this)) {
2675             selectedView = null;
2676             hideSelector();
2677 
2678             // but we don't want to set the ressurect position (that would make subsequent
2679             // unhandled key events bring back the item we just scrolled off!)
2680             mResurrectToPosition = INVALID_POSITION;
2681         }
2682 
2683         if (needToRedraw) {
2684             if (selectedView != null) {
2685                 positionSelectorLikeFocus(selectedPos, selectedView);
2686                 mSelectedTop = selectedView.getTop();
2687             }
2688             if (!awakenScrollBars()) {
2689                 invalidate();
2690             }
2691             invokeOnItemScrollListener();
2692             return true;
2693         }
2694 
2695         return false;
2696     }
2697 
2698     /**
2699      * When selection changes, it is possible that the previously selected or the
2700      * next selected item will change its size.  If so, we need to offset some folks,
2701      * and re-layout the items as appropriate.
2702      *
2703      * @param selectedView The currently selected view (before changing selection).
2704      *   should be <code>null</code> if there was no previous selection.
2705      * @param direction Either {@link android.view.View#FOCUS_UP} or
2706      *        {@link android.view.View#FOCUS_DOWN}.
2707      * @param newSelectedPosition The position of the next selection.
2708      * @param newFocusAssigned whether new focus was assigned.  This matters because
2709      *        when something has focus, we don't want to show selection (ugh).
2710      */
handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition, boolean newFocusAssigned)2711     private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2712             boolean newFocusAssigned) {
2713         if (newSelectedPosition == INVALID_POSITION) {
2714             throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2715         }
2716 
2717         // whether or not we are moving down or up, we want to preserve the
2718         // top of whatever view is on top:
2719         // - moving down: the view that had selection
2720         // - moving up: the view that is getting selection
2721         View topView;
2722         View bottomView;
2723         int topViewIndex, bottomViewIndex;
2724         boolean topSelected = false;
2725         final int selectedIndex = mSelectedPosition - mFirstPosition;
2726         final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2727         if (direction == View.FOCUS_UP) {
2728             topViewIndex = nextSelectedIndex;
2729             bottomViewIndex = selectedIndex;
2730             topView = getChildAt(topViewIndex);
2731             bottomView = selectedView;
2732             topSelected = true;
2733         } else {
2734             topViewIndex = selectedIndex;
2735             bottomViewIndex = nextSelectedIndex;
2736             topView = selectedView;
2737             bottomView = getChildAt(bottomViewIndex);
2738         }
2739 
2740         final int numChildren = getChildCount();
2741 
2742         // start with top view: is it changing size?
2743         if (topView != null) {
2744             topView.setSelected(!newFocusAssigned && topSelected);
2745             measureAndAdjustDown(topView, topViewIndex, numChildren);
2746         }
2747 
2748         // is the bottom view changing size?
2749         if (bottomView != null) {
2750             bottomView.setSelected(!newFocusAssigned && !topSelected);
2751             measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2752         }
2753     }
2754 
2755     /**
2756      * Re-measure a child, and if its height changes, lay it out preserving its
2757      * top, and adjust the children below it appropriately.
2758      * @param child The child
2759      * @param childIndex The view group index of the child.
2760      * @param numChildren The number of children in the view group.
2761      */
measureAndAdjustDown(View child, int childIndex, int numChildren)2762     private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2763         int oldHeight = child.getHeight();
2764         measureItem(child);
2765         if (child.getMeasuredHeight() != oldHeight) {
2766             // lay out the view, preserving its top
2767             relayoutMeasuredItem(child);
2768 
2769             // adjust views below appropriately
2770             final int heightDelta = child.getMeasuredHeight() - oldHeight;
2771             for (int i = childIndex + 1; i < numChildren; i++) {
2772                 getChildAt(i).offsetTopAndBottom(heightDelta);
2773             }
2774         }
2775     }
2776 
2777     /**
2778      * Measure a particular list child.
2779      * TODO: unify with setUpChild.
2780      * @param child The child.
2781      */
measureItem(View child)2782     private void measureItem(View child) {
2783         ViewGroup.LayoutParams p = child.getLayoutParams();
2784         if (p == null) {
2785             p = new ViewGroup.LayoutParams(
2786                     ViewGroup.LayoutParams.MATCH_PARENT,
2787                     ViewGroup.LayoutParams.WRAP_CONTENT);
2788         }
2789 
2790         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2791                 mListPadding.left + mListPadding.right, p.width);
2792         int lpHeight = p.height;
2793         int childHeightSpec;
2794         if (lpHeight > 0) {
2795             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2796         } else {
2797             childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2798                     MeasureSpec.UNSPECIFIED);
2799         }
2800         child.measure(childWidthSpec, childHeightSpec);
2801     }
2802 
2803     /**
2804      * Layout a child that has been measured, preserving its top position.
2805      * TODO: unify with setUpChild.
2806      * @param child The child.
2807      */
relayoutMeasuredItem(View child)2808     private void relayoutMeasuredItem(View child) {
2809         final int w = child.getMeasuredWidth();
2810         final int h = child.getMeasuredHeight();
2811         final int childLeft = mListPadding.left;
2812         final int childRight = childLeft + w;
2813         final int childTop = child.getTop();
2814         final int childBottom = childTop + h;
2815         child.layout(childLeft, childTop, childRight, childBottom);
2816     }
2817 
2818     /**
2819      * @return The amount to preview next items when arrow srolling.
2820      */
getArrowScrollPreviewLength()2821     private int getArrowScrollPreviewLength() {
2822         return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2823     }
2824 
2825     /**
2826      * Determine how much we need to scroll in order to get the next selected view
2827      * visible, with a fading edge showing below as applicable.  The amount is
2828      * capped at {@link #getMaxScrollAmount()} .
2829      *
2830      * @param direction either {@link android.view.View#FOCUS_UP} or
2831      *        {@link android.view.View#FOCUS_DOWN}.
2832      * @param nextSelectedPosition The position of the next selection, or
2833      *        {@link #INVALID_POSITION} if there is no next selectable position
2834      * @return The amount to scroll. Note: this is always positive!  Direction
2835      *         needs to be taken into account when actually scrolling.
2836      */
amountToScroll(int direction, int nextSelectedPosition)2837     private int amountToScroll(int direction, int nextSelectedPosition) {
2838         final int listBottom = getHeight() - mListPadding.bottom;
2839         final int listTop = mListPadding.top;
2840 
2841         int numChildren = getChildCount();
2842 
2843         if (direction == View.FOCUS_DOWN) {
2844             int indexToMakeVisible = numChildren - 1;
2845             if (nextSelectedPosition != INVALID_POSITION) {
2846                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2847             }
2848             while (numChildren <= indexToMakeVisible) {
2849                 // Child to view is not attached yet.
2850                 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2851                 numChildren++;
2852             }
2853             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2854             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2855 
2856             int goalBottom = listBottom;
2857             if (positionToMakeVisible < mItemCount - 1) {
2858                 goalBottom -= getArrowScrollPreviewLength();
2859             }
2860 
2861             if (viewToMakeVisible.getBottom() <= goalBottom) {
2862                 // item is fully visible.
2863                 return 0;
2864             }
2865 
2866             if (nextSelectedPosition != INVALID_POSITION
2867                     && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2868                 // item already has enough of it visible, changing selection is good enough
2869                 return 0;
2870             }
2871 
2872             int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2873 
2874             if ((mFirstPosition + numChildren) == mItemCount) {
2875                 // last is last in list -> make sure we don't scroll past it
2876                 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2877                 amountToScroll = Math.min(amountToScroll, max);
2878             }
2879 
2880             return Math.min(amountToScroll, getMaxScrollAmount());
2881         } else {
2882             int indexToMakeVisible = 0;
2883             if (nextSelectedPosition != INVALID_POSITION) {
2884                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2885             }
2886             while (indexToMakeVisible < 0) {
2887                 // Child to view is not attached yet.
2888                 addViewAbove(getChildAt(0), mFirstPosition);
2889                 mFirstPosition--;
2890                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2891             }
2892             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2893             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2894             int goalTop = listTop;
2895             if (positionToMakeVisible > 0) {
2896                 goalTop += getArrowScrollPreviewLength();
2897             }
2898             if (viewToMakeVisible.getTop() >= goalTop) {
2899                 // item is fully visible.
2900                 return 0;
2901             }
2902 
2903             if (nextSelectedPosition != INVALID_POSITION &&
2904                     (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2905                 // item already has enough of it visible, changing selection is good enough
2906                 return 0;
2907             }
2908 
2909             int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2910             if (mFirstPosition == 0) {
2911                 // first is first in list -> make sure we don't scroll past it
2912                 final int max = listTop - getChildAt(0).getTop();
2913                 amountToScroll = Math.min(amountToScroll,  max);
2914             }
2915             return Math.min(amountToScroll, getMaxScrollAmount());
2916         }
2917     }
2918 
2919     /**
2920      * Holds results of focus aware arrow scrolling.
2921      */
2922     static private class ArrowScrollFocusResult {
2923         private int mSelectedPosition;
2924         private int mAmountToScroll;
2925 
2926         /**
2927          * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2928          */
populate(int selectedPosition, int amountToScroll)2929         void populate(int selectedPosition, int amountToScroll) {
2930             mSelectedPosition = selectedPosition;
2931             mAmountToScroll = amountToScroll;
2932         }
2933 
getSelectedPosition()2934         public int getSelectedPosition() {
2935             return mSelectedPosition;
2936         }
2937 
getAmountToScroll()2938         public int getAmountToScroll() {
2939             return mAmountToScroll;
2940         }
2941     }
2942 
2943     /**
2944      * @param direction either {@link android.view.View#FOCUS_UP} or
2945      *        {@link android.view.View#FOCUS_DOWN}.
2946      * @return The position of the next selectable position of the views that
2947      *         are currently visible, taking into account the fact that there might
2948      *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
2949      *         selectable view on screen in the given direction.
2950      */
lookForSelectablePositionOnScreen(int direction)2951     private int lookForSelectablePositionOnScreen(int direction) {
2952         final int firstPosition = mFirstPosition;
2953         if (direction == View.FOCUS_DOWN) {
2954             int startPos = (mSelectedPosition != INVALID_POSITION) ?
2955                     mSelectedPosition + 1 :
2956                     firstPosition;
2957             if (startPos >= mAdapter.getCount()) {
2958                 return INVALID_POSITION;
2959             }
2960             if (startPos < firstPosition) {
2961                 startPos = firstPosition;
2962             }
2963 
2964             final int lastVisiblePos = getLastVisiblePosition();
2965             final ListAdapter adapter = getAdapter();
2966             for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2967                 if (adapter.isEnabled(pos)
2968                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2969                     return pos;
2970                 }
2971             }
2972         } else {
2973             int last = firstPosition + getChildCount() - 1;
2974             int startPos = (mSelectedPosition != INVALID_POSITION) ?
2975                     mSelectedPosition - 1 :
2976                     firstPosition + getChildCount() - 1;
2977             if (startPos < 0 || startPos >= mAdapter.getCount()) {
2978                 return INVALID_POSITION;
2979             }
2980             if (startPos > last) {
2981                 startPos = last;
2982             }
2983 
2984             final ListAdapter adapter = getAdapter();
2985             for (int pos = startPos; pos >= firstPosition; pos--) {
2986                 if (adapter.isEnabled(pos)
2987                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2988                     return pos;
2989                 }
2990             }
2991         }
2992         return INVALID_POSITION;
2993     }
2994 
2995     /**
2996      * Do an arrow scroll based on focus searching.  If a new view is
2997      * given focus, return the selection delta and amount to scroll via
2998      * an {@link ArrowScrollFocusResult}, otherwise, return null.
2999      *
3000      * @param direction either {@link android.view.View#FOCUS_UP} or
3001      *        {@link android.view.View#FOCUS_DOWN}.
3002      * @return The result if focus has changed, or <code>null</code>.
3003      */
arrowScrollFocused(final int direction)3004     private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
3005         final View selectedView = getSelectedView();
3006         View newFocus;
3007         if (selectedView != null && selectedView.hasFocus()) {
3008             View oldFocus = selectedView.findFocus();
3009             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
3010         } else {
3011             if (direction == View.FOCUS_DOWN) {
3012                 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
3013                 final int listTop = mListPadding.top +
3014                         (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3015                 final int ySearchPoint =
3016                         (selectedView != null && selectedView.getTop() > listTop) ?
3017                                 selectedView.getTop() :
3018                                 listTop;
3019                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3020             } else {
3021                 final boolean bottomFadingEdgeShowing =
3022                         (mFirstPosition + getChildCount() - 1) < mItemCount;
3023                 final int listBottom = getHeight() - mListPadding.bottom -
3024                         (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3025                 final int ySearchPoint =
3026                         (selectedView != null && selectedView.getBottom() < listBottom) ?
3027                                 selectedView.getBottom() :
3028                                 listBottom;
3029                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3030             }
3031             newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
3032         }
3033 
3034         if (newFocus != null) {
3035             final int positionOfNewFocus = positionOfNewFocus(newFocus);
3036 
3037             // if the focus change is in a different new position, make sure
3038             // we aren't jumping over another selectable position
3039             if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
3040                 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
3041                 if (selectablePosition != INVALID_POSITION &&
3042                         ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
3043                         (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
3044                     return null;
3045                 }
3046             }
3047 
3048             int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
3049 
3050             final int maxScrollAmount = getMaxScrollAmount();
3051             if (focusScroll < maxScrollAmount) {
3052                 // not moving too far, safe to give next view focus
3053                 newFocus.requestFocus(direction);
3054                 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
3055                 return mArrowScrollFocusResult;
3056             } else if (distanceToView(newFocus) < maxScrollAmount){
3057                 // Case to consider:
3058                 // too far to get entire next focusable on screen, but by going
3059                 // max scroll amount, we are getting it at least partially in view,
3060                 // so give it focus and scroll the max ammount.
3061                 newFocus.requestFocus(direction);
3062                 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
3063                 return mArrowScrollFocusResult;
3064             }
3065         }
3066         return null;
3067     }
3068 
3069     /**
3070      * @param newFocus The view that would have focus.
3071      * @return the position that contains newFocus
3072      */
3073     private int positionOfNewFocus(View newFocus) {
3074         final int numChildren = getChildCount();
3075         for (int i = 0; i < numChildren; i++) {
3076             final View child = getChildAt(i);
3077             if (isViewAncestorOf(newFocus, child)) {
3078                 return mFirstPosition + i;
3079             }
3080         }
3081         throw new IllegalArgumentException("newFocus is not a child of any of the"
3082                 + " children of the list!");
3083     }
3084 
3085     /**
3086      * Return true if child is an ancestor of parent, (or equal to the parent).
3087      */
3088     private boolean isViewAncestorOf(View child, View parent) {
3089         if (child == parent) {
3090             return true;
3091         }
3092 
3093         final ViewParent theParent = child.getParent();
3094         return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
3095     }
3096 
3097     /**
3098      * Determine how much we need to scroll in order to get newFocus in view.
3099      * @param direction either {@link android.view.View#FOCUS_UP} or
3100      *        {@link android.view.View#FOCUS_DOWN}.
3101      * @param newFocus The view that would take focus.
3102      * @param positionOfNewFocus The position of the list item containing newFocus
3103      * @return The amount to scroll.  Note: this is always positive!  Direction
3104      *   needs to be taken into account when actually scrolling.
3105      */
3106     private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
3107         int amountToScroll = 0;
3108         newFocus.getDrawingRect(mTempRect);
3109         offsetDescendantRectToMyCoords(newFocus, mTempRect);
3110         if (direction == View.FOCUS_UP) {
3111             if (mTempRect.top < mListPadding.top) {
3112                 amountToScroll = mListPadding.top - mTempRect.top;
3113                 if (positionOfNewFocus > 0) {
3114                     amountToScroll += getArrowScrollPreviewLength();
3115                 }
3116             }
3117         } else {
3118             final int listBottom = getHeight() - mListPadding.bottom;
3119             if (mTempRect.bottom > listBottom) {
3120                 amountToScroll = mTempRect.bottom - listBottom;
3121                 if (positionOfNewFocus < mItemCount - 1) {
3122                     amountToScroll += getArrowScrollPreviewLength();
3123                 }
3124             }
3125         }
3126         return amountToScroll;
3127     }
3128 
3129     /**
3130      * Determine the distance to the nearest edge of a view in a particular
3131      * direction.
3132      *
3133      * @param descendant A descendant of this list.
3134      * @return The distance, or 0 if the nearest edge is already on screen.
3135      */
3136     private int distanceToView(View descendant) {
3137         int distance = 0;
3138         descendant.getDrawingRect(mTempRect);
3139         offsetDescendantRectToMyCoords(descendant, mTempRect);
3140         final int listBottom = mBottom - mTop - mListPadding.bottom;
3141         if (mTempRect.bottom < mListPadding.top) {
3142             distance = mListPadding.top - mTempRect.bottom;
3143         } else if (mTempRect.top > listBottom) {
3144             distance = mTempRect.top - listBottom;
3145         }
3146         return distance;
3147     }
3148 
3149 
3150     /**
3151      * Scroll the children by amount, adding a view at the end and removing
3152      * views that fall off as necessary.
3153      *
3154      * @param amount The amount (positive or negative) to scroll.
3155      */
3156     private void scrollListItemsBy(int amount) {
3157         offsetChildrenTopAndBottom(amount);
3158 
3159         final int listBottom = getHeight() - mListPadding.bottom;
3160         final int listTop = mListPadding.top;
3161         final AbsListView.RecycleBin recycleBin = mRecycler;
3162 
3163         if (amount < 0) {
3164             // shifted items up
3165 
3166             // may need to pan views into the bottom space
3167             int numChildren = getChildCount();
3168             View last = getChildAt(numChildren - 1);
3169             while (last.getBottom() < listBottom) {
3170                 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3171                 if (lastVisiblePosition < mItemCount - 1) {
3172                     last = addViewBelow(last, lastVisiblePosition);
3173                     numChildren++;
3174                 } else {
3175                     break;
3176                 }
3177             }
3178 
3179             // may have brought in the last child of the list that is skinnier
3180             // than the fading edge, thereby leaving space at the end.  need
3181             // to shift back
3182             if (last.getBottom() < listBottom) {
3183                 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3184             }
3185 
3186             // top views may be panned off screen
3187             View first = getChildAt(0);
3188             while (first.getBottom() < listTop) {
3189                 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3190                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3191                     recycleBin.addScrapView(first, mFirstPosition);
3192                 }
3193                 detachViewFromParent(first);
3194                 first = getChildAt(0);
3195                 mFirstPosition++;
3196             }
3197         } else {
3198             // shifted items down
3199             View first = getChildAt(0);
3200 
3201             // may need to pan views into top
3202             while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3203                 first = addViewAbove(first, mFirstPosition);
3204                 mFirstPosition--;
3205             }
3206 
3207             // may have brought the very first child of the list in too far and
3208             // need to shift it back
3209             if (first.getTop() > listTop) {
3210                 offsetChildrenTopAndBottom(listTop - first.getTop());
3211             }
3212 
3213             int lastIndex = getChildCount() - 1;
3214             View last = getChildAt(lastIndex);
3215 
3216             // bottom view may be panned off screen
3217             while (last.getTop() > listBottom) {
3218                 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3219                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3220                     recycleBin.addScrapView(last, mFirstPosition+lastIndex);
3221                 }
3222                 detachViewFromParent(last);
3223                 last = getChildAt(--lastIndex);
3224             }
3225         }
3226         recycleBin.fullyDetachScrapViews();
3227         removeUnusedFixedViews(mHeaderViewInfos);
3228         removeUnusedFixedViews(mFooterViewInfos);
3229     }
3230 
3231     private View addViewAbove(View theView, int position) {
3232         int abovePosition = position - 1;
3233         View view = obtainView(abovePosition, mIsScrap);
3234         int edgeOfNewChild = theView.getTop() - mDividerHeight;
3235         setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3236                 false, mIsScrap[0]);
3237         return view;
3238     }
3239 
3240     private View addViewBelow(View theView, int position) {
3241         int belowPosition = position + 1;
3242         View view = obtainView(belowPosition, mIsScrap);
3243         int edgeOfNewChild = theView.getBottom() + mDividerHeight;
3244         setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3245                 false, mIsScrap[0]);
3246         return view;
3247     }
3248 
3249     /**
3250      * Indicates that the views created by the ListAdapter can contain focusable
3251      * items.
3252      *
3253      * @param itemsCanFocus true if items can get focus, false otherwise
3254      */
3255     public void setItemsCanFocus(boolean itemsCanFocus) {
3256         mItemsCanFocus = itemsCanFocus;
3257         if (!itemsCanFocus) {
3258             setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3259         }
3260     }
3261 
3262     /**
3263      * @return Whether the views created by the ListAdapter can contain focusable
3264      * items.
3265      */
3266     public boolean getItemsCanFocus() {
3267         return mItemsCanFocus;
3268     }
3269 
3270     @Override
3271     public boolean isOpaque() {
3272         boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
3273                 hasOpaqueScrollbars()) || super.isOpaque();
3274         if (retValue) {
3275             // only return true if the list items cover the entire area of the view
3276             final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
3277             View first = getChildAt(0);
3278             if (first == null || first.getTop() > listTop) {
3279                 return false;
3280             }
3281             final int listBottom = getHeight() -
3282                     (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
3283             View last = getChildAt(getChildCount() - 1);
3284             if (last == null || last.getBottom() < listBottom) {
3285                 return false;
3286             }
3287         }
3288         return retValue;
3289     }
3290 
3291     @Override
3292     public void setCacheColorHint(int color) {
3293         final boolean opaque = (color >>> 24) == 0xFF;
3294         mIsCacheColorOpaque = opaque;
3295         if (opaque) {
3296             if (mDividerPaint == null) {
3297                 mDividerPaint = new Paint();
3298             }
3299             mDividerPaint.setColor(color);
3300         }
3301         super.setCacheColorHint(color);
3302     }
3303 
3304     void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3305         final int height = drawable.getMinimumHeight();
3306 
3307         canvas.save();
3308         canvas.clipRect(bounds);
3309 
3310         final int span = bounds.bottom - bounds.top;
3311         if (span < height) {
3312             bounds.top = bounds.bottom - height;
3313         }
3314 
3315         drawable.setBounds(bounds);
3316         drawable.draw(canvas);
3317 
3318         canvas.restore();
3319     }
3320 
3321     void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3322         final int height = drawable.getMinimumHeight();
3323 
3324         canvas.save();
3325         canvas.clipRect(bounds);
3326 
3327         final int span = bounds.bottom - bounds.top;
3328         if (span < height) {
3329             bounds.bottom = bounds.top + height;
3330         }
3331 
3332         drawable.setBounds(bounds);
3333         drawable.draw(canvas);
3334 
3335         canvas.restore();
3336     }
3337 
3338     @Override
3339     protected void dispatchDraw(Canvas canvas) {
3340         if (mCachingStarted) {
3341             mCachingActive = true;
3342         }
3343 
3344         // Draw the dividers
3345         final int dividerHeight = mDividerHeight;
3346         final Drawable overscrollHeader = mOverScrollHeader;
3347         final Drawable overscrollFooter = mOverScrollFooter;
3348         final boolean drawOverscrollHeader = overscrollHeader != null;
3349         final boolean drawOverscrollFooter = overscrollFooter != null;
3350         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3351 
3352         if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
3353             // Only modify the top and bottom in the loop, we set the left and right here
3354             final Rect bounds = mTempRect;
3355             bounds.left = mPaddingLeft;
3356             bounds.right = mRight - mLeft - mPaddingRight;
3357 
3358             final int count = getChildCount();
3359             final int headerCount = getHeaderViewsCount();
3360             final int itemCount = mItemCount;
3361             final int footerLimit = (itemCount - mFooterViewInfos.size());
3362             final boolean headerDividers = mHeaderDividersEnabled;
3363             final boolean footerDividers = mFooterDividersEnabled;
3364             final int first = mFirstPosition;
3365             final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3366             final ListAdapter adapter = mAdapter;
3367             // If the list is opaque *and* the background is not, we want to
3368             // fill a rect where the dividers would be for non-selectable items
3369             // If the list is opaque and the background is also opaque, we don't
3370             // need to draw anything since the background will do it for us
3371             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3372 
3373             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
3374                 mDividerPaint = new Paint();
3375                 mDividerPaint.setColor(getCacheColorHint());
3376             }
3377             final Paint paint = mDividerPaint;
3378 
3379             int effectivePaddingTop = 0;
3380             int effectivePaddingBottom = 0;
3381             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3382                 effectivePaddingTop = mListPadding.top;
3383                 effectivePaddingBottom = mListPadding.bottom;
3384             }
3385 
3386             final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
3387             if (!mStackFromBottom) {
3388                 int bottom = 0;
3389 
3390                 // Draw top divider or header for overscroll
3391                 final int scrollY = mScrollY;
3392                 if (count > 0 && scrollY < 0) {
3393                     if (drawOverscrollHeader) {
3394                         bounds.bottom = 0;
3395                         bounds.top = scrollY;
3396                         drawOverscrollHeader(canvas, overscrollHeader, bounds);
3397                     } else if (drawDividers) {
3398                         bounds.bottom = 0;
3399                         bounds.top = -dividerHeight;
3400                         drawDivider(canvas, bounds, -1);
3401                     }
3402                 }
3403 
3404                 for (int i = 0; i < count; i++) {
3405                     final int itemIndex = (first + i);
3406                     final boolean isHeader = (itemIndex < headerCount);
3407                     final boolean isFooter = (itemIndex >= footerLimit);
3408                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3409                         final View child = getChildAt(i);
3410                         bottom = child.getBottom();
3411                         final boolean isLastItem = (i == (count - 1));
3412 
3413                         if (drawDividers && (bottom < listBottom)
3414                                 && !(drawOverscrollFooter && isLastItem)) {
3415                             final int nextIndex = (itemIndex + 1);
3416                             // Draw dividers between enabled items, headers
3417                             // and/or footers when enabled and requested, and
3418                             // after the last enabled item.
3419                             if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3420                                     && (nextIndex >= headerCount)) && (isLastItem
3421                                     || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3422                                             && (nextIndex < footerLimit)))) {
3423                                 bounds.top = bottom;
3424                                 bounds.bottom = bottom + dividerHeight;
3425                                 drawDivider(canvas, bounds, i);
3426                             } else if (fillForMissingDividers) {
3427                                 bounds.top = bottom;
3428                                 bounds.bottom = bottom + dividerHeight;
3429                                 canvas.drawRect(bounds, paint);
3430                             }
3431                         }
3432                     }
3433                 }
3434 
3435                 final int overFooterBottom = mBottom + mScrollY;
3436                 if (drawOverscrollFooter && first + count == itemCount &&
3437                         overFooterBottom > bottom) {
3438                     bounds.top = bottom;
3439                     bounds.bottom = overFooterBottom;
3440                     drawOverscrollFooter(canvas, overscrollFooter, bounds);
3441                 }
3442             } else {
3443                 int top;
3444 
3445                 final int scrollY = mScrollY;
3446 
3447                 if (count > 0 && drawOverscrollHeader) {
3448                     bounds.top = scrollY;
3449                     bounds.bottom = getChildAt(0).getTop();
3450                     drawOverscrollHeader(canvas, overscrollHeader, bounds);
3451                 }
3452 
3453                 final int start = drawOverscrollHeader ? 1 : 0;
3454                 for (int i = start; i < count; i++) {
3455                     final int itemIndex = (first + i);
3456                     final boolean isHeader = (itemIndex < headerCount);
3457                     final boolean isFooter = (itemIndex >= footerLimit);
3458                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3459                         final View child = getChildAt(i);
3460                         top = child.getTop();
3461                         if (drawDividers && (top > effectivePaddingTop)) {
3462                             final boolean isFirstItem = (i == start);
3463                             final int previousIndex = (itemIndex - 1);
3464                             // Draw dividers between enabled items, headers
3465                             // and/or footers when enabled and requested, and
3466                             // before the first enabled item.
3467                             if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3468                                     && (previousIndex >= headerCount)) && (isFirstItem ||
3469                                     adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3470                                             && (previousIndex < footerLimit)))) {
3471                                 bounds.top = top - dividerHeight;
3472                                 bounds.bottom = top;
3473                                 // Give the method the child ABOVE the divider,
3474                                 // so we subtract one from our child position.
3475                                 // Give -1 when there is no child above the
3476                                 // divider.
3477                                 drawDivider(canvas, bounds, i - 1);
3478                             } else if (fillForMissingDividers) {
3479                                 bounds.top = top - dividerHeight;
3480                                 bounds.bottom = top;
3481                                 canvas.drawRect(bounds, paint);
3482                             }
3483                         }
3484                     }
3485                 }
3486 
3487                 if (count > 0 && scrollY > 0) {
3488                     if (drawOverscrollFooter) {
3489                         final int absListBottom = mBottom;
3490                         bounds.top = absListBottom;
3491                         bounds.bottom = absListBottom + scrollY;
3492                         drawOverscrollFooter(canvas, overscrollFooter, bounds);
3493                     } else if (drawDividers) {
3494                         bounds.top = listBottom;
3495                         bounds.bottom = listBottom + dividerHeight;
3496                         drawDivider(canvas, bounds, -1);
3497                     }
3498                 }
3499             }
3500         }
3501 
3502         // Draw the indicators (these should be drawn above the dividers) and children
3503         super.dispatchDraw(canvas);
3504     }
3505 
3506     @Override
3507     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3508         boolean more = super.drawChild(canvas, child, drawingTime);
3509         if (mCachingActive && child.mCachingFailed) {
3510             mCachingActive = false;
3511         }
3512         return more;
3513     }
3514 
3515     /**
3516      * Draws a divider for the given child in the given bounds.
3517      *
3518      * @param canvas The canvas to draw to.
3519      * @param bounds The bounds of the divider.
3520      * @param childIndex The index of child (of the View) above the divider.
3521      *            This will be -1 if there is no child above the divider to be
3522      *            drawn.
3523      */
3524     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3525         // This widget draws the same divider for all children
3526         final Drawable divider = mDivider;
3527 
3528         divider.setBounds(bounds);
3529         divider.draw(canvas);
3530     }
3531 
3532     /**
3533      * Returns the drawable that will be drawn between each item in the list.
3534      *
3535      * @return the current drawable drawn between list elements
3536      * @attr ref R.styleable#ListView_divider
3537      */
3538     @Nullable
3539     public Drawable getDivider() {
3540         return mDivider;
3541     }
3542 
3543     /**
3544      * Sets the drawable that will be drawn between each item in the list.
3545      * <p>
3546      * <strong>Note:</strong> If the drawable does not have an intrinsic
3547      * height, you should also call {@link #setDividerHeight(int)}.
3548      *
3549      * @param divider the drawable to use
3550      * @attr ref R.styleable#ListView_divider
3551      */
3552     public void setDivider(@Nullable Drawable divider) {
3553         if (divider != null) {
3554             mDividerHeight = divider.getIntrinsicHeight();
3555         } else {
3556             mDividerHeight = 0;
3557         }
3558         mDivider = divider;
3559         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
3560         requestLayout();
3561         invalidate();
3562     }
3563 
3564     /**
3565      * @return Returns the height of the divider that will be drawn between each item in the list.
3566      */
3567     public int getDividerHeight() {
3568         return mDividerHeight;
3569     }
3570 
3571     /**
3572      * Sets the height of the divider that will be drawn between each item in the list. Calling
3573      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3574      *
3575      * @param height The new height of the divider in pixels.
3576      */
3577     public void setDividerHeight(int height) {
3578         mDividerHeight = height;
3579         requestLayout();
3580         invalidate();
3581     }
3582 
3583     /**
3584      * Enables or disables the drawing of the divider for header views.
3585      *
3586      * @param headerDividersEnabled True to draw the headers, false otherwise.
3587      *
3588      * @see #setFooterDividersEnabled(boolean)
3589      * @see #areHeaderDividersEnabled()
3590      * @see #addHeaderView(android.view.View)
3591      */
3592     public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3593         mHeaderDividersEnabled = headerDividersEnabled;
3594         invalidate();
3595     }
3596 
3597     /**
3598      * @return Whether the drawing of the divider for header views is enabled
3599      *
3600      * @see #setHeaderDividersEnabled(boolean)
3601      */
3602     public boolean areHeaderDividersEnabled() {
3603         return mHeaderDividersEnabled;
3604     }
3605 
3606     /**
3607      * Enables or disables the drawing of the divider for footer views.
3608      *
3609      * @param footerDividersEnabled True to draw the footers, false otherwise.
3610      *
3611      * @see #setHeaderDividersEnabled(boolean)
3612      * @see #areFooterDividersEnabled()
3613      * @see #addFooterView(android.view.View)
3614      */
3615     public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3616         mFooterDividersEnabled = footerDividersEnabled;
3617         invalidate();
3618     }
3619 
3620     /**
3621      * @return Whether the drawing of the divider for footer views is enabled
3622      *
3623      * @see #setFooterDividersEnabled(boolean)
3624      */
3625     public boolean areFooterDividersEnabled() {
3626         return mFooterDividersEnabled;
3627     }
3628 
3629     /**
3630      * Sets the drawable that will be drawn above all other list content.
3631      * This area can become visible when the user overscrolls the list.
3632      *
3633      * @param header The drawable to use
3634      */
3635     public void setOverscrollHeader(Drawable header) {
3636         mOverScrollHeader = header;
3637         if (mScrollY < 0) {
3638             invalidate();
3639         }
3640     }
3641 
3642     /**
3643      * @return The drawable that will be drawn above all other list content
3644      */
3645     public Drawable getOverscrollHeader() {
3646         return mOverScrollHeader;
3647     }
3648 
3649     /**
3650      * Sets the drawable that will be drawn below all other list content.
3651      * This area can become visible when the user overscrolls the list,
3652      * or when the list's content does not fully fill the container area.
3653      *
3654      * @param footer The drawable to use
3655      */
3656     public void setOverscrollFooter(Drawable footer) {
3657         mOverScrollFooter = footer;
3658         invalidate();
3659     }
3660 
3661     /**
3662      * @return The drawable that will be drawn below all other list content
3663      */
3664     public Drawable getOverscrollFooter() {
3665         return mOverScrollFooter;
3666     }
3667 
3668     @Override
3669     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3670         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3671 
3672         final ListAdapter adapter = mAdapter;
3673         int closetChildIndex = -1;
3674         int closestChildTop = 0;
3675         if (adapter != null && gainFocus && previouslyFocusedRect != null) {
3676             previouslyFocusedRect.offset(mScrollX, mScrollY);
3677 
3678             // Don't cache the result of getChildCount or mFirstPosition here,
3679             // it could change in layoutChildren.
3680             if (adapter.getCount() < getChildCount() + mFirstPosition) {
3681                 mLayoutMode = LAYOUT_NORMAL;
3682                 layoutChildren();
3683             }
3684 
3685             // figure out which item should be selected based on previously
3686             // focused rect
3687             Rect otherRect = mTempRect;
3688             int minDistance = Integer.MAX_VALUE;
3689             final int childCount = getChildCount();
3690             final int firstPosition = mFirstPosition;
3691 
3692             for (int i = 0; i < childCount; i++) {
3693                 // only consider selectable views
3694                 if (!adapter.isEnabled(firstPosition + i)) {
3695                     continue;
3696                 }
3697 
3698                 View other = getChildAt(i);
3699                 other.getDrawingRect(otherRect);
3700                 offsetDescendantRectToMyCoords(other, otherRect);
3701                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3702 
3703                 if (distance < minDistance) {
3704                     minDistance = distance;
3705                     closetChildIndex = i;
3706                     closestChildTop = other.getTop();
3707                 }
3708             }
3709         }
3710 
3711         if (closetChildIndex >= 0) {
3712             setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
3713         } else {
3714             requestLayout();
3715         }
3716     }
3717 
3718 
3719     /*
3720      * (non-Javadoc)
3721      *
3722      * Children specified in XML are assumed to be header views. After we have
3723      * parsed them move them out of the children list and into mHeaderViews.
3724      */
3725     @Override
3726     protected void onFinishInflate() {
3727         super.onFinishInflate();
3728 
3729         int count = getChildCount();
3730         if (count > 0) {
3731             for (int i = 0; i < count; ++i) {
3732                 addHeaderView(getChildAt(i));
3733             }
3734             removeAllViews();
3735         }
3736     }
3737 
3738     /* (non-Javadoc)
3739      * @see android.view.View#findViewById(int)
3740      * First look in our children, then in any header and footer views that may be scrolled off.
3741      */
3742     @Override
3743     protected View findViewTraversal(@IdRes int id) {
3744         View v;
3745         v = super.findViewTraversal(id);
3746         if (v == null) {
3747             v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3748             if (v != null) {
3749                 return v;
3750             }
3751             v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3752             if (v != null) {
3753                 return v;
3754             }
3755         }
3756         return v;
3757     }
3758 
3759     /* (non-Javadoc)
3760      *
3761      * Look in the passed in list of headers or footers for the view.
3762      */
3763     View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3764         if (where != null) {
3765             int len = where.size();
3766             View v;
3767 
3768             for (int i = 0; i < len; i++) {
3769                 v = where.get(i).view;
3770 
3771                 if (!v.isRootNamespace()) {
3772                     v = v.findViewById(id);
3773 
3774                     if (v != null) {
3775                         return v;
3776                     }
3777                 }
3778             }
3779         }
3780         return null;
3781     }
3782 
3783     /* (non-Javadoc)
3784      * @see android.view.View#findViewWithTag(Object)
3785      * First look in our children, then in any header and footer views that may be scrolled off.
3786      */
3787     @Override
3788     protected View findViewWithTagTraversal(Object tag) {
3789         View v;
3790         v = super.findViewWithTagTraversal(tag);
3791         if (v == null) {
3792             v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
3793             if (v != null) {
3794                 return v;
3795             }
3796 
3797             v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
3798             if (v != null) {
3799                 return v;
3800             }
3801         }
3802         return v;
3803     }
3804 
3805     /* (non-Javadoc)
3806      *
3807      * Look in the passed in list of headers or footers for the view with the tag.
3808      */
3809     View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
3810         if (where != null) {
3811             int len = where.size();
3812             View v;
3813 
3814             for (int i = 0; i < len; i++) {
3815                 v = where.get(i).view;
3816 
3817                 if (!v.isRootNamespace()) {
3818                     v = v.findViewWithTag(tag);
3819 
3820                     if (v != null) {
3821                         return v;
3822                     }
3823                 }
3824             }
3825         }
3826         return null;
3827     }
3828 
3829     /**
3830      * @hide
3831      * @see android.view.View#findViewByPredicate(Predicate)
3832      * First look in our children, then in any header and footer views that may be scrolled off.
3833      */
3834     @Override
3835     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
3836         View v;
3837         v = super.findViewByPredicateTraversal(predicate, childToSkip);
3838         if (v == null) {
3839             v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
3840             if (v != null) {
3841                 return v;
3842             }
3843 
3844             v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
3845             if (v != null) {
3846                 return v;
3847             }
3848         }
3849         return v;
3850     }
3851 
3852     /* (non-Javadoc)
3853      *
3854      * Look in the passed in list of headers or footers for the first view that matches
3855      * the predicate.
3856      */
3857     View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
3858             Predicate<View> predicate, View childToSkip) {
3859         if (where != null) {
3860             int len = where.size();
3861             View v;
3862 
3863             for (int i = 0; i < len; i++) {
3864                 v = where.get(i).view;
3865 
3866                 if (v != childToSkip && !v.isRootNamespace()) {
3867                     v = v.findViewByPredicate(predicate);
3868 
3869                     if (v != null) {
3870                         return v;
3871                     }
3872                 }
3873             }
3874         }
3875         return null;
3876     }
3877 
3878     /**
3879      * Returns the set of checked items ids. The result is only valid if the
3880      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3881      *
3882      * @return A new array which contains the id of each checked item in the
3883      *         list.
3884      *
3885      * @deprecated Use {@link #getCheckedItemIds()} instead.
3886      */
3887     @Deprecated
3888     public long[] getCheckItemIds() {
3889         // Use new behavior that correctly handles stable ID mapping.
3890         if (mAdapter != null && mAdapter.hasStableIds()) {
3891             return getCheckedItemIds();
3892         }
3893 
3894         // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3895         // Fall back to it to support legacy apps.
3896         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3897             final SparseBooleanArray states = mCheckStates;
3898             final int count = states.size();
3899             final long[] ids = new long[count];
3900             final ListAdapter adapter = mAdapter;
3901 
3902             int checkedCount = 0;
3903             for (int i = 0; i < count; i++) {
3904                 if (states.valueAt(i)) {
3905                     ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3906                 }
3907             }
3908 
3909             // Trim array if needed. mCheckStates may contain false values
3910             // resulting in checkedCount being smaller than count.
3911             if (checkedCount == count) {
3912                 return ids;
3913             } else {
3914                 final long[] result = new long[checkedCount];
3915                 System.arraycopy(ids, 0, result, 0, checkedCount);
3916 
3917                 return result;
3918             }
3919         }
3920         return new long[0];
3921     }
3922 
3923     @Override
3924     int getHeightForPosition(int position) {
3925         final int height = super.getHeightForPosition(position);
3926         if (shouldAdjustHeightForDivider(position)) {
3927             return height + mDividerHeight;
3928         }
3929         return height;
3930     }
3931 
3932     private boolean shouldAdjustHeightForDivider(int itemIndex) {
3933         final int dividerHeight = mDividerHeight;
3934         final Drawable overscrollHeader = mOverScrollHeader;
3935         final Drawable overscrollFooter = mOverScrollFooter;
3936         final boolean drawOverscrollHeader = overscrollHeader != null;
3937         final boolean drawOverscrollFooter = overscrollFooter != null;
3938         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3939 
3940         if (drawDividers) {
3941             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3942             final int itemCount = mItemCount;
3943             final int headerCount = getHeaderViewsCount();
3944             final int footerLimit = (itemCount - mFooterViewInfos.size());
3945             final boolean isHeader = (itemIndex < headerCount);
3946             final boolean isFooter = (itemIndex >= footerLimit);
3947             final boolean headerDividers = mHeaderDividersEnabled;
3948             final boolean footerDividers = mFooterDividersEnabled;
3949             if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3950                 final ListAdapter adapter = mAdapter;
3951                 if (!mStackFromBottom) {
3952                     final boolean isLastItem = (itemIndex == (itemCount - 1));
3953                     if (!drawOverscrollFooter || !isLastItem) {
3954                         final int nextIndex = itemIndex + 1;
3955                         // Draw dividers between enabled items, headers
3956                         // and/or footers when enabled and requested, and
3957                         // after the last enabled item.
3958                         if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3959                                 && (nextIndex >= headerCount)) && (isLastItem
3960                                 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3961                                                 && (nextIndex < footerLimit)))) {
3962                             return true;
3963                         } else if (fillForMissingDividers) {
3964                             return true;
3965                         }
3966                     }
3967                 } else {
3968                     final int start = drawOverscrollHeader ? 1 : 0;
3969                     final boolean isFirstItem = (itemIndex == start);
3970                     if (!isFirstItem) {
3971                         final int previousIndex = (itemIndex - 1);
3972                         // Draw dividers between enabled items, headers
3973                         // and/or footers when enabled and requested, and
3974                         // before the first enabled item.
3975                         if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3976                                 && (previousIndex >= headerCount)) && (isFirstItem ||
3977                                 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3978                                         && (previousIndex < footerLimit)))) {
3979                             return true;
3980                         } else if (fillForMissingDividers) {
3981                             return true;
3982                         }
3983                     }
3984                 }
3985             }
3986         }
3987 
3988         return false;
3989     }
3990 
3991     @Override
3992     public CharSequence getAccessibilityClassName() {
3993         return ListView.class.getName();
3994     }
3995 
3996     /** @hide */
3997     @Override
3998     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3999         super.onInitializeAccessibilityNodeInfoInternal(info);
4000 
4001         final int rowsCount = getCount();
4002         final int selectionMode = getSelectionModeForAccessibility();
4003         final CollectionInfo collectionInfo = CollectionInfo.obtain(
4004                 rowsCount, 1, false, selectionMode);
4005         info.setCollectionInfo(collectionInfo);
4006 
4007         if (rowsCount > 0) {
4008             info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
4009         }
4010     }
4011 
4012     /** @hide */
4013     @Override
4014     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4015         if (super.performAccessibilityActionInternal(action, arguments)) {
4016             return true;
4017         }
4018 
4019         switch (action) {
4020             case R.id.accessibilityActionScrollToPosition: {
4021                 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
4022                 final int position = Math.min(row, getCount() - 1);
4023                 if (row >= 0) {
4024                     // The accessibility service gets data asynchronously, so
4025                     // we'll be a little lenient by clamping the last position.
4026                     smoothScrollToPosition(position);
4027                     return true;
4028                 }
4029             } break;
4030         }
4031 
4032         return false;
4033     }
4034 
4035     @Override
4036     public void onInitializeAccessibilityNodeInfoForItem(
4037             View view, int position, AccessibilityNodeInfo info) {
4038         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
4039 
4040         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
4041         final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
4042         final boolean isSelected = isItemChecked(position);
4043         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
4044                 position, 1, 0, 1, isHeading, isSelected);
4045         info.setCollectionItemInfo(itemInfo);
4046     }
4047 
4048     /** @hide */
4049     @Override
4050     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
4051         super.encodeProperties(encoder);
4052 
4053         encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
4054     }
4055 
4056     /** @hide */
4057     protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
4058             ArrayList<ListView.FixedViewInfo> headerViewInfos,
4059             ArrayList<ListView.FixedViewInfo> footerViewInfos,
4060             ListAdapter adapter) {
4061         return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
4062     }
4063 
4064     /** @hide */
4065     protected void wrapHeaderListAdapterInternal() {
4066         mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
4067     }
4068 
4069     /** @hide */
4070     protected void dispatchDataSetObserverOnChangedInternal() {
4071         if (mDataSetObserver != null) {
4072             mDataSetObserver.onChanged();
4073         }
4074     }
4075 }
4076