• 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     private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
116     private 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                 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
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                 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
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 = new HeaderViewListAdapter(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      * Obtain the view and add it to our list of children. The view can be made
1943      * fresh, converted from an unused view, or used as is if it was in the
1944      * 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 If flow is true, align top edge to y. If false, align bottom
1949      *        edge to y.
1950      * @param childrenLeft Left edge where children should be positioned
1951      * @param selected Is this position selected?
1952      * @return View that was added
1953      */
makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected)1954     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1955             boolean selected) {
1956         View child;
1957 
1958 
1959         if (!mDataChanged) {
1960             // Try to use an existing view for this position
1961             child = mRecycler.getActiveView(position);
1962             if (child != null) {
1963                 // Found it -- we're using an existing child
1964                 // This just needs to be positioned
1965                 setupChild(child, position, y, flow, childrenLeft, selected, true);
1966 
1967                 return child;
1968             }
1969         }
1970 
1971         // Make a new view for this position, or convert an unused view if possible
1972         child = obtainView(position, mIsScrap);
1973 
1974         // This needs to be positioned and measured
1975         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
1976 
1977         return child;
1978     }
1979 
1980     /**
1981      * Add a view as a child and make sure it is measured (if necessary) and
1982      * positioned properly.
1983      *
1984      * @param child The view to add
1985      * @param position The position of this child
1986      * @param y The y position relative to which this view will be positioned
1987      * @param flowDown If true, align top edge to y. If false, align bottom
1988      *        edge to y.
1989      * @param childrenLeft Left edge where children should be positioned
1990      * @param selected Is this position selected?
1991      * @param recycled Has this view been pulled from the recycle bin? If so it
1992      *        does not need to be remeasured.
1993      */
setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled)1994     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1995             boolean selected, boolean recycled) {
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 = !recycled || updateChildSelected || child.isLayoutRequested();
2005 
2006         // Respect layout params that are already in the view. Otherwise make some up...
2007         // noinspection unchecked
2008         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
2009         if (p == null) {
2010             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
2011         }
2012         p.viewType = mAdapter.getItemViewType(position);
2013         p.isEnabled = mAdapter.isEnabled(position);
2014 
2015         if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
2016                 && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
2017             attachViewToParent(child, flowDown ? -1 : 0, p);
2018         } else {
2019             p.forceAdd = false;
2020             if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
2021                 p.recycledHeaderFooter = true;
2022             }
2023             addViewInLayout(child, flowDown ? -1 : 0, p, true);
2024         }
2025 
2026         if (updateChildSelected) {
2027             child.setSelected(isSelected);
2028         }
2029 
2030         if (updateChildPressed) {
2031             child.setPressed(isPressed);
2032         }
2033 
2034         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
2035             if (child instanceof Checkable) {
2036                 ((Checkable) child).setChecked(mCheckStates.get(position));
2037             } else if (getContext().getApplicationInfo().targetSdkVersion
2038                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
2039                 child.setActivated(mCheckStates.get(position));
2040             }
2041         }
2042 
2043         if (needToMeasure) {
2044             final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2045                     mListPadding.left + mListPadding.right, p.width);
2046             final int lpHeight = p.height;
2047             final int childHeightSpec;
2048             if (lpHeight > 0) {
2049                 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2050             } else {
2051                 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2052                         MeasureSpec.UNSPECIFIED);
2053             }
2054             child.measure(childWidthSpec, childHeightSpec);
2055         } else {
2056             cleanupLayoutState(child);
2057         }
2058 
2059         final int w = child.getMeasuredWidth();
2060         final int h = child.getMeasuredHeight();
2061         final int childTop = flowDown ? y : y - h;
2062 
2063         if (needToMeasure) {
2064             final int childRight = childrenLeft + w;
2065             final int childBottom = childTop + h;
2066             child.layout(childrenLeft, childTop, childRight, childBottom);
2067         } else {
2068             child.offsetLeftAndRight(childrenLeft - child.getLeft());
2069             child.offsetTopAndBottom(childTop - child.getTop());
2070         }
2071 
2072         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
2073             child.setDrawingCacheEnabled(true);
2074         }
2075 
2076         if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
2077                 != position) {
2078             child.jumpDrawablesToCurrentState();
2079         }
2080 
2081         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2082     }
2083 
2084     @Override
canAnimate()2085     protected boolean canAnimate() {
2086         return super.canAnimate() && mItemCount > 0;
2087     }
2088 
2089     /**
2090      * Sets the currently selected item. If in touch mode, the item will not be selected
2091      * but it will still be positioned appropriately. If the specified selection position
2092      * is less than 0, then the item at position 0 will be selected.
2093      *
2094      * @param position Index (starting at 0) of the data item to be selected.
2095      */
2096     @Override
setSelection(int position)2097     public void setSelection(int position) {
2098         setSelectionFromTop(position, 0);
2099     }
2100 
2101     /**
2102      * Makes the item at the supplied position selected.
2103      *
2104      * @param position the position of the item to select
2105      */
2106     @Override
setSelectionInt(int position)2107     void setSelectionInt(int position) {
2108         setNextSelectedPositionInt(position);
2109         boolean awakeScrollbars = false;
2110 
2111         final int selectedPosition = mSelectedPosition;
2112 
2113         if (selectedPosition >= 0) {
2114             if (position == selectedPosition - 1) {
2115                 awakeScrollbars = true;
2116             } else if (position == selectedPosition + 1) {
2117                 awakeScrollbars = true;
2118             }
2119         }
2120 
2121         if (mPositionScroller != null) {
2122             mPositionScroller.stop();
2123         }
2124 
2125         layoutChildren();
2126 
2127         if (awakeScrollbars) {
2128             awakenScrollBars();
2129         }
2130     }
2131 
2132     /**
2133      * Find a position that can be selected (i.e., is not a separator).
2134      *
2135      * @param position The starting position to look at.
2136      * @param lookDown Whether to look down for other positions.
2137      * @return The next selectable position starting at position and then searching either up or
2138      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
2139      */
2140     @Override
lookForSelectablePosition(int position, boolean lookDown)2141     int lookForSelectablePosition(int position, boolean lookDown) {
2142         final ListAdapter adapter = mAdapter;
2143         if (adapter == null || isInTouchMode()) {
2144             return INVALID_POSITION;
2145         }
2146 
2147         final int count = adapter.getCount();
2148         if (!mAreAllItemsSelectable) {
2149             if (lookDown) {
2150                 position = Math.max(0, position);
2151                 while (position < count && !adapter.isEnabled(position)) {
2152                     position++;
2153                 }
2154             } else {
2155                 position = Math.min(position, count - 1);
2156                 while (position >= 0 && !adapter.isEnabled(position)) {
2157                     position--;
2158                 }
2159             }
2160         }
2161 
2162         if (position < 0 || position >= count) {
2163             return INVALID_POSITION;
2164         }
2165 
2166         return position;
2167     }
2168 
2169     /**
2170      * Find a position that can be selected (i.e., is not a separator). If there
2171      * are no selectable positions in the specified direction from the starting
2172      * position, searches in the opposite direction from the starting position
2173      * to the current position.
2174      *
2175      * @param current the current position
2176      * @param position the starting position
2177      * @param lookDown whether to look down for other positions
2178      * @return the next selectable position, or {@link #INVALID_POSITION} if
2179      *         nothing can be found
2180      */
lookForSelectablePositionAfter(int current, int position, boolean lookDown)2181     int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2182         final ListAdapter adapter = mAdapter;
2183         if (adapter == null || isInTouchMode()) {
2184             return INVALID_POSITION;
2185         }
2186 
2187         // First check after the starting position in the specified direction.
2188         final int after = lookForSelectablePosition(position, lookDown);
2189         if (after != INVALID_POSITION) {
2190             return after;
2191         }
2192 
2193         // Then check between the starting position and the current position.
2194         final int count = adapter.getCount();
2195         current = MathUtils.constrain(current, -1, count - 1);
2196         if (lookDown) {
2197             position = Math.min(position - 1, count - 1);
2198             while ((position > current) && !adapter.isEnabled(position)) {
2199                 position--;
2200             }
2201             if (position <= current) {
2202                 return INVALID_POSITION;
2203             }
2204         } else {
2205             position = Math.max(0, position + 1);
2206             while ((position < current) && !adapter.isEnabled(position)) {
2207                 position++;
2208             }
2209             if (position >= current) {
2210                 return INVALID_POSITION;
2211             }
2212         }
2213 
2214         return position;
2215     }
2216 
2217     /**
2218      * setSelectionAfterHeaderView set the selection to be the first list item
2219      * after the header views.
2220      */
setSelectionAfterHeaderView()2221     public void setSelectionAfterHeaderView() {
2222         final int count = mHeaderViewInfos.size();
2223         if (count > 0) {
2224             mNextSelectedPosition = 0;
2225             return;
2226         }
2227 
2228         if (mAdapter != null) {
2229             setSelection(count);
2230         } else {
2231             mNextSelectedPosition = count;
2232             mLayoutMode = LAYOUT_SET_SELECTION;
2233         }
2234 
2235     }
2236 
2237     @Override
dispatchKeyEvent(KeyEvent event)2238     public boolean dispatchKeyEvent(KeyEvent event) {
2239         // Dispatch in the normal way
2240         boolean handled = super.dispatchKeyEvent(event);
2241         if (!handled) {
2242             // If we didn't handle it...
2243             View focused = getFocusedChild();
2244             if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2245                 // ... and our focused child didn't handle it
2246                 // ... give it to ourselves so we can scroll if necessary
2247                 handled = onKeyDown(event.getKeyCode(), event);
2248             }
2249         }
2250         return handled;
2251     }
2252 
2253     @Override
onKeyDown(int keyCode, KeyEvent event)2254     public boolean onKeyDown(int keyCode, KeyEvent event) {
2255         return commonKey(keyCode, 1, event);
2256     }
2257 
2258     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)2259     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2260         return commonKey(keyCode, repeatCount, event);
2261     }
2262 
2263     @Override
onKeyUp(int keyCode, KeyEvent event)2264     public boolean onKeyUp(int keyCode, KeyEvent event) {
2265         return commonKey(keyCode, 1, event);
2266     }
2267 
commonKey(int keyCode, int count, KeyEvent event)2268     private boolean commonKey(int keyCode, int count, KeyEvent event) {
2269         if (mAdapter == null || !isAttachedToWindow()) {
2270             return false;
2271         }
2272 
2273         if (mDataChanged) {
2274             layoutChildren();
2275         }
2276 
2277         boolean handled = false;
2278         int action = event.getAction();
2279         if (KeyEvent.isConfirmKey(keyCode)
2280                 && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
2281             handled = resurrectSelectionIfNeeded();
2282             if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
2283                 keyPressed();
2284                 handled = true;
2285             }
2286         }
2287 
2288 
2289         if (!handled && action != KeyEvent.ACTION_UP) {
2290             switch (keyCode) {
2291             case KeyEvent.KEYCODE_DPAD_UP:
2292                 if (event.hasNoModifiers()) {
2293                     handled = resurrectSelectionIfNeeded();
2294                     if (!handled) {
2295                         while (count-- > 0) {
2296                             if (arrowScroll(FOCUS_UP)) {
2297                                 handled = true;
2298                             } else {
2299                                 break;
2300                             }
2301                         }
2302                     }
2303                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2304                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2305                 }
2306                 break;
2307 
2308             case KeyEvent.KEYCODE_DPAD_DOWN:
2309                 if (event.hasNoModifiers()) {
2310                     handled = resurrectSelectionIfNeeded();
2311                     if (!handled) {
2312                         while (count-- > 0) {
2313                             if (arrowScroll(FOCUS_DOWN)) {
2314                                 handled = true;
2315                             } else {
2316                                 break;
2317                             }
2318                         }
2319                     }
2320                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2321                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2322                 }
2323                 break;
2324 
2325             case KeyEvent.KEYCODE_DPAD_LEFT:
2326                 if (event.hasNoModifiers()) {
2327                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2328                 }
2329                 break;
2330 
2331             case KeyEvent.KEYCODE_DPAD_RIGHT:
2332                 if (event.hasNoModifiers()) {
2333                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2334                 }
2335                 break;
2336 
2337             case KeyEvent.KEYCODE_PAGE_UP:
2338                 if (event.hasNoModifiers()) {
2339                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
2340                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2341                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2342                 }
2343                 break;
2344 
2345             case KeyEvent.KEYCODE_PAGE_DOWN:
2346                 if (event.hasNoModifiers()) {
2347                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
2348                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2349                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2350                 }
2351                 break;
2352 
2353             case KeyEvent.KEYCODE_MOVE_HOME:
2354                 if (event.hasNoModifiers()) {
2355                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2356                 }
2357                 break;
2358 
2359             case KeyEvent.KEYCODE_MOVE_END:
2360                 if (event.hasNoModifiers()) {
2361                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2362                 }
2363                 break;
2364 
2365             case KeyEvent.KEYCODE_TAB:
2366                 // This creates an asymmetry in TAB navigation order. At some
2367                 // point in the future we may decide that it's preferable to
2368                 // force the list selection to the top or bottom when receiving
2369                 // TAB focus from another widget, but for now this is adequate.
2370                 if (event.hasNoModifiers()) {
2371                     handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2372                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2373                     handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
2374                 }
2375                 break;
2376             }
2377         }
2378 
2379         if (handled) {
2380             return true;
2381         }
2382 
2383         if (sendToTextFilter(keyCode, count, event)) {
2384             return true;
2385         }
2386 
2387         switch (action) {
2388             case KeyEvent.ACTION_DOWN:
2389                 return super.onKeyDown(keyCode, event);
2390 
2391             case KeyEvent.ACTION_UP:
2392                 return super.onKeyUp(keyCode, event);
2393 
2394             case KeyEvent.ACTION_MULTIPLE:
2395                 return super.onKeyMultiple(keyCode, count, event);
2396 
2397             default: // shouldn't happen
2398                 return false;
2399         }
2400     }
2401 
2402     /**
2403      * Scrolls up or down by the number of items currently present on screen.
2404      *
2405      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2406      * @return whether selection was moved
2407      */
pageScroll(int direction)2408     boolean pageScroll(int direction) {
2409         final int nextPage;
2410         final boolean down;
2411 
2412         if (direction == FOCUS_UP) {
2413             nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2414             down = false;
2415         } else if (direction == FOCUS_DOWN) {
2416             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2417             down = true;
2418         } else {
2419             return false;
2420         }
2421 
2422         if (nextPage >= 0) {
2423             final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
2424             if (position >= 0) {
2425                 mLayoutMode = LAYOUT_SPECIFIC;
2426                 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2427 
2428                 if (down && (position > (mItemCount - getChildCount()))) {
2429                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
2430                 }
2431 
2432                 if (!down && (position < getChildCount())) {
2433                     mLayoutMode = LAYOUT_FORCE_TOP;
2434                 }
2435 
2436                 setSelectionInt(position);
2437                 invokeOnItemScrollListener();
2438                 if (!awakenScrollBars()) {
2439                     invalidate();
2440                 }
2441 
2442                 return true;
2443             }
2444         }
2445 
2446         return false;
2447     }
2448 
2449     /**
2450      * Go to the last or first item if possible (not worrying about panning
2451      * across or navigating within the internal focus of the currently selected
2452      * item.)
2453      *
2454      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2455      * @return whether selection was moved
2456      */
fullScroll(int direction)2457     boolean fullScroll(int direction) {
2458         boolean moved = false;
2459         if (direction == FOCUS_UP) {
2460             if (mSelectedPosition != 0) {
2461                 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
2462                 if (position >= 0) {
2463                     mLayoutMode = LAYOUT_FORCE_TOP;
2464                     setSelectionInt(position);
2465                     invokeOnItemScrollListener();
2466                 }
2467                 moved = true;
2468             }
2469         } else if (direction == FOCUS_DOWN) {
2470             final int lastItem = (mItemCount - 1);
2471             if (mSelectedPosition < lastItem) {
2472                 final int position = lookForSelectablePositionAfter(
2473                         mSelectedPosition, lastItem, false);
2474                 if (position >= 0) {
2475                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
2476                     setSelectionInt(position);
2477                     invokeOnItemScrollListener();
2478                 }
2479                 moved = true;
2480             }
2481         }
2482 
2483         if (moved && !awakenScrollBars()) {
2484             awakenScrollBars();
2485             invalidate();
2486         }
2487 
2488         return moved;
2489     }
2490 
2491     /**
2492      * To avoid horizontal focus searches changing the selected item, we
2493      * manually focus search within the selected item (as applicable), and
2494      * prevent focus from jumping to something within another item.
2495      * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2496      * @return Whether this consumes the key event.
2497      */
handleHorizontalFocusWithinListItem(int direction)2498     private boolean handleHorizontalFocusWithinListItem(int direction) {
2499         if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
2500             throw new IllegalArgumentException("direction must be one of"
2501                     + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
2502         }
2503 
2504         final int numChildren = getChildCount();
2505         if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2506             final View selectedView = getSelectedView();
2507             if (selectedView != null && selectedView.hasFocus() &&
2508                     selectedView instanceof ViewGroup) {
2509 
2510                 final View currentFocus = selectedView.findFocus();
2511                 final View nextFocus = FocusFinder.getInstance().findNextFocus(
2512                         (ViewGroup) selectedView, currentFocus, direction);
2513                 if (nextFocus != null) {
2514                     // do the math to get interesting rect in next focus' coordinates
2515                     Rect focusedRect = mTempRect;
2516                     if (currentFocus != null) {
2517                         currentFocus.getFocusedRect(focusedRect);
2518                         offsetDescendantRectToMyCoords(currentFocus, focusedRect);
2519                         offsetRectIntoDescendantCoords(nextFocus, focusedRect);
2520                     } else {
2521                         focusedRect = null;
2522                     }
2523                     if (nextFocus.requestFocus(direction, focusedRect)) {
2524                         return true;
2525                     }
2526                 }
2527                 // we are blocking the key from being handled (by returning true)
2528                 // if the global result is going to be some other view within this
2529                 // list.  this is to acheive the overall goal of having
2530                 // horizontal d-pad navigation remain in the current item.
2531                 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2532                         (ViewGroup) getRootView(), currentFocus, direction);
2533                 if (globalNextFocus != null) {
2534                     return isViewAncestorOf(globalNextFocus, this);
2535                 }
2536             }
2537         }
2538         return false;
2539     }
2540 
2541     /**
2542      * Scrolls to the next or previous item if possible.
2543      *
2544      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2545      *
2546      * @return whether selection was moved
2547      */
arrowScroll(int direction)2548     boolean arrowScroll(int direction) {
2549         try {
2550             mInLayout = true;
2551             final boolean handled = arrowScrollImpl(direction);
2552             if (handled) {
2553                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2554             }
2555             return handled;
2556         } finally {
2557             mInLayout = false;
2558         }
2559     }
2560 
2561     /**
2562      * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
2563      * to move to. This return a position in the direction given if the selected item
2564      * is fully visible.
2565      *
2566      * @param selectedView Current selected view to move from
2567      * @param selectedPos Current selected position to move from
2568      * @param direction Direction to move in
2569      * @return Desired selected position after moving in the given direction
2570      */
nextSelectedPositionForDirection( View selectedView, int selectedPos, int direction)2571     private final int nextSelectedPositionForDirection(
2572             View selectedView, int selectedPos, int direction) {
2573         int nextSelected;
2574 
2575         if (direction == View.FOCUS_DOWN) {
2576             final int listBottom = getHeight() - mListPadding.bottom;
2577             if (selectedView != null && selectedView.getBottom() <= listBottom) {
2578                 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2579                         selectedPos + 1 :
2580                         mFirstPosition;
2581             } else {
2582                 return INVALID_POSITION;
2583             }
2584         } else {
2585             final int listTop = mListPadding.top;
2586             if (selectedView != null && selectedView.getTop() >= listTop) {
2587                 final int lastPos = mFirstPosition + getChildCount() - 1;
2588                 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2589                         selectedPos - 1 :
2590                         lastPos;
2591             } else {
2592                 return INVALID_POSITION;
2593             }
2594         }
2595 
2596         if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2597             return INVALID_POSITION;
2598         }
2599         return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2600     }
2601 
2602     /**
2603      * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
2604      * whether there are focusable items etc.
2605      *
2606      * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2607      * @return Whether any scrolling, selection or focus change occured.
2608      */
arrowScrollImpl(int direction)2609     private boolean arrowScrollImpl(int direction) {
2610         if (getChildCount() <= 0) {
2611             return false;
2612         }
2613 
2614         View selectedView = getSelectedView();
2615         int selectedPos = mSelectedPosition;
2616 
2617         int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
2618         int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2619 
2620         // if we are moving focus, we may OVERRIDE the default behavior
2621         final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2622         if (focusResult != null) {
2623             nextSelectedPosition = focusResult.getSelectedPosition();
2624             amountToScroll = focusResult.getAmountToScroll();
2625         }
2626 
2627         boolean needToRedraw = focusResult != null;
2628         if (nextSelectedPosition != INVALID_POSITION) {
2629             handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2630             setSelectedPositionInt(nextSelectedPosition);
2631             setNextSelectedPositionInt(nextSelectedPosition);
2632             selectedView = getSelectedView();
2633             selectedPos = nextSelectedPosition;
2634             if (mItemsCanFocus && focusResult == null) {
2635                 // there was no new view found to take focus, make sure we
2636                 // don't leave focus with the old selection
2637                 final View focused = getFocusedChild();
2638                 if (focused != null) {
2639                     focused.clearFocus();
2640                 }
2641             }
2642             needToRedraw = true;
2643             checkSelectionChanged();
2644         }
2645 
2646         if (amountToScroll > 0) {
2647             scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2648             needToRedraw = true;
2649         }
2650 
2651         // if we didn't find a new focusable, make sure any existing focused
2652         // item that was panned off screen gives up focus.
2653         if (mItemsCanFocus && (focusResult == null)
2654                 && selectedView != null && selectedView.hasFocus()) {
2655             final View focused = selectedView.findFocus();
2656             if (focused != null) {
2657                 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2658                     focused.clearFocus();
2659                 }
2660             }
2661         }
2662 
2663         // if  the current selection is panned off, we need to remove the selection
2664         if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2665                 && !isViewAncestorOf(selectedView, this)) {
2666             selectedView = null;
2667             hideSelector();
2668 
2669             // but we don't want to set the ressurect position (that would make subsequent
2670             // unhandled key events bring back the item we just scrolled off!)
2671             mResurrectToPosition = INVALID_POSITION;
2672         }
2673 
2674         if (needToRedraw) {
2675             if (selectedView != null) {
2676                 positionSelectorLikeFocus(selectedPos, selectedView);
2677                 mSelectedTop = selectedView.getTop();
2678             }
2679             if (!awakenScrollBars()) {
2680                 invalidate();
2681             }
2682             invokeOnItemScrollListener();
2683             return true;
2684         }
2685 
2686         return false;
2687     }
2688 
2689     /**
2690      * When selection changes, it is possible that the previously selected or the
2691      * next selected item will change its size.  If so, we need to offset some folks,
2692      * and re-layout the items as appropriate.
2693      *
2694      * @param selectedView The currently selected view (before changing selection).
2695      *   should be <code>null</code> if there was no previous selection.
2696      * @param direction Either {@link android.view.View#FOCUS_UP} or
2697      *        {@link android.view.View#FOCUS_DOWN}.
2698      * @param newSelectedPosition The position of the next selection.
2699      * @param newFocusAssigned whether new focus was assigned.  This matters because
2700      *        when something has focus, we don't want to show selection (ugh).
2701      */
handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition, boolean newFocusAssigned)2702     private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2703             boolean newFocusAssigned) {
2704         if (newSelectedPosition == INVALID_POSITION) {
2705             throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2706         }
2707 
2708         // whether or not we are moving down or up, we want to preserve the
2709         // top of whatever view is on top:
2710         // - moving down: the view that had selection
2711         // - moving up: the view that is getting selection
2712         View topView;
2713         View bottomView;
2714         int topViewIndex, bottomViewIndex;
2715         boolean topSelected = false;
2716         final int selectedIndex = mSelectedPosition - mFirstPosition;
2717         final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2718         if (direction == View.FOCUS_UP) {
2719             topViewIndex = nextSelectedIndex;
2720             bottomViewIndex = selectedIndex;
2721             topView = getChildAt(topViewIndex);
2722             bottomView = selectedView;
2723             topSelected = true;
2724         } else {
2725             topViewIndex = selectedIndex;
2726             bottomViewIndex = nextSelectedIndex;
2727             topView = selectedView;
2728             bottomView = getChildAt(bottomViewIndex);
2729         }
2730 
2731         final int numChildren = getChildCount();
2732 
2733         // start with top view: is it changing size?
2734         if (topView != null) {
2735             topView.setSelected(!newFocusAssigned && topSelected);
2736             measureAndAdjustDown(topView, topViewIndex, numChildren);
2737         }
2738 
2739         // is the bottom view changing size?
2740         if (bottomView != null) {
2741             bottomView.setSelected(!newFocusAssigned && !topSelected);
2742             measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2743         }
2744     }
2745 
2746     /**
2747      * Re-measure a child, and if its height changes, lay it out preserving its
2748      * top, and adjust the children below it appropriately.
2749      * @param child The child
2750      * @param childIndex The view group index of the child.
2751      * @param numChildren The number of children in the view group.
2752      */
measureAndAdjustDown(View child, int childIndex, int numChildren)2753     private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2754         int oldHeight = child.getHeight();
2755         measureItem(child);
2756         if (child.getMeasuredHeight() != oldHeight) {
2757             // lay out the view, preserving its top
2758             relayoutMeasuredItem(child);
2759 
2760             // adjust views below appropriately
2761             final int heightDelta = child.getMeasuredHeight() - oldHeight;
2762             for (int i = childIndex + 1; i < numChildren; i++) {
2763                 getChildAt(i).offsetTopAndBottom(heightDelta);
2764             }
2765         }
2766     }
2767 
2768     /**
2769      * Measure a particular list child.
2770      * TODO: unify with setUpChild.
2771      * @param child The child.
2772      */
measureItem(View child)2773     private void measureItem(View child) {
2774         ViewGroup.LayoutParams p = child.getLayoutParams();
2775         if (p == null) {
2776             p = new ViewGroup.LayoutParams(
2777                     ViewGroup.LayoutParams.MATCH_PARENT,
2778                     ViewGroup.LayoutParams.WRAP_CONTENT);
2779         }
2780 
2781         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2782                 mListPadding.left + mListPadding.right, p.width);
2783         int lpHeight = p.height;
2784         int childHeightSpec;
2785         if (lpHeight > 0) {
2786             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2787         } else {
2788             childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2789                     MeasureSpec.UNSPECIFIED);
2790         }
2791         child.measure(childWidthSpec, childHeightSpec);
2792     }
2793 
2794     /**
2795      * Layout a child that has been measured, preserving its top position.
2796      * TODO: unify with setUpChild.
2797      * @param child The child.
2798      */
relayoutMeasuredItem(View child)2799     private void relayoutMeasuredItem(View child) {
2800         final int w = child.getMeasuredWidth();
2801         final int h = child.getMeasuredHeight();
2802         final int childLeft = mListPadding.left;
2803         final int childRight = childLeft + w;
2804         final int childTop = child.getTop();
2805         final int childBottom = childTop + h;
2806         child.layout(childLeft, childTop, childRight, childBottom);
2807     }
2808 
2809     /**
2810      * @return The amount to preview next items when arrow srolling.
2811      */
getArrowScrollPreviewLength()2812     private int getArrowScrollPreviewLength() {
2813         return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2814     }
2815 
2816     /**
2817      * Determine how much we need to scroll in order to get the next selected view
2818      * visible, with a fading edge showing below as applicable.  The amount is
2819      * capped at {@link #getMaxScrollAmount()} .
2820      *
2821      * @param direction either {@link android.view.View#FOCUS_UP} or
2822      *        {@link android.view.View#FOCUS_DOWN}.
2823      * @param nextSelectedPosition The position of the next selection, or
2824      *        {@link #INVALID_POSITION} if there is no next selectable position
2825      * @return The amount to scroll. Note: this is always positive!  Direction
2826      *         needs to be taken into account when actually scrolling.
2827      */
amountToScroll(int direction, int nextSelectedPosition)2828     private int amountToScroll(int direction, int nextSelectedPosition) {
2829         final int listBottom = getHeight() - mListPadding.bottom;
2830         final int listTop = mListPadding.top;
2831 
2832         int numChildren = getChildCount();
2833 
2834         if (direction == View.FOCUS_DOWN) {
2835             int indexToMakeVisible = numChildren - 1;
2836             if (nextSelectedPosition != INVALID_POSITION) {
2837                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2838             }
2839             while (numChildren <= indexToMakeVisible) {
2840                 // Child to view is not attached yet.
2841                 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2842                 numChildren++;
2843             }
2844             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2845             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2846 
2847             int goalBottom = listBottom;
2848             if (positionToMakeVisible < mItemCount - 1) {
2849                 goalBottom -= getArrowScrollPreviewLength();
2850             }
2851 
2852             if (viewToMakeVisible.getBottom() <= goalBottom) {
2853                 // item is fully visible.
2854                 return 0;
2855             }
2856 
2857             if (nextSelectedPosition != INVALID_POSITION
2858                     && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2859                 // item already has enough of it visible, changing selection is good enough
2860                 return 0;
2861             }
2862 
2863             int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2864 
2865             if ((mFirstPosition + numChildren) == mItemCount) {
2866                 // last is last in list -> make sure we don't scroll past it
2867                 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2868                 amountToScroll = Math.min(amountToScroll, max);
2869             }
2870 
2871             return Math.min(amountToScroll, getMaxScrollAmount());
2872         } else {
2873             int indexToMakeVisible = 0;
2874             if (nextSelectedPosition != INVALID_POSITION) {
2875                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2876             }
2877             while (indexToMakeVisible < 0) {
2878                 // Child to view is not attached yet.
2879                 addViewAbove(getChildAt(0), mFirstPosition);
2880                 mFirstPosition--;
2881                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2882             }
2883             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2884             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2885             int goalTop = listTop;
2886             if (positionToMakeVisible > 0) {
2887                 goalTop += getArrowScrollPreviewLength();
2888             }
2889             if (viewToMakeVisible.getTop() >= goalTop) {
2890                 // item is fully visible.
2891                 return 0;
2892             }
2893 
2894             if (nextSelectedPosition != INVALID_POSITION &&
2895                     (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2896                 // item already has enough of it visible, changing selection is good enough
2897                 return 0;
2898             }
2899 
2900             int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2901             if (mFirstPosition == 0) {
2902                 // first is first in list -> make sure we don't scroll past it
2903                 final int max = listTop - getChildAt(0).getTop();
2904                 amountToScroll = Math.min(amountToScroll,  max);
2905             }
2906             return Math.min(amountToScroll, getMaxScrollAmount());
2907         }
2908     }
2909 
2910     /**
2911      * Holds results of focus aware arrow scrolling.
2912      */
2913     static private class ArrowScrollFocusResult {
2914         private int mSelectedPosition;
2915         private int mAmountToScroll;
2916 
2917         /**
2918          * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2919          */
populate(int selectedPosition, int amountToScroll)2920         void populate(int selectedPosition, int amountToScroll) {
2921             mSelectedPosition = selectedPosition;
2922             mAmountToScroll = amountToScroll;
2923         }
2924 
getSelectedPosition()2925         public int getSelectedPosition() {
2926             return mSelectedPosition;
2927         }
2928 
getAmountToScroll()2929         public int getAmountToScroll() {
2930             return mAmountToScroll;
2931         }
2932     }
2933 
2934     /**
2935      * @param direction either {@link android.view.View#FOCUS_UP} or
2936      *        {@link android.view.View#FOCUS_DOWN}.
2937      * @return The position of the next selectable position of the views that
2938      *         are currently visible, taking into account the fact that there might
2939      *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
2940      *         selectable view on screen in the given direction.
2941      */
lookForSelectablePositionOnScreen(int direction)2942     private int lookForSelectablePositionOnScreen(int direction) {
2943         final int firstPosition = mFirstPosition;
2944         if (direction == View.FOCUS_DOWN) {
2945             int startPos = (mSelectedPosition != INVALID_POSITION) ?
2946                     mSelectedPosition + 1 :
2947                     firstPosition;
2948             if (startPos >= mAdapter.getCount()) {
2949                 return INVALID_POSITION;
2950             }
2951             if (startPos < firstPosition) {
2952                 startPos = firstPosition;
2953             }
2954 
2955             final int lastVisiblePos = getLastVisiblePosition();
2956             final ListAdapter adapter = getAdapter();
2957             for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2958                 if (adapter.isEnabled(pos)
2959                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2960                     return pos;
2961                 }
2962             }
2963         } else {
2964             int last = firstPosition + getChildCount() - 1;
2965             int startPos = (mSelectedPosition != INVALID_POSITION) ?
2966                     mSelectedPosition - 1 :
2967                     firstPosition + getChildCount() - 1;
2968             if (startPos < 0 || startPos >= mAdapter.getCount()) {
2969                 return INVALID_POSITION;
2970             }
2971             if (startPos > last) {
2972                 startPos = last;
2973             }
2974 
2975             final ListAdapter adapter = getAdapter();
2976             for (int pos = startPos; pos >= firstPosition; pos--) {
2977                 if (adapter.isEnabled(pos)
2978                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2979                     return pos;
2980                 }
2981             }
2982         }
2983         return INVALID_POSITION;
2984     }
2985 
2986     /**
2987      * Do an arrow scroll based on focus searching.  If a new view is
2988      * given focus, return the selection delta and amount to scroll via
2989      * an {@link ArrowScrollFocusResult}, otherwise, return null.
2990      *
2991      * @param direction either {@link android.view.View#FOCUS_UP} or
2992      *        {@link android.view.View#FOCUS_DOWN}.
2993      * @return The result if focus has changed, or <code>null</code>.
2994      */
arrowScrollFocused(final int direction)2995     private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2996         final View selectedView = getSelectedView();
2997         View newFocus;
2998         if (selectedView != null && selectedView.hasFocus()) {
2999             View oldFocus = selectedView.findFocus();
3000             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
3001         } else {
3002             if (direction == View.FOCUS_DOWN) {
3003                 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
3004                 final int listTop = mListPadding.top +
3005                         (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3006                 final int ySearchPoint =
3007                         (selectedView != null && selectedView.getTop() > listTop) ?
3008                                 selectedView.getTop() :
3009                                 listTop;
3010                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3011             } else {
3012                 final boolean bottomFadingEdgeShowing =
3013                         (mFirstPosition + getChildCount() - 1) < mItemCount;
3014                 final int listBottom = getHeight() - mListPadding.bottom -
3015                         (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3016                 final int ySearchPoint =
3017                         (selectedView != null && selectedView.getBottom() < listBottom) ?
3018                                 selectedView.getBottom() :
3019                                 listBottom;
3020                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3021             }
3022             newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
3023         }
3024 
3025         if (newFocus != null) {
3026             final int positionOfNewFocus = positionOfNewFocus(newFocus);
3027 
3028             // if the focus change is in a different new position, make sure
3029             // we aren't jumping over another selectable position
3030             if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
3031                 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
3032                 if (selectablePosition != INVALID_POSITION &&
3033                         ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
3034                         (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
3035                     return null;
3036                 }
3037             }
3038 
3039             int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
3040 
3041             final int maxScrollAmount = getMaxScrollAmount();
3042             if (focusScroll < maxScrollAmount) {
3043                 // not moving too far, safe to give next view focus
3044                 newFocus.requestFocus(direction);
3045                 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
3046                 return mArrowScrollFocusResult;
3047             } else if (distanceToView(newFocus) < maxScrollAmount){
3048                 // Case to consider:
3049                 // too far to get entire next focusable on screen, but by going
3050                 // max scroll amount, we are getting it at least partially in view,
3051                 // so give it focus and scroll the max ammount.
3052                 newFocus.requestFocus(direction);
3053                 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
3054                 return mArrowScrollFocusResult;
3055             }
3056         }
3057         return null;
3058     }
3059 
3060     /**
3061      * @param newFocus The view that would have focus.
3062      * @return the position that contains newFocus
3063      */
3064     private int positionOfNewFocus(View newFocus) {
3065         final int numChildren = getChildCount();
3066         for (int i = 0; i < numChildren; i++) {
3067             final View child = getChildAt(i);
3068             if (isViewAncestorOf(newFocus, child)) {
3069                 return mFirstPosition + i;
3070             }
3071         }
3072         throw new IllegalArgumentException("newFocus is not a child of any of the"
3073                 + " children of the list!");
3074     }
3075 
3076     /**
3077      * Return true if child is an ancestor of parent, (or equal to the parent).
3078      */
3079     private boolean isViewAncestorOf(View child, View parent) {
3080         if (child == parent) {
3081             return true;
3082         }
3083 
3084         final ViewParent theParent = child.getParent();
3085         return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
3086     }
3087 
3088     /**
3089      * Determine how much we need to scroll in order to get newFocus in view.
3090      * @param direction either {@link android.view.View#FOCUS_UP} or
3091      *        {@link android.view.View#FOCUS_DOWN}.
3092      * @param newFocus The view that would take focus.
3093      * @param positionOfNewFocus The position of the list item containing newFocus
3094      * @return The amount to scroll.  Note: this is always positive!  Direction
3095      *   needs to be taken into account when actually scrolling.
3096      */
3097     private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
3098         int amountToScroll = 0;
3099         newFocus.getDrawingRect(mTempRect);
3100         offsetDescendantRectToMyCoords(newFocus, mTempRect);
3101         if (direction == View.FOCUS_UP) {
3102             if (mTempRect.top < mListPadding.top) {
3103                 amountToScroll = mListPadding.top - mTempRect.top;
3104                 if (positionOfNewFocus > 0) {
3105                     amountToScroll += getArrowScrollPreviewLength();
3106                 }
3107             }
3108         } else {
3109             final int listBottom = getHeight() - mListPadding.bottom;
3110             if (mTempRect.bottom > listBottom) {
3111                 amountToScroll = mTempRect.bottom - listBottom;
3112                 if (positionOfNewFocus < mItemCount - 1) {
3113                     amountToScroll += getArrowScrollPreviewLength();
3114                 }
3115             }
3116         }
3117         return amountToScroll;
3118     }
3119 
3120     /**
3121      * Determine the distance to the nearest edge of a view in a particular
3122      * direction.
3123      *
3124      * @param descendant A descendant of this list.
3125      * @return The distance, or 0 if the nearest edge is already on screen.
3126      */
3127     private int distanceToView(View descendant) {
3128         int distance = 0;
3129         descendant.getDrawingRect(mTempRect);
3130         offsetDescendantRectToMyCoords(descendant, mTempRect);
3131         final int listBottom = mBottom - mTop - mListPadding.bottom;
3132         if (mTempRect.bottom < mListPadding.top) {
3133             distance = mListPadding.top - mTempRect.bottom;
3134         } else if (mTempRect.top > listBottom) {
3135             distance = mTempRect.top - listBottom;
3136         }
3137         return distance;
3138     }
3139 
3140 
3141     /**
3142      * Scroll the children by amount, adding a view at the end and removing
3143      * views that fall off as necessary.
3144      *
3145      * @param amount The amount (positive or negative) to scroll.
3146      */
3147     private void scrollListItemsBy(int amount) {
3148         offsetChildrenTopAndBottom(amount);
3149 
3150         final int listBottom = getHeight() - mListPadding.bottom;
3151         final int listTop = mListPadding.top;
3152         final AbsListView.RecycleBin recycleBin = mRecycler;
3153 
3154         if (amount < 0) {
3155             // shifted items up
3156 
3157             // may need to pan views into the bottom space
3158             int numChildren = getChildCount();
3159             View last = getChildAt(numChildren - 1);
3160             while (last.getBottom() < listBottom) {
3161                 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3162                 if (lastVisiblePosition < mItemCount - 1) {
3163                     last = addViewBelow(last, lastVisiblePosition);
3164                     numChildren++;
3165                 } else {
3166                     break;
3167                 }
3168             }
3169 
3170             // may have brought in the last child of the list that is skinnier
3171             // than the fading edge, thereby leaving space at the end.  need
3172             // to shift back
3173             if (last.getBottom() < listBottom) {
3174                 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3175             }
3176 
3177             // top views may be panned off screen
3178             View first = getChildAt(0);
3179             while (first.getBottom() < listTop) {
3180                 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3181                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3182                     recycleBin.addScrapView(first, mFirstPosition);
3183                 }
3184                 detachViewFromParent(first);
3185                 first = getChildAt(0);
3186                 mFirstPosition++;
3187             }
3188         } else {
3189             // shifted items down
3190             View first = getChildAt(0);
3191 
3192             // may need to pan views into top
3193             while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3194                 first = addViewAbove(first, mFirstPosition);
3195                 mFirstPosition--;
3196             }
3197 
3198             // may have brought the very first child of the list in too far and
3199             // need to shift it back
3200             if (first.getTop() > listTop) {
3201                 offsetChildrenTopAndBottom(listTop - first.getTop());
3202             }
3203 
3204             int lastIndex = getChildCount() - 1;
3205             View last = getChildAt(lastIndex);
3206 
3207             // bottom view may be panned off screen
3208             while (last.getTop() > listBottom) {
3209                 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3210                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3211                     recycleBin.addScrapView(last, mFirstPosition+lastIndex);
3212                 }
3213                 detachViewFromParent(last);
3214                 last = getChildAt(--lastIndex);
3215             }
3216         }
3217         recycleBin.fullyDetachScrapViews();
3218         removeUnusedFixedViews(mHeaderViewInfos);
3219         removeUnusedFixedViews(mFooterViewInfos);
3220     }
3221 
3222     private View addViewAbove(View theView, int position) {
3223         int abovePosition = position - 1;
3224         View view = obtainView(abovePosition, mIsScrap);
3225         int edgeOfNewChild = theView.getTop() - mDividerHeight;
3226         setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3227                 false, mIsScrap[0]);
3228         return view;
3229     }
3230 
3231     private View addViewBelow(View theView, int position) {
3232         int belowPosition = position + 1;
3233         View view = obtainView(belowPosition, mIsScrap);
3234         int edgeOfNewChild = theView.getBottom() + mDividerHeight;
3235         setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3236                 false, mIsScrap[0]);
3237         return view;
3238     }
3239 
3240     /**
3241      * Indicates that the views created by the ListAdapter can contain focusable
3242      * items.
3243      *
3244      * @param itemsCanFocus true if items can get focus, false otherwise
3245      */
3246     public void setItemsCanFocus(boolean itemsCanFocus) {
3247         mItemsCanFocus = itemsCanFocus;
3248         if (!itemsCanFocus) {
3249             setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3250         }
3251     }
3252 
3253     /**
3254      * @return Whether the views created by the ListAdapter can contain focusable
3255      * items.
3256      */
3257     public boolean getItemsCanFocus() {
3258         return mItemsCanFocus;
3259     }
3260 
3261     @Override
3262     public boolean isOpaque() {
3263         boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
3264                 hasOpaqueScrollbars()) || super.isOpaque();
3265         if (retValue) {
3266             // only return true if the list items cover the entire area of the view
3267             final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
3268             View first = getChildAt(0);
3269             if (first == null || first.getTop() > listTop) {
3270                 return false;
3271             }
3272             final int listBottom = getHeight() -
3273                     (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
3274             View last = getChildAt(getChildCount() - 1);
3275             if (last == null || last.getBottom() < listBottom) {
3276                 return false;
3277             }
3278         }
3279         return retValue;
3280     }
3281 
3282     @Override
3283     public void setCacheColorHint(int color) {
3284         final boolean opaque = (color >>> 24) == 0xFF;
3285         mIsCacheColorOpaque = opaque;
3286         if (opaque) {
3287             if (mDividerPaint == null) {
3288                 mDividerPaint = new Paint();
3289             }
3290             mDividerPaint.setColor(color);
3291         }
3292         super.setCacheColorHint(color);
3293     }
3294 
3295     void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3296         final int height = drawable.getMinimumHeight();
3297 
3298         canvas.save();
3299         canvas.clipRect(bounds);
3300 
3301         final int span = bounds.bottom - bounds.top;
3302         if (span < height) {
3303             bounds.top = bounds.bottom - height;
3304         }
3305 
3306         drawable.setBounds(bounds);
3307         drawable.draw(canvas);
3308 
3309         canvas.restore();
3310     }
3311 
3312     void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3313         final int height = drawable.getMinimumHeight();
3314 
3315         canvas.save();
3316         canvas.clipRect(bounds);
3317 
3318         final int span = bounds.bottom - bounds.top;
3319         if (span < height) {
3320             bounds.bottom = bounds.top + height;
3321         }
3322 
3323         drawable.setBounds(bounds);
3324         drawable.draw(canvas);
3325 
3326         canvas.restore();
3327     }
3328 
3329     @Override
3330     protected void dispatchDraw(Canvas canvas) {
3331         if (mCachingStarted) {
3332             mCachingActive = true;
3333         }
3334 
3335         // Draw the dividers
3336         final int dividerHeight = mDividerHeight;
3337         final Drawable overscrollHeader = mOverScrollHeader;
3338         final Drawable overscrollFooter = mOverScrollFooter;
3339         final boolean drawOverscrollHeader = overscrollHeader != null;
3340         final boolean drawOverscrollFooter = overscrollFooter != null;
3341         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3342 
3343         if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
3344             // Only modify the top and bottom in the loop, we set the left and right here
3345             final Rect bounds = mTempRect;
3346             bounds.left = mPaddingLeft;
3347             bounds.right = mRight - mLeft - mPaddingRight;
3348 
3349             final int count = getChildCount();
3350             final int headerCount = mHeaderViewInfos.size();
3351             final int itemCount = mItemCount;
3352             final int footerLimit = (itemCount - mFooterViewInfos.size());
3353             final boolean headerDividers = mHeaderDividersEnabled;
3354             final boolean footerDividers = mFooterDividersEnabled;
3355             final int first = mFirstPosition;
3356             final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3357             final ListAdapter adapter = mAdapter;
3358             // If the list is opaque *and* the background is not, we want to
3359             // fill a rect where the dividers would be for non-selectable items
3360             // If the list is opaque and the background is also opaque, we don't
3361             // need to draw anything since the background will do it for us
3362             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3363 
3364             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
3365                 mDividerPaint = new Paint();
3366                 mDividerPaint.setColor(getCacheColorHint());
3367             }
3368             final Paint paint = mDividerPaint;
3369 
3370             int effectivePaddingTop = 0;
3371             int effectivePaddingBottom = 0;
3372             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3373                 effectivePaddingTop = mListPadding.top;
3374                 effectivePaddingBottom = mListPadding.bottom;
3375             }
3376 
3377             final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
3378             if (!mStackFromBottom) {
3379                 int bottom = 0;
3380 
3381                 // Draw top divider or header for overscroll
3382                 final int scrollY = mScrollY;
3383                 if (count > 0 && scrollY < 0) {
3384                     if (drawOverscrollHeader) {
3385                         bounds.bottom = 0;
3386                         bounds.top = scrollY;
3387                         drawOverscrollHeader(canvas, overscrollHeader, bounds);
3388                     } else if (drawDividers) {
3389                         bounds.bottom = 0;
3390                         bounds.top = -dividerHeight;
3391                         drawDivider(canvas, bounds, -1);
3392                     }
3393                 }
3394 
3395                 for (int i = 0; i < count; i++) {
3396                     final int itemIndex = (first + i);
3397                     final boolean isHeader = (itemIndex < headerCount);
3398                     final boolean isFooter = (itemIndex >= footerLimit);
3399                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3400                         final View child = getChildAt(i);
3401                         bottom = child.getBottom();
3402                         final boolean isLastItem = (i == (count - 1));
3403 
3404                         if (drawDividers && (bottom < listBottom)
3405                                 && !(drawOverscrollFooter && isLastItem)) {
3406                             final int nextIndex = (itemIndex + 1);
3407                             // Draw dividers between enabled items, headers
3408                             // and/or footers when enabled and requested, and
3409                             // after the last enabled item.
3410                             if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3411                                     && (nextIndex >= headerCount)) && (isLastItem
3412                                     || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3413                                             && (nextIndex < footerLimit)))) {
3414                                 bounds.top = bottom;
3415                                 bounds.bottom = bottom + dividerHeight;
3416                                 drawDivider(canvas, bounds, i);
3417                             } else if (fillForMissingDividers) {
3418                                 bounds.top = bottom;
3419                                 bounds.bottom = bottom + dividerHeight;
3420                                 canvas.drawRect(bounds, paint);
3421                             }
3422                         }
3423                     }
3424                 }
3425 
3426                 final int overFooterBottom = mBottom + mScrollY;
3427                 if (drawOverscrollFooter && first + count == itemCount &&
3428                         overFooterBottom > bottom) {
3429                     bounds.top = bottom;
3430                     bounds.bottom = overFooterBottom;
3431                     drawOverscrollFooter(canvas, overscrollFooter, bounds);
3432                 }
3433             } else {
3434                 int top;
3435 
3436                 final int scrollY = mScrollY;
3437 
3438                 if (count > 0 && drawOverscrollHeader) {
3439                     bounds.top = scrollY;
3440                     bounds.bottom = getChildAt(0).getTop();
3441                     drawOverscrollHeader(canvas, overscrollHeader, bounds);
3442                 }
3443 
3444                 final int start = drawOverscrollHeader ? 1 : 0;
3445                 for (int i = start; i < count; i++) {
3446                     final int itemIndex = (first + i);
3447                     final boolean isHeader = (itemIndex < headerCount);
3448                     final boolean isFooter = (itemIndex >= footerLimit);
3449                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3450                         final View child = getChildAt(i);
3451                         top = child.getTop();
3452                         if (drawDividers && (top > effectivePaddingTop)) {
3453                             final boolean isFirstItem = (i == start);
3454                             final int previousIndex = (itemIndex - 1);
3455                             // Draw dividers between enabled items, headers
3456                             // and/or footers when enabled and requested, and
3457                             // before the first enabled item.
3458                             if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3459                                     && (previousIndex >= headerCount)) && (isFirstItem ||
3460                                     adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3461                                             && (previousIndex < footerLimit)))) {
3462                                 bounds.top = top - dividerHeight;
3463                                 bounds.bottom = top;
3464                                 // Give the method the child ABOVE the divider,
3465                                 // so we subtract one from our child position.
3466                                 // Give -1 when there is no child above the
3467                                 // divider.
3468                                 drawDivider(canvas, bounds, i - 1);
3469                             } else if (fillForMissingDividers) {
3470                                 bounds.top = top - dividerHeight;
3471                                 bounds.bottom = top;
3472                                 canvas.drawRect(bounds, paint);
3473                             }
3474                         }
3475                     }
3476                 }
3477 
3478                 if (count > 0 && scrollY > 0) {
3479                     if (drawOverscrollFooter) {
3480                         final int absListBottom = mBottom;
3481                         bounds.top = absListBottom;
3482                         bounds.bottom = absListBottom + scrollY;
3483                         drawOverscrollFooter(canvas, overscrollFooter, bounds);
3484                     } else if (drawDividers) {
3485                         bounds.top = listBottom;
3486                         bounds.bottom = listBottom + dividerHeight;
3487                         drawDivider(canvas, bounds, -1);
3488                     }
3489                 }
3490             }
3491         }
3492 
3493         // Draw the indicators (these should be drawn above the dividers) and children
3494         super.dispatchDraw(canvas);
3495     }
3496 
3497     @Override
3498     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3499         boolean more = super.drawChild(canvas, child, drawingTime);
3500         if (mCachingActive && child.mCachingFailed) {
3501             mCachingActive = false;
3502         }
3503         return more;
3504     }
3505 
3506     /**
3507      * Draws a divider for the given child in the given bounds.
3508      *
3509      * @param canvas The canvas to draw to.
3510      * @param bounds The bounds of the divider.
3511      * @param childIndex The index of child (of the View) above the divider.
3512      *            This will be -1 if there is no child above the divider to be
3513      *            drawn.
3514      */
3515     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3516         // This widget draws the same divider for all children
3517         final Drawable divider = mDivider;
3518 
3519         divider.setBounds(bounds);
3520         divider.draw(canvas);
3521     }
3522 
3523     /**
3524      * Returns the drawable that will be drawn between each item in the list.
3525      *
3526      * @return the current drawable drawn between list elements
3527      * @attr ref R.styleable#ListView_divider
3528      */
3529     @Nullable
3530     public Drawable getDivider() {
3531         return mDivider;
3532     }
3533 
3534     /**
3535      * Sets the drawable that will be drawn between each item in the list.
3536      * <p>
3537      * <strong>Note:</strong> If the drawable does not have an intrinsic
3538      * height, you should also call {@link #setDividerHeight(int)}.
3539      *
3540      * @param divider the drawable to use
3541      * @attr ref R.styleable#ListView_divider
3542      */
3543     public void setDivider(@Nullable Drawable divider) {
3544         if (divider != null) {
3545             mDividerHeight = divider.getIntrinsicHeight();
3546         } else {
3547             mDividerHeight = 0;
3548         }
3549         mDivider = divider;
3550         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
3551         requestLayout();
3552         invalidate();
3553     }
3554 
3555     /**
3556      * @return Returns the height of the divider that will be drawn between each item in the list.
3557      */
3558     public int getDividerHeight() {
3559         return mDividerHeight;
3560     }
3561 
3562     /**
3563      * Sets the height of the divider that will be drawn between each item in the list. Calling
3564      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3565      *
3566      * @param height The new height of the divider in pixels.
3567      */
3568     public void setDividerHeight(int height) {
3569         mDividerHeight = height;
3570         requestLayout();
3571         invalidate();
3572     }
3573 
3574     /**
3575      * Enables or disables the drawing of the divider for header views.
3576      *
3577      * @param headerDividersEnabled True to draw the headers, false otherwise.
3578      *
3579      * @see #setFooterDividersEnabled(boolean)
3580      * @see #areHeaderDividersEnabled()
3581      * @see #addHeaderView(android.view.View)
3582      */
3583     public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3584         mHeaderDividersEnabled = headerDividersEnabled;
3585         invalidate();
3586     }
3587 
3588     /**
3589      * @return Whether the drawing of the divider for header views is enabled
3590      *
3591      * @see #setHeaderDividersEnabled(boolean)
3592      */
3593     public boolean areHeaderDividersEnabled() {
3594         return mHeaderDividersEnabled;
3595     }
3596 
3597     /**
3598      * Enables or disables the drawing of the divider for footer views.
3599      *
3600      * @param footerDividersEnabled True to draw the footers, false otherwise.
3601      *
3602      * @see #setHeaderDividersEnabled(boolean)
3603      * @see #areFooterDividersEnabled()
3604      * @see #addFooterView(android.view.View)
3605      */
3606     public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3607         mFooterDividersEnabled = footerDividersEnabled;
3608         invalidate();
3609     }
3610 
3611     /**
3612      * @return Whether the drawing of the divider for footer views is enabled
3613      *
3614      * @see #setFooterDividersEnabled(boolean)
3615      */
3616     public boolean areFooterDividersEnabled() {
3617         return mFooterDividersEnabled;
3618     }
3619 
3620     /**
3621      * Sets the drawable that will be drawn above all other list content.
3622      * This area can become visible when the user overscrolls the list.
3623      *
3624      * @param header The drawable to use
3625      */
3626     public void setOverscrollHeader(Drawable header) {
3627         mOverScrollHeader = header;
3628         if (mScrollY < 0) {
3629             invalidate();
3630         }
3631     }
3632 
3633     /**
3634      * @return The drawable that will be drawn above all other list content
3635      */
3636     public Drawable getOverscrollHeader() {
3637         return mOverScrollHeader;
3638     }
3639 
3640     /**
3641      * Sets the drawable that will be drawn below all other list content.
3642      * This area can become visible when the user overscrolls the list,
3643      * or when the list's content does not fully fill the container area.
3644      *
3645      * @param footer The drawable to use
3646      */
3647     public void setOverscrollFooter(Drawable footer) {
3648         mOverScrollFooter = footer;
3649         invalidate();
3650     }
3651 
3652     /**
3653      * @return The drawable that will be drawn below all other list content
3654      */
3655     public Drawable getOverscrollFooter() {
3656         return mOverScrollFooter;
3657     }
3658 
3659     @Override
3660     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3661         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3662 
3663         final ListAdapter adapter = mAdapter;
3664         int closetChildIndex = -1;
3665         int closestChildTop = 0;
3666         if (adapter != null && gainFocus && previouslyFocusedRect != null) {
3667             previouslyFocusedRect.offset(mScrollX, mScrollY);
3668 
3669             // Don't cache the result of getChildCount or mFirstPosition here,
3670             // it could change in layoutChildren.
3671             if (adapter.getCount() < getChildCount() + mFirstPosition) {
3672                 mLayoutMode = LAYOUT_NORMAL;
3673                 layoutChildren();
3674             }
3675 
3676             // figure out which item should be selected based on previously
3677             // focused rect
3678             Rect otherRect = mTempRect;
3679             int minDistance = Integer.MAX_VALUE;
3680             final int childCount = getChildCount();
3681             final int firstPosition = mFirstPosition;
3682 
3683             for (int i = 0; i < childCount; i++) {
3684                 // only consider selectable views
3685                 if (!adapter.isEnabled(firstPosition + i)) {
3686                     continue;
3687                 }
3688 
3689                 View other = getChildAt(i);
3690                 other.getDrawingRect(otherRect);
3691                 offsetDescendantRectToMyCoords(other, otherRect);
3692                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3693 
3694                 if (distance < minDistance) {
3695                     minDistance = distance;
3696                     closetChildIndex = i;
3697                     closestChildTop = other.getTop();
3698                 }
3699             }
3700         }
3701 
3702         if (closetChildIndex >= 0) {
3703             setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
3704         } else {
3705             requestLayout();
3706         }
3707     }
3708 
3709 
3710     /*
3711      * (non-Javadoc)
3712      *
3713      * Children specified in XML are assumed to be header views. After we have
3714      * parsed them move them out of the children list and into mHeaderViews.
3715      */
3716     @Override
3717     protected void onFinishInflate() {
3718         super.onFinishInflate();
3719 
3720         int count = getChildCount();
3721         if (count > 0) {
3722             for (int i = 0; i < count; ++i) {
3723                 addHeaderView(getChildAt(i));
3724             }
3725             removeAllViews();
3726         }
3727     }
3728 
3729     /* (non-Javadoc)
3730      * @see android.view.View#findViewById(int)
3731      * First look in our children, then in any header and footer views that may be scrolled off.
3732      */
3733     @Override
3734     protected View findViewTraversal(@IdRes int id) {
3735         View v;
3736         v = super.findViewTraversal(id);
3737         if (v == null) {
3738             v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3739             if (v != null) {
3740                 return v;
3741             }
3742             v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3743             if (v != null) {
3744                 return v;
3745             }
3746         }
3747         return v;
3748     }
3749 
3750     /* (non-Javadoc)
3751      *
3752      * Look in the passed in list of headers or footers for the view.
3753      */
3754     View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3755         if (where != null) {
3756             int len = where.size();
3757             View v;
3758 
3759             for (int i = 0; i < len; i++) {
3760                 v = where.get(i).view;
3761 
3762                 if (!v.isRootNamespace()) {
3763                     v = v.findViewById(id);
3764 
3765                     if (v != null) {
3766                         return v;
3767                     }
3768                 }
3769             }
3770         }
3771         return null;
3772     }
3773 
3774     /* (non-Javadoc)
3775      * @see android.view.View#findViewWithTag(Object)
3776      * First look in our children, then in any header and footer views that may be scrolled off.
3777      */
3778     @Override
3779     protected View findViewWithTagTraversal(Object tag) {
3780         View v;
3781         v = super.findViewWithTagTraversal(tag);
3782         if (v == null) {
3783             v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
3784             if (v != null) {
3785                 return v;
3786             }
3787 
3788             v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
3789             if (v != null) {
3790                 return v;
3791             }
3792         }
3793         return v;
3794     }
3795 
3796     /* (non-Javadoc)
3797      *
3798      * Look in the passed in list of headers or footers for the view with the tag.
3799      */
3800     View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
3801         if (where != null) {
3802             int len = where.size();
3803             View v;
3804 
3805             for (int i = 0; i < len; i++) {
3806                 v = where.get(i).view;
3807 
3808                 if (!v.isRootNamespace()) {
3809                     v = v.findViewWithTag(tag);
3810 
3811                     if (v != null) {
3812                         return v;
3813                     }
3814                 }
3815             }
3816         }
3817         return null;
3818     }
3819 
3820     /**
3821      * @hide
3822      * @see android.view.View#findViewByPredicate(Predicate)
3823      * First look in our children, then in any header and footer views that may be scrolled off.
3824      */
3825     @Override
3826     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
3827         View v;
3828         v = super.findViewByPredicateTraversal(predicate, childToSkip);
3829         if (v == null) {
3830             v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
3831             if (v != null) {
3832                 return v;
3833             }
3834 
3835             v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
3836             if (v != null) {
3837                 return v;
3838             }
3839         }
3840         return v;
3841     }
3842 
3843     /* (non-Javadoc)
3844      *
3845      * Look in the passed in list of headers or footers for the first view that matches
3846      * the predicate.
3847      */
3848     View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
3849             Predicate<View> predicate, View childToSkip) {
3850         if (where != null) {
3851             int len = where.size();
3852             View v;
3853 
3854             for (int i = 0; i < len; i++) {
3855                 v = where.get(i).view;
3856 
3857                 if (v != childToSkip && !v.isRootNamespace()) {
3858                     v = v.findViewByPredicate(predicate);
3859 
3860                     if (v != null) {
3861                         return v;
3862                     }
3863                 }
3864             }
3865         }
3866         return null;
3867     }
3868 
3869     /**
3870      * Returns the set of checked items ids. The result is only valid if the
3871      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3872      *
3873      * @return A new array which contains the id of each checked item in the
3874      *         list.
3875      *
3876      * @deprecated Use {@link #getCheckedItemIds()} instead.
3877      */
3878     @Deprecated
3879     public long[] getCheckItemIds() {
3880         // Use new behavior that correctly handles stable ID mapping.
3881         if (mAdapter != null && mAdapter.hasStableIds()) {
3882             return getCheckedItemIds();
3883         }
3884 
3885         // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3886         // Fall back to it to support legacy apps.
3887         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3888             final SparseBooleanArray states = mCheckStates;
3889             final int count = states.size();
3890             final long[] ids = new long[count];
3891             final ListAdapter adapter = mAdapter;
3892 
3893             int checkedCount = 0;
3894             for (int i = 0; i < count; i++) {
3895                 if (states.valueAt(i)) {
3896                     ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3897                 }
3898             }
3899 
3900             // Trim array if needed. mCheckStates may contain false values
3901             // resulting in checkedCount being smaller than count.
3902             if (checkedCount == count) {
3903                 return ids;
3904             } else {
3905                 final long[] result = new long[checkedCount];
3906                 System.arraycopy(ids, 0, result, 0, checkedCount);
3907 
3908                 return result;
3909             }
3910         }
3911         return new long[0];
3912     }
3913 
3914     @Override
3915     int getHeightForPosition(int position) {
3916         final int height = super.getHeightForPosition(position);
3917         if (shouldAdjustHeightForDivider(position)) {
3918             return height + mDividerHeight;
3919         }
3920         return height;
3921     }
3922 
3923     private boolean shouldAdjustHeightForDivider(int itemIndex) {
3924         final int dividerHeight = mDividerHeight;
3925         final Drawable overscrollHeader = mOverScrollHeader;
3926         final Drawable overscrollFooter = mOverScrollFooter;
3927         final boolean drawOverscrollHeader = overscrollHeader != null;
3928         final boolean drawOverscrollFooter = overscrollFooter != null;
3929         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3930 
3931         if (drawDividers) {
3932             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3933             final int itemCount = mItemCount;
3934             final int headerCount = mHeaderViewInfos.size();
3935             final int footerLimit = (itemCount - mFooterViewInfos.size());
3936             final boolean isHeader = (itemIndex < headerCount);
3937             final boolean isFooter = (itemIndex >= footerLimit);
3938             final boolean headerDividers = mHeaderDividersEnabled;
3939             final boolean footerDividers = mFooterDividersEnabled;
3940             if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3941                 final ListAdapter adapter = mAdapter;
3942                 if (!mStackFromBottom) {
3943                     final boolean isLastItem = (itemIndex == (itemCount - 1));
3944                     if (!drawOverscrollFooter || !isLastItem) {
3945                         final int nextIndex = itemIndex + 1;
3946                         // Draw dividers between enabled items, headers
3947                         // and/or footers when enabled and requested, and
3948                         // after the last enabled item.
3949                         if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3950                                 && (nextIndex >= headerCount)) && (isLastItem
3951                                 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3952                                                 && (nextIndex < footerLimit)))) {
3953                             return true;
3954                         } else if (fillForMissingDividers) {
3955                             return true;
3956                         }
3957                     }
3958                 } else {
3959                     final int start = drawOverscrollHeader ? 1 : 0;
3960                     final boolean isFirstItem = (itemIndex == start);
3961                     if (!isFirstItem) {
3962                         final int previousIndex = (itemIndex - 1);
3963                         // Draw dividers between enabled items, headers
3964                         // and/or footers when enabled and requested, and
3965                         // before the first enabled item.
3966                         if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3967                                 && (previousIndex >= headerCount)) && (isFirstItem ||
3968                                 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3969                                         && (previousIndex < footerLimit)))) {
3970                             return true;
3971                         } else if (fillForMissingDividers) {
3972                             return true;
3973                         }
3974                     }
3975                 }
3976             }
3977         }
3978 
3979         return false;
3980     }
3981 
3982     @Override
3983     public CharSequence getAccessibilityClassName() {
3984         return ListView.class.getName();
3985     }
3986 
3987     /** @hide */
3988     @Override
3989     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3990         super.onInitializeAccessibilityNodeInfoInternal(info);
3991 
3992         final int rowsCount = getCount();
3993         final int selectionMode = getSelectionModeForAccessibility();
3994         final CollectionInfo collectionInfo = CollectionInfo.obtain(
3995                 rowsCount, 1, false, selectionMode);
3996         info.setCollectionInfo(collectionInfo);
3997 
3998         if (rowsCount > 0) {
3999             info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
4000         }
4001     }
4002 
4003     /** @hide */
4004     @Override
4005     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4006         if (super.performAccessibilityActionInternal(action, arguments)) {
4007             return true;
4008         }
4009 
4010         switch (action) {
4011             case R.id.accessibilityActionScrollToPosition: {
4012                 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
4013                 final int position = Math.min(row, getCount() - 1);
4014                 if (row >= 0) {
4015                     // The accessibility service gets data asynchronously, so
4016                     // we'll be a little lenient by clamping the last position.
4017                     smoothScrollToPosition(position);
4018                     return true;
4019                 }
4020             } break;
4021         }
4022 
4023         return false;
4024     }
4025 
4026     @Override
4027     public void onInitializeAccessibilityNodeInfoForItem(
4028             View view, int position, AccessibilityNodeInfo info) {
4029         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
4030 
4031         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
4032         final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
4033         final boolean isSelected = isItemChecked(position);
4034         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
4035                 position, 1, 0, 1, isHeading, isSelected);
4036         info.setCollectionItemInfo(itemInfo);
4037     }
4038 
4039     /** @hide */
4040     @Override
4041     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
4042         super.encodeProperties(encoder);
4043 
4044         encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
4045     }
4046 }
4047