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