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