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