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.MotionEvent;
     36 import android.view.SoundEffectConstants;
     37 import android.view.View;
     38 import android.view.ViewDebug;
     39 import android.view.ViewGroup;
     40 import android.view.ViewParent;
     41 import android.view.accessibility.AccessibilityEvent;
     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}resources/tutorials/views/hello-listview.html">List View
     61  * tutorial</a>.</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         return selectedView;
    682     }
    683 
    684     /**
    685      * Fills the list from pos up to the top of the list view.
    686      *
    687      * @param pos The first position to put in the list
    688      *
    689      * @param nextBottom The location where the bottom of the item associated
    690      *        with pos should be drawn
    691      *
    692      * @return The view that is currently selected
    693      */
    694     private View fillUp(int pos, int nextBottom) {
    695         View selectedView = null;
    696 
    697         int end = 0;
    698         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    699             end = mListPadding.top;
    700         }
    701 
    702         while (nextBottom > end && pos >= 0) {
    703             // is this the selected item?
    704             boolean selected = pos == mSelectedPosition;
    705             View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
    706             nextBottom = child.getTop() - mDividerHeight;
    707             if (selected) {
    708                 selectedView = child;
    709             }
    710             pos--;
    711         }
    712 
    713         mFirstPosition = pos + 1;
    714 
    715         return selectedView;
    716     }
    717 
    718     /**
    719      * Fills the list from top to bottom, starting with mFirstPosition
    720      *
    721      * @param nextTop The location where the top of the first item should be
    722      *        drawn
    723      *
    724      * @return The view that is currently selected
    725      */
    726     private View fillFromTop(int nextTop) {
    727         mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    728         mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    729         if (mFirstPosition < 0) {
    730             mFirstPosition = 0;
    731         }
    732         return fillDown(mFirstPosition, nextTop);
    733     }
    734 
    735 
    736     /**
    737      * Put mSelectedPosition in the middle of the screen and then build up and
    738      * down from there. This method forces mSelectedPosition to the center.
    739      *
    740      * @param childrenTop Top of the area in which children can be drawn, as
    741      *        measured in pixels
    742      * @param childrenBottom Bottom of the area in which children can be drawn,
    743      *        as measured in pixels
    744      * @return Currently selected view
    745      */
    746     private View fillFromMiddle(int childrenTop, int childrenBottom) {
    747         int height = childrenBottom - childrenTop;
    748 
    749         int position = reconcileSelectedPosition();
    750 
    751         View sel = makeAndAddView(position, childrenTop, true,
    752                 mListPadding.left, true);
    753         mFirstPosition = position;
    754 
    755         int selHeight = sel.getMeasuredHeight();
    756         if (selHeight <= height) {
    757             sel.offsetTopAndBottom((height - selHeight) / 2);
    758         }
    759 
    760         fillAboveAndBelow(sel, position);
    761 
    762         if (!mStackFromBottom) {
    763             correctTooHigh(getChildCount());
    764         } else {
    765             correctTooLow(getChildCount());
    766         }
    767 
    768         return sel;
    769     }
    770 
    771     /**
    772      * Once the selected view as been placed, fill up the visible area above and
    773      * below it.
    774      *
    775      * @param sel The selected view
    776      * @param position The position corresponding to sel
    777      */
    778     private void fillAboveAndBelow(View sel, int position) {
    779         final int dividerHeight = mDividerHeight;
    780         if (!mStackFromBottom) {
    781             fillUp(position - 1, sel.getTop() - dividerHeight);
    782             adjustViewsUpOrDown();
    783             fillDown(position + 1, sel.getBottom() + dividerHeight);
    784         } else {
    785             fillDown(position + 1, sel.getBottom() + dividerHeight);
    786             adjustViewsUpOrDown();
    787             fillUp(position - 1, sel.getTop() - dividerHeight);
    788         }
    789     }
    790 
    791 
    792     /**
    793      * Fills the grid based on positioning the new selection at a specific
    794      * location. The selection may be moved so that it does not intersect the
    795      * faded edges. The grid is then filled upwards and downwards from there.
    796      *
    797      * @param selectedTop Where the selected item should be
    798      * @param childrenTop Where to start drawing children
    799      * @param childrenBottom Last pixel where children can be drawn
    800      * @return The view that currently has selection
    801      */
    802     private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
    803         int fadingEdgeLength = getVerticalFadingEdgeLength();
    804         final int selectedPosition = mSelectedPosition;
    805 
    806         View sel;
    807 
    808         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    809                 selectedPosition);
    810         final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
    811                 selectedPosition);
    812 
    813         sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
    814 
    815 
    816         // Some of the newly selected item extends below the bottom of the list
    817         if (sel.getBottom() > bottomSelectionPixel) {
    818             // Find space available above the selection into which we can scroll
    819             // upwards
    820             final int spaceAbove = sel.getTop() - topSelectionPixel;
    821 
    822             // Find space required to bring the bottom of the selected item
    823             // fully into view
    824             final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    825             final int offset = Math.min(spaceAbove, spaceBelow);
    826 
    827             // Now offset the selected item to get it into view
    828             sel.offsetTopAndBottom(-offset);
    829         } else if (sel.getTop() < topSelectionPixel) {
    830             // Find space required to bring the top of the selected item fully
    831             // into view
    832             final int spaceAbove = topSelectionPixel - sel.getTop();
    833 
    834             // Find space available below the selection into which we can scroll
    835             // downwards
    836             final int spaceBelow = bottomSelectionPixel - sel.getBottom();
    837             final int offset = Math.min(spaceAbove, spaceBelow);
    838 
    839             // Offset the selected item to get it into view
    840             sel.offsetTopAndBottom(offset);
    841         }
    842 
    843         // Fill in views above and below
    844         fillAboveAndBelow(sel, selectedPosition);
    845 
    846         if (!mStackFromBottom) {
    847             correctTooHigh(getChildCount());
    848         } else {
    849             correctTooLow(getChildCount());
    850         }
    851 
    852         return sel;
    853     }
    854 
    855     /**
    856      * Calculate the bottom-most pixel we can draw the selection into
    857      *
    858      * @param childrenBottom Bottom pixel were children can be drawn
    859      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    860      * @param selectedPosition The position that will be selected
    861      * @return The bottom-most pixel we can draw the selection into
    862      */
    863     private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
    864             int selectedPosition) {
    865         int bottomSelectionPixel = childrenBottom;
    866         if (selectedPosition != mItemCount - 1) {
    867             bottomSelectionPixel -= fadingEdgeLength;
    868         }
    869         return bottomSelectionPixel;
    870     }
    871 
    872     /**
    873      * Calculate the top-most pixel we can draw the selection into
    874      *
    875      * @param childrenTop Top pixel were children can be drawn
    876      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    877      * @param selectedPosition The position that will be selected
    878      * @return The top-most pixel we can draw the selection into
    879      */
    880     private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
    881         // first pixel we can draw the selection into
    882         int topSelectionPixel = childrenTop;
    883         if (selectedPosition > 0) {
    884             topSelectionPixel += fadingEdgeLength;
    885         }
    886         return topSelectionPixel;
    887     }
    888 
    889     /**
    890      * Smoothly scroll to the specified adapter position. The view will
    891      * scroll such that the indicated position is displayed.
    892      * @param position Scroll to this adapter position.
    893      */
    894     @android.view.RemotableViewMethod
    895     public void smoothScrollToPosition(int position) {
    896         super.smoothScrollToPosition(position);
    897     }
    898 
    899     /**
    900      * Smoothly scroll to the specified adapter position offset. The view will
    901      * scroll such that the indicated position is displayed.
    902      * @param offset The amount to offset from the adapter position to scroll to.
    903      */
    904     @android.view.RemotableViewMethod
    905     public void smoothScrollByOffset(int offset) {
    906         super.smoothScrollByOffset(offset);
    907     }
    908 
    909     /**
    910      * Fills the list based on positioning the new selection relative to the old
    911      * selection. The new selection will be placed at, above, or below the
    912      * location of the new selection depending on how the selection is moving.
    913      * The selection will then be pinned to the visible part of the screen,
    914      * excluding the edges that are faded. The list is then filled upwards and
    915      * downwards from there.
    916      *
    917      * @param oldSel The old selected view. Useful for trying to put the new
    918      *        selection in the same place
    919      * @param newSel The view that is to become selected. Useful for trying to
    920      *        put the new selection in the same place
    921      * @param delta Which way we are moving
    922      * @param childrenTop Where to start drawing children
    923      * @param childrenBottom Last pixel where children can be drawn
    924      * @return The view that currently has selection
    925      */
    926     private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
    927             int childrenBottom) {
    928         int fadingEdgeLength = getVerticalFadingEdgeLength();
    929         final int selectedPosition = mSelectedPosition;
    930 
    931         View sel;
    932 
    933         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    934                 selectedPosition);
    935         final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
    936                 selectedPosition);
    937 
    938         if (delta > 0) {
    939             /*
    940              * Case 1: Scrolling down.
    941              */
    942 
    943             /*
    944              *     Before           After
    945              *    |       |        |       |
    946              *    +-------+        +-------+
    947              *    |   A   |        |   A   |
    948              *    |   1   |   =>   +-------+
    949              *    +-------+        |   B   |
    950              *    |   B   |        |   2   |
    951              *    +-------+        +-------+
    952              *    |       |        |       |
    953              *
    954              *    Try to keep the top of the previously selected item where it was.
    955              *    oldSel = A
    956              *    sel = B
    957              */
    958 
    959             // Put oldSel (A) where it belongs
    960             oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
    961                     mListPadding.left, false);
    962 
    963             final int dividerHeight = mDividerHeight;
    964 
    965             // Now put the new selection (B) below that
    966             sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
    967                     mListPadding.left, true);
    968 
    969             // Some of the newly selected item extends below the bottom of the list
    970             if (sel.getBottom() > bottomSelectionPixel) {
    971 
    972                 // Find space available above the selection into which we can scroll upwards
    973                 int spaceAbove = sel.getTop() - topSelectionPixel;
    974 
    975                 // Find space required to bring the bottom of the selected item fully into view
    976                 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    977 
    978                 // Don't scroll more than half the height of the list
    979                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
    980                 int offset = Math.min(spaceAbove, spaceBelow);
    981                 offset = Math.min(offset, halfVerticalSpace);
    982 
    983                 // We placed oldSel, so offset that item
    984                 oldSel.offsetTopAndBottom(-offset);
    985                 // Now offset the selected item to get it into view
    986                 sel.offsetTopAndBottom(-offset);
    987             }
    988 
    989             // Fill in views above and below
    990             if (!mStackFromBottom) {
    991                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
    992                 adjustViewsUpOrDown();
    993                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
    994             } else {
    995                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
    996                 adjustViewsUpOrDown();
    997                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
    998             }
    999         } else if (delta < 0) {
   1000             /*
   1001              * Case 2: Scrolling up.
   1002              */
   1003 
   1004             /*
   1005              *     Before           After
   1006              *    |       |        |       |
   1007              *    +-------+        +-------+
   1008              *    |   A   |        |   A   |
   1009              *    +-------+   =>   |   1   |
   1010              *    |   B   |        +-------+
   1011              *    |   2   |        |   B   |
   1012              *    +-------+        +-------+
   1013              *    |       |        |       |
   1014              *
   1015              *    Try to keep the top of the item about to become selected where it was.
   1016              *    newSel = A
   1017              *    olSel = B
   1018              */
   1019 
   1020             if (newSel != null) {
   1021                 // Try to position the top of newSel (A) where it was before it was selected
   1022                 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
   1023                         true);
   1024             } else {
   1025                 // If (A) was not on screen and so did not have a view, position
   1026                 // it above the oldSel (B)
   1027                 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
   1028                         true);
   1029             }
   1030 
   1031             // Some of the newly selected item extends above the top of the list
   1032             if (sel.getTop() < topSelectionPixel) {
   1033                 // Find space required to bring the top of the selected item fully into view
   1034                 int spaceAbove = topSelectionPixel - sel.getTop();
   1035 
   1036                // Find space available below the selection into which we can scroll downwards
   1037                 int spaceBelow = bottomSelectionPixel - sel.getBottom();
   1038 
   1039                 // Don't scroll more than half the height of the list
   1040                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
   1041                 int offset = Math.min(spaceAbove, spaceBelow);
   1042                 offset = Math.min(offset, halfVerticalSpace);
   1043 
   1044                 // Offset the selected item to get it into view
   1045                 sel.offsetTopAndBottom(offset);
   1046             }
   1047 
   1048             // Fill in views above and below
   1049             fillAboveAndBelow(sel, selectedPosition);
   1050         } else {
   1051 
   1052             int oldTop = oldSel.getTop();
   1053 
   1054             /*
   1055              * Case 3: Staying still
   1056              */
   1057             sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
   1058 
   1059             // We're staying still...
   1060             if (oldTop < childrenTop) {
   1061                 // ... but the top of the old selection was off screen.
   1062                 // (This can happen if the data changes size out from under us)
   1063                 int newBottom = sel.getBottom();
   1064                 if (newBottom < childrenTop + 20) {
   1065                     // Not enough visible -- bring it onscreen
   1066                     sel.offsetTopAndBottom(childrenTop - sel.getTop());
   1067                 }
   1068             }
   1069 
   1070             // Fill in views above and below
   1071             fillAboveAndBelow(sel, selectedPosition);
   1072         }
   1073 
   1074         return sel;
   1075     }
   1076 
   1077     private class FocusSelector implements Runnable {
   1078         private int mPosition;
   1079         private int mPositionTop;
   1080 
   1081         public FocusSelector setup(int position, int top) {
   1082             mPosition = position;
   1083             mPositionTop = top;
   1084             return this;
   1085         }
   1086 
   1087         public void run() {
   1088             setSelectionFromTop(mPosition, mPositionTop);
   1089         }
   1090     }
   1091 
   1092     @Override
   1093     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1094         if (getChildCount() > 0) {
   1095             View focusedChild = getFocusedChild();
   1096             if (focusedChild != null) {
   1097                 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
   1098                 final int childBottom = focusedChild.getBottom();
   1099                 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
   1100                 final int top = focusedChild.getTop() - offset;
   1101                 if (mFocusSelector == null) {
   1102                     mFocusSelector = new FocusSelector();
   1103                 }
   1104                 post(mFocusSelector.setup(childPosition, top));
   1105             }
   1106         }
   1107         super.onSizeChanged(w, h, oldw, oldh);
   1108     }
   1109 
   1110     @Override
   1111     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1112         // Sets up mListPadding
   1113         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1114 
   1115         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   1116         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   1117         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   1118         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   1119 
   1120         int childWidth = 0;
   1121         int childHeight = 0;
   1122         int childState = 0;
   1123 
   1124         mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
   1125         if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
   1126                 heightMode == MeasureSpec.UNSPECIFIED)) {
   1127             final View child = obtainView(0, mIsScrap);
   1128 
   1129             measureScrapChild(child, 0, widthMeasureSpec);
   1130 
   1131             childWidth = child.getMeasuredWidth();
   1132             childHeight = child.getMeasuredHeight();
   1133             childState = combineMeasuredStates(childState, child.getMeasuredState());
   1134 
   1135             if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
   1136                     ((LayoutParams) child.getLayoutParams()).viewType)) {
   1137                 mRecycler.addScrapView(child, -1);
   1138             }
   1139         }
   1140 
   1141         if (widthMode == MeasureSpec.UNSPECIFIED) {
   1142             widthSize = mListPadding.left + mListPadding.right + childWidth +
   1143                     getVerticalScrollbarWidth();
   1144         } else {
   1145             widthSize |= (childState&MEASURED_STATE_MASK);
   1146         }
   1147 
   1148         if (heightMode == MeasureSpec.UNSPECIFIED) {
   1149             heightSize = mListPadding.top + mListPadding.bottom + childHeight +
   1150                     getVerticalFadingEdgeLength() * 2;
   1151         }
   1152 
   1153         if (heightMode == MeasureSpec.AT_MOST) {
   1154             // TODO: after first layout we should maybe start at the first visible position, not 0
   1155             heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
   1156         }
   1157 
   1158         setMeasuredDimension(widthSize , heightSize);
   1159         mWidthMeasureSpec = widthMeasureSpec;
   1160     }
   1161 
   1162     private void measureScrapChild(View child, int position, int widthMeasureSpec) {
   1163         LayoutParams p = (LayoutParams) child.getLayoutParams();
   1164         if (p == null) {
   1165             p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   1166                     ViewGroup.LayoutParams.WRAP_CONTENT, 0);
   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                     if (ViewDebug.TRACE_RECYCLER) {
   1561                         ViewDebug.trace(getChildAt(i),
   1562                                 ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
   1563                     }
   1564                 }
   1565             } else {
   1566                 recycleBin.fillActiveViews(childCount, firstPosition);
   1567             }
   1568 
   1569             // take focus back to us temporarily to avoid the eventual
   1570             // call to clear focus when removing the focused child below
   1571             // from messing things up when ViewAncestor assigns focus back
   1572             // to someone else
   1573             final View focusedChild = getFocusedChild();
   1574             if (focusedChild != null) {
   1575                 // TODO: in some cases focusedChild.getParent() == null
   1576 
   1577                 // we can remember the focused view to restore after relayout if the
   1578                 // data hasn't changed, or if the focused position is a header or footer
   1579                 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
   1580                     focusLayoutRestoreDirectChild = focusedChild;
   1581                     // remember the specific view that had focus
   1582                     focusLayoutRestoreView = findFocus();
   1583                     if (focusLayoutRestoreView != null) {
   1584                         // tell it we are going to mess with it
   1585                         focusLayoutRestoreView.onStartTemporaryDetach();
   1586                     }
   1587                 }
   1588                 requestFocus();
   1589             }
   1590 
   1591             // Clear out old views
   1592             detachAllViewsFromParent();
   1593 
   1594             switch (mLayoutMode) {
   1595             case LAYOUT_SET_SELECTION:
   1596                 if (newSel != null) {
   1597                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
   1598                 } else {
   1599                     sel = fillFromMiddle(childrenTop, childrenBottom);
   1600                 }
   1601                 break;
   1602             case LAYOUT_SYNC:
   1603                 sel = fillSpecific(mSyncPosition, mSpecificTop);
   1604                 break;
   1605             case LAYOUT_FORCE_BOTTOM:
   1606                 sel = fillUp(mItemCount - 1, childrenBottom);
   1607                 adjustViewsUpOrDown();
   1608                 break;
   1609             case LAYOUT_FORCE_TOP:
   1610                 mFirstPosition = 0;
   1611                 sel = fillFromTop(childrenTop);
   1612                 adjustViewsUpOrDown();
   1613                 break;
   1614             case LAYOUT_SPECIFIC:
   1615                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
   1616                 break;
   1617             case LAYOUT_MOVE_SELECTION:
   1618                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
   1619                 break;
   1620             default:
   1621                 if (childCount == 0) {
   1622                     if (!mStackFromBottom) {
   1623                         final int position = lookForSelectablePosition(0, true);
   1624                         setSelectedPositionInt(position);
   1625                         sel = fillFromTop(childrenTop);
   1626                     } else {
   1627                         final int position = lookForSelectablePosition(mItemCount - 1, false);
   1628                         setSelectedPositionInt(position);
   1629                         sel = fillUp(mItemCount - 1, childrenBottom);
   1630                     }
   1631                 } else {
   1632                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
   1633                         sel = fillSpecific(mSelectedPosition,
   1634                                 oldSel == null ? childrenTop : oldSel.getTop());
   1635                     } else if (mFirstPosition < mItemCount) {
   1636                         sel = fillSpecific(mFirstPosition,
   1637                                 oldFirst == null ? childrenTop : oldFirst.getTop());
   1638                     } else {
   1639                         sel = fillSpecific(0, childrenTop);
   1640                     }
   1641                 }
   1642                 break;
   1643             }
   1644 
   1645             // Flush any cached views that did not get reused above
   1646             recycleBin.scrapActiveViews();
   1647 
   1648             if (sel != null) {
   1649                 // the current selected item should get focus if items
   1650                 // are focusable
   1651                 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
   1652                     final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
   1653                             focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
   1654                     if (!focusWasTaken) {
   1655                         // selected item didn't take focus, fine, but still want
   1656                         // to make sure something else outside of the selected view
   1657                         // has focus
   1658                         final View focused = getFocusedChild();
   1659                         if (focused != null) {
   1660                             focused.clearFocus();
   1661                         }
   1662                         positionSelector(INVALID_POSITION, sel);
   1663                     } else {
   1664                         sel.setSelected(false);
   1665                         mSelectorRect.setEmpty();
   1666                     }
   1667                 } else {
   1668                     positionSelector(INVALID_POSITION, sel);
   1669                 }
   1670                 mSelectedTop = sel.getTop();
   1671             } else {
   1672                 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
   1673                     View child = getChildAt(mMotionPosition - mFirstPosition);
   1674                     if (child != null) positionSelector(mMotionPosition, child);
   1675                 } else {
   1676                     mSelectedTop = 0;
   1677                     mSelectorRect.setEmpty();
   1678                 }
   1679 
   1680                 // even if there is not selected position, we may need to restore
   1681                 // focus (i.e. something focusable in touch mode)
   1682                 if (hasFocus() && focusLayoutRestoreView != null) {
   1683                     focusLayoutRestoreView.requestFocus();
   1684                 }
   1685             }
   1686 
   1687             // tell focus view we are done mucking with it, if it is still in
   1688             // our view hierarchy.
   1689             if (focusLayoutRestoreView != null
   1690                     && focusLayoutRestoreView.getWindowToken() != null) {
   1691                 focusLayoutRestoreView.onFinishTemporaryDetach();
   1692             }
   1693 
   1694             mLayoutMode = LAYOUT_NORMAL;
   1695             mDataChanged = false;
   1696             mNeedSync = false;
   1697             setNextSelectedPositionInt(mSelectedPosition);
   1698 
   1699             updateScrollIndicators();
   1700 
   1701             if (mItemCount > 0) {
   1702                 checkSelectionChanged();
   1703             }
   1704 
   1705             invokeOnItemScrollListener();
   1706         } finally {
   1707             if (!blockLayoutRequests) {
   1708                 mBlockLayoutRequests = false;
   1709             }
   1710         }
   1711     }
   1712 
   1713     /**
   1714      * @param child a direct child of this list.
   1715      * @return Whether child is a header or footer view.
   1716      */
   1717     private boolean isDirectChildHeaderOrFooter(View child) {
   1718 
   1719         final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
   1720         final int numHeaders = headers.size();
   1721         for (int i = 0; i < numHeaders; i++) {
   1722             if (child == headers.get(i).view) {
   1723                 return true;
   1724             }
   1725         }
   1726         final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
   1727         final int numFooters = footers.size();
   1728         for (int i = 0; i < numFooters; i++) {
   1729             if (child == footers.get(i).view) {
   1730                 return true;
   1731             }
   1732         }
   1733         return false;
   1734     }
   1735 
   1736     /**
   1737      * Obtain the view and add it to our list of children. The view can be made
   1738      * fresh, converted from an unused view, or used as is if it was in the
   1739      * recycle bin.
   1740      *
   1741      * @param position Logical position in the list
   1742      * @param y Top or bottom edge of the view to add
   1743      * @param flow If flow is true, align top edge to y. If false, align bottom
   1744      *        edge to y.
   1745      * @param childrenLeft Left edge where children should be positioned
   1746      * @param selected Is this position selected?
   1747      * @return View that was added
   1748      */
   1749     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
   1750             boolean selected) {
   1751         View child;
   1752 
   1753 
   1754         if (!mDataChanged) {
   1755             // Try to use an existing view for this position
   1756             child = mRecycler.getActiveView(position);
   1757             if (child != null) {
   1758                 if (ViewDebug.TRACE_RECYCLER) {
   1759                     ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
   1760                             position, getChildCount());
   1761                 }
   1762 
   1763                 // Found it -- we're using an existing child
   1764                 // This just needs to be positioned
   1765                 setupChild(child, position, y, flow, childrenLeft, selected, true);
   1766 
   1767                 return child;
   1768             }
   1769         }
   1770 
   1771         // Make a new view for this position, or convert an unused view if possible
   1772         child = obtainView(position, mIsScrap);
   1773 
   1774         // This needs to be positioned and measured
   1775         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
   1776 
   1777         return child;
   1778     }
   1779 
   1780     /**
   1781      * Add a view as a child and make sure it is measured (if necessary) and
   1782      * positioned properly.
   1783      *
   1784      * @param child The view to add
   1785      * @param position The position of this child
   1786      * @param y The y position relative to which this view will be positioned
   1787      * @param flowDown If true, align top edge to y. If false, align bottom
   1788      *        edge to y.
   1789      * @param childrenLeft Left edge where children should be positioned
   1790      * @param selected Is this position selected?
   1791      * @param recycled Has this view been pulled from the recycle bin? If so it
   1792      *        does not need to be remeasured.
   1793      */
   1794     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
   1795             boolean selected, boolean recycled) {
   1796         final boolean isSelected = selected && shouldShowSelector();
   1797         final boolean updateChildSelected = isSelected != child.isSelected();
   1798         final int mode = mTouchMode;
   1799         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
   1800                 mMotionPosition == position;
   1801         final boolean updateChildPressed = isPressed != child.isPressed();
   1802         final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
   1803 
   1804         // Respect layout params that are already in the view. Otherwise make some up...
   1805         // noinspection unchecked
   1806         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
   1807         if (p == null) {
   1808             p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   1809                     ViewGroup.LayoutParams.WRAP_CONTENT, 0);
   1810         }
   1811         p.viewType = mAdapter.getItemViewType(position);
   1812 
   1813         if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
   1814                 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
   1815             attachViewToParent(child, flowDown ? -1 : 0, p);
   1816         } else {
   1817             p.forceAdd = false;
   1818             if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   1819                 p.recycledHeaderFooter = true;
   1820             }
   1821             addViewInLayout(child, flowDown ? -1 : 0, p, true);
   1822         }
   1823 
   1824         if (updateChildSelected) {
   1825             child.setSelected(isSelected);
   1826         }
   1827 
   1828         if (updateChildPressed) {
   1829             child.setPressed(isPressed);
   1830         }
   1831 
   1832         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
   1833             if (child instanceof Checkable) {
   1834                 ((Checkable) child).setChecked(mCheckStates.get(position));
   1835             } else if (getContext().getApplicationInfo().targetSdkVersion
   1836                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
   1837                 child.setActivated(mCheckStates.get(position));
   1838             }
   1839         }
   1840 
   1841         if (needToMeasure) {
   1842             int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   1843                     mListPadding.left + mListPadding.right, p.width);
   1844             int lpHeight = p.height;
   1845             int childHeightSpec;
   1846             if (lpHeight > 0) {
   1847                 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   1848             } else {
   1849                 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1850             }
   1851             child.measure(childWidthSpec, childHeightSpec);
   1852         } else {
   1853             cleanupLayoutState(child);
   1854         }
   1855 
   1856         final int w = child.getMeasuredWidth();
   1857         final int h = child.getMeasuredHeight();
   1858         final int childTop = flowDown ? y : y - h;
   1859 
   1860         if (needToMeasure) {
   1861             final int childRight = childrenLeft + w;
   1862             final int childBottom = childTop + h;
   1863             child.layout(childrenLeft, childTop, childRight, childBottom);
   1864         } else {
   1865             child.offsetLeftAndRight(childrenLeft - child.getLeft());
   1866             child.offsetTopAndBottom(childTop - child.getTop());
   1867         }
   1868 
   1869         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
   1870             child.setDrawingCacheEnabled(true);
   1871         }
   1872 
   1873         if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
   1874                 != position) {
   1875             child.jumpDrawablesToCurrentState();
   1876         }
   1877     }
   1878 
   1879     @Override
   1880     protected boolean canAnimate() {
   1881         return super.canAnimate() && mItemCount > 0;
   1882     }
   1883 
   1884     /**
   1885      * Sets the currently selected item. If in touch mode, the item will not be selected
   1886      * but it will still be positioned appropriately. If the specified selection position
   1887      * is less than 0, then the item at position 0 will be selected.
   1888      *
   1889      * @param position Index (starting at 0) of the data item to be selected.
   1890      */
   1891     @Override
   1892     public void setSelection(int position) {
   1893         setSelectionFromTop(position, 0);
   1894     }
   1895 
   1896     /**
   1897      * Sets the selected item and positions the selection y pixels from the top edge
   1898      * of the ListView. (If in touch mode, the item will not be selected but it will
   1899      * still be positioned appropriately.)
   1900      *
   1901      * @param position Index (starting at 0) of the data item to be selected.
   1902      * @param y The distance from the top edge of the ListView (plus padding) that the
   1903      *        item will be positioned.
   1904      */
   1905     public void setSelectionFromTop(int position, int y) {
   1906         if (mAdapter == null) {
   1907             return;
   1908         }
   1909 
   1910         if (!isInTouchMode()) {
   1911             position = lookForSelectablePosition(position, true);
   1912             if (position >= 0) {
   1913                 setNextSelectedPositionInt(position);
   1914             }
   1915         } else {
   1916             mResurrectToPosition = position;
   1917         }
   1918 
   1919         if (position >= 0) {
   1920             mLayoutMode = LAYOUT_SPECIFIC;
   1921             mSpecificTop = mListPadding.top + y;
   1922 
   1923             if (mNeedSync) {
   1924                 mSyncPosition = position;
   1925                 mSyncRowId = mAdapter.getItemId(position);
   1926             }
   1927 
   1928             requestLayout();
   1929         }
   1930     }
   1931 
   1932     /**
   1933      * Makes the item at the supplied position selected.
   1934      *
   1935      * @param position the position of the item to select
   1936      */
   1937     @Override
   1938     void setSelectionInt(int position) {
   1939         setNextSelectedPositionInt(position);
   1940         boolean awakeScrollbars = false;
   1941 
   1942         final int selectedPosition = mSelectedPosition;
   1943 
   1944         if (selectedPosition >= 0) {
   1945             if (position == selectedPosition - 1) {
   1946                 awakeScrollbars = true;
   1947             } else if (position == selectedPosition + 1) {
   1948                 awakeScrollbars = true;
   1949             }
   1950         }
   1951 
   1952         layoutChildren();
   1953 
   1954         if (awakeScrollbars) {
   1955             awakenScrollBars();
   1956         }
   1957     }
   1958 
   1959     /**
   1960      * Find a position that can be selected (i.e., is not a separator).
   1961      *
   1962      * @param position The starting position to look at.
   1963      * @param lookDown Whether to look down for other positions.
   1964      * @return The next selectable position starting at position and then searching either up or
   1965      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   1966      */
   1967     @Override
   1968     int lookForSelectablePosition(int position, boolean lookDown) {
   1969         final ListAdapter adapter = mAdapter;
   1970         if (adapter == null || isInTouchMode()) {
   1971             return INVALID_POSITION;
   1972         }
   1973 
   1974         final int count = adapter.getCount();
   1975         if (!mAreAllItemsSelectable) {
   1976             if (lookDown) {
   1977                 position = Math.max(0, position);
   1978                 while (position < count && !adapter.isEnabled(position)) {
   1979                     position++;
   1980                 }
   1981             } else {
   1982                 position = Math.min(position, count - 1);
   1983                 while (position >= 0 && !adapter.isEnabled(position)) {
   1984                     position--;
   1985                 }
   1986             }
   1987 
   1988             if (position < 0 || position >= count) {
   1989                 return INVALID_POSITION;
   1990             }
   1991             return position;
   1992         } else {
   1993             if (position < 0 || position >= count) {
   1994                 return INVALID_POSITION;
   1995             }
   1996             return position;
   1997         }
   1998     }
   1999 
   2000     /**
   2001      * setSelectionAfterHeaderView set the selection to be the first list item
   2002      * after the header views.
   2003      */
   2004     public void setSelectionAfterHeaderView() {
   2005         final int count = mHeaderViewInfos.size();
   2006         if (count > 0) {
   2007             mNextSelectedPosition = 0;
   2008             return;
   2009         }
   2010 
   2011         if (mAdapter != null) {
   2012             setSelection(count);
   2013         } else {
   2014             mNextSelectedPosition = count;
   2015             mLayoutMode = LAYOUT_SET_SELECTION;
   2016         }
   2017 
   2018     }
   2019 
   2020     @Override
   2021     public boolean dispatchKeyEvent(KeyEvent event) {
   2022         // Dispatch in the normal way
   2023         boolean handled = super.dispatchKeyEvent(event);
   2024         if (!handled) {
   2025             // If we didn't handle it...
   2026             View focused = getFocusedChild();
   2027             if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
   2028                 // ... and our focused child didn't handle it
   2029                 // ... give it to ourselves so we can scroll if necessary
   2030                 handled = onKeyDown(event.getKeyCode(), event);
   2031             }
   2032         }
   2033         return handled;
   2034     }
   2035 
   2036     @Override
   2037     public boolean onKeyDown(int keyCode, KeyEvent event) {
   2038         return commonKey(keyCode, 1, event);
   2039     }
   2040 
   2041     @Override
   2042     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   2043         return commonKey(keyCode, repeatCount, event);
   2044     }
   2045 
   2046     @Override
   2047     public boolean onKeyUp(int keyCode, KeyEvent event) {
   2048         return commonKey(keyCode, 1, event);
   2049     }
   2050 
   2051     private boolean commonKey(int keyCode, int count, KeyEvent event) {
   2052         if (mAdapter == null || !mIsAttached) {
   2053             return false;
   2054         }
   2055 
   2056         if (mDataChanged) {
   2057             layoutChildren();
   2058         }
   2059 
   2060         boolean handled = false;
   2061         int action = event.getAction();
   2062 
   2063         if (action != KeyEvent.ACTION_UP) {
   2064             switch (keyCode) {
   2065             case KeyEvent.KEYCODE_DPAD_UP:
   2066                 if (event.hasNoModifiers()) {
   2067                     handled = resurrectSelectionIfNeeded();
   2068                     if (!handled) {
   2069                         while (count-- > 0) {
   2070                             if (arrowScroll(FOCUS_UP)) {
   2071                                 handled = true;
   2072                             } else {
   2073                                 break;
   2074                             }
   2075                         }
   2076                     }
   2077                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2078                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2079                 }
   2080                 break;
   2081 
   2082             case KeyEvent.KEYCODE_DPAD_DOWN:
   2083                 if (event.hasNoModifiers()) {
   2084                     handled = resurrectSelectionIfNeeded();
   2085                     if (!handled) {
   2086                         while (count-- > 0) {
   2087                             if (arrowScroll(FOCUS_DOWN)) {
   2088                                 handled = true;
   2089                             } else {
   2090                                 break;
   2091                             }
   2092                         }
   2093                     }
   2094                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2095                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2096                 }
   2097                 break;
   2098 
   2099             case KeyEvent.KEYCODE_DPAD_LEFT:
   2100                 if (event.hasNoModifiers()) {
   2101                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
   2102                 }
   2103                 break;
   2104 
   2105             case KeyEvent.KEYCODE_DPAD_RIGHT:
   2106                 if (event.hasNoModifiers()) {
   2107                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
   2108                 }
   2109                 break;
   2110 
   2111             case KeyEvent.KEYCODE_DPAD_CENTER:
   2112             case KeyEvent.KEYCODE_ENTER:
   2113                 if (event.hasNoModifiers()) {
   2114                     handled = resurrectSelectionIfNeeded();
   2115                     if (!handled
   2116                             && event.getRepeatCount() == 0 && getChildCount() > 0) {
   2117                         keyPressed();
   2118                         handled = true;
   2119                     }
   2120                 }
   2121                 break;
   2122 
   2123             case KeyEvent.KEYCODE_SPACE:
   2124                 if (mPopup == null || !mPopup.isShowing()) {
   2125                     if (event.hasNoModifiers()) {
   2126                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2127                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2128                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2129                     }
   2130                     handled = true;
   2131                 }
   2132                 break;
   2133 
   2134             case KeyEvent.KEYCODE_PAGE_UP:
   2135                 if (event.hasNoModifiers()) {
   2136                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2137                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2138                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2139                 }
   2140                 break;
   2141 
   2142             case KeyEvent.KEYCODE_PAGE_DOWN:
   2143                 if (event.hasNoModifiers()) {
   2144                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2145                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2146                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2147                 }
   2148                 break;
   2149 
   2150             case KeyEvent.KEYCODE_MOVE_HOME:
   2151                 if (event.hasNoModifiers()) {
   2152                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2153                 }
   2154                 break;
   2155 
   2156             case KeyEvent.KEYCODE_MOVE_END:
   2157                 if (event.hasNoModifiers()) {
   2158                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2159                 }
   2160                 break;
   2161 
   2162             case KeyEvent.KEYCODE_TAB:
   2163                 // XXX Sometimes it is useful to be able to TAB through the items in
   2164                 //     a ListView sequentially.  Unfortunately this can create an
   2165                 //     asymmetry in TAB navigation order unless the list selection
   2166                 //     always reverts to the top or bottom when receiving TAB focus from
   2167                 //     another widget.  Leaving this behavior disabled for now but
   2168                 //     perhaps it should be configurable (and more comprehensive).
   2169                 if (false) {
   2170                     if (event.hasNoModifiers()) {
   2171                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
   2172                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2173                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
   2174                     }
   2175                 }
   2176                 break;
   2177             }
   2178         }
   2179 
   2180         if (handled) {
   2181             return true;
   2182         }
   2183 
   2184         if (sendToTextFilter(keyCode, count, event)) {
   2185             return true;
   2186         }
   2187 
   2188         switch (action) {
   2189             case KeyEvent.ACTION_DOWN:
   2190                 return super.onKeyDown(keyCode, event);
   2191 
   2192             case KeyEvent.ACTION_UP:
   2193                 return super.onKeyUp(keyCode, event);
   2194 
   2195             case KeyEvent.ACTION_MULTIPLE:
   2196                 return super.onKeyMultiple(keyCode, count, event);
   2197 
   2198             default: // shouldn't happen
   2199                 return false;
   2200         }
   2201     }
   2202 
   2203     /**
   2204      * Scrolls up or down by the number of items currently present on screen.
   2205      *
   2206      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2207      * @return whether selection was moved
   2208      */
   2209     boolean pageScroll(int direction) {
   2210         int nextPage = -1;
   2211         boolean down = false;
   2212 
   2213         if (direction == FOCUS_UP) {
   2214             nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
   2215         } else if (direction == FOCUS_DOWN) {
   2216             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
   2217             down = true;
   2218         }
   2219 
   2220         if (nextPage >= 0) {
   2221             int position = lookForSelectablePosition(nextPage, down);
   2222             if (position >= 0) {
   2223                 mLayoutMode = LAYOUT_SPECIFIC;
   2224                 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
   2225 
   2226                 if (down && position > mItemCount - getChildCount()) {
   2227                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2228                 }
   2229 
   2230                 if (!down && position < getChildCount()) {
   2231                     mLayoutMode = LAYOUT_FORCE_TOP;
   2232                 }
   2233 
   2234                 setSelectionInt(position);
   2235                 invokeOnItemScrollListener();
   2236                 if (!awakenScrollBars()) {
   2237                     invalidate();
   2238                 }
   2239 
   2240                 return true;
   2241             }
   2242         }
   2243 
   2244         return false;
   2245     }
   2246 
   2247     /**
   2248      * Go to the last or first item if possible (not worrying about panning across or navigating
   2249      * within the internal focus of the currently selected item.)
   2250      *
   2251      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2252      *
   2253      * @return whether selection was moved
   2254      */
   2255     boolean fullScroll(int direction) {
   2256         boolean moved = false;
   2257         if (direction == FOCUS_UP) {
   2258             if (mSelectedPosition != 0) {
   2259                 int position = lookForSelectablePosition(0, true);
   2260                 if (position >= 0) {
   2261                     mLayoutMode = LAYOUT_FORCE_TOP;
   2262                     setSelectionInt(position);
   2263                     invokeOnItemScrollListener();
   2264                 }
   2265                 moved = true;
   2266             }
   2267         } else if (direction == FOCUS_DOWN) {
   2268             if (mSelectedPosition < mItemCount - 1) {
   2269                 int position = lookForSelectablePosition(mItemCount - 1, true);
   2270                 if (position >= 0) {
   2271                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2272                     setSelectionInt(position);
   2273                     invokeOnItemScrollListener();
   2274                 }
   2275                 moved = true;
   2276             }
   2277         }
   2278 
   2279         if (moved && !awakenScrollBars()) {
   2280             awakenScrollBars();
   2281             invalidate();
   2282         }
   2283 
   2284         return moved;
   2285     }
   2286 
   2287     /**
   2288      * To avoid horizontal focus searches changing the selected item, we
   2289      * manually focus search within the selected item (as applicable), and
   2290      * prevent focus from jumping to something within another item.
   2291      * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
   2292      * @return Whether this consumes the key event.
   2293      */
   2294     private boolean handleHorizontalFocusWithinListItem(int direction) {
   2295         if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
   2296             throw new IllegalArgumentException("direction must be one of"
   2297                     + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
   2298         }
   2299 
   2300         final int numChildren = getChildCount();
   2301         if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
   2302             final View selectedView = getSelectedView();
   2303             if (selectedView != null && selectedView.hasFocus() &&
   2304                     selectedView instanceof ViewGroup) {
   2305 
   2306                 final View currentFocus = selectedView.findFocus();
   2307                 final View nextFocus = FocusFinder.getInstance().findNextFocus(
   2308                         (ViewGroup) selectedView, currentFocus, direction);
   2309                 if (nextFocus != null) {
   2310                     // do the math to get interesting rect in next focus' coordinates
   2311                     currentFocus.getFocusedRect(mTempRect);
   2312                     offsetDescendantRectToMyCoords(currentFocus, mTempRect);
   2313                     offsetRectIntoDescendantCoords(nextFocus, mTempRect);
   2314                     if (nextFocus.requestFocus(direction, mTempRect)) {
   2315                         return true;
   2316                     }
   2317                 }
   2318                 // we are blocking the key from being handled (by returning true)
   2319                 // if the global result is going to be some other view within this
   2320                 // list.  this is to acheive the overall goal of having
   2321                 // horizontal d-pad navigation remain in the current item.
   2322                 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
   2323                         (ViewGroup) getRootView(), currentFocus, direction);
   2324                 if (globalNextFocus != null) {
   2325                     return isViewAncestorOf(globalNextFocus, this);
   2326                 }
   2327             }
   2328         }
   2329         return false;
   2330     }
   2331 
   2332     /**
   2333      * Scrolls to the next or previous item if possible.
   2334      *
   2335      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2336      *
   2337      * @return whether selection was moved
   2338      */
   2339     boolean arrowScroll(int direction) {
   2340         try {
   2341             mInLayout = true;
   2342             final boolean handled = arrowScrollImpl(direction);
   2343             if (handled) {
   2344                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
   2345             }
   2346             return handled;
   2347         } finally {
   2348             mInLayout = false;
   2349         }
   2350     }
   2351 
   2352     /**
   2353      * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
   2354      * whether there are focusable items etc.
   2355      *
   2356      * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
   2357      * @return Whether any scrolling, selection or focus change occured.
   2358      */
   2359     private boolean arrowScrollImpl(int direction) {
   2360         if (getChildCount() <= 0) {
   2361             return false;
   2362         }
   2363 
   2364         View selectedView = getSelectedView();
   2365         int selectedPos = mSelectedPosition;
   2366 
   2367         int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
   2368         int amountToScroll = amountToScroll(direction, nextSelectedPosition);
   2369 
   2370         // if we are moving focus, we may OVERRIDE the default behavior
   2371         final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
   2372         if (focusResult != null) {
   2373             nextSelectedPosition = focusResult.getSelectedPosition();
   2374             amountToScroll = focusResult.getAmountToScroll();
   2375         }
   2376 
   2377         boolean needToRedraw = focusResult != null;
   2378         if (nextSelectedPosition != INVALID_POSITION) {
   2379             handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
   2380             setSelectedPositionInt(nextSelectedPosition);
   2381             setNextSelectedPositionInt(nextSelectedPosition);
   2382             selectedView = getSelectedView();
   2383             selectedPos = nextSelectedPosition;
   2384             if (mItemsCanFocus && focusResult == null) {
   2385                 // there was no new view found to take focus, make sure we
   2386                 // don't leave focus with the old selection
   2387                 final View focused = getFocusedChild();
   2388                 if (focused != null) {
   2389                     focused.clearFocus();
   2390                 }
   2391             }
   2392             needToRedraw = true;
   2393             checkSelectionChanged();
   2394         }
   2395 
   2396         if (amountToScroll > 0) {
   2397             scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
   2398             needToRedraw = true;
   2399         }
   2400 
   2401         // if we didn't find a new focusable, make sure any existing focused
   2402         // item that was panned off screen gives up focus.
   2403         if (mItemsCanFocus && (focusResult == null)
   2404                 && selectedView != null && selectedView.hasFocus()) {
   2405             final View focused = selectedView.findFocus();
   2406             if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
   2407                 focused.clearFocus();
   2408             }
   2409         }
   2410 
   2411         // if  the current selection is panned off, we need to remove the selection
   2412         if (nextSelectedPosition == INVALID_POSITION && selectedView != null
   2413                 && !isViewAncestorOf(selectedView, this)) {
   2414             selectedView = null;
   2415             hideSelector();
   2416 
   2417             // but we don't want to set the ressurect position (that would make subsequent
   2418             // unhandled key events bring back the item we just scrolled off!)
   2419             mResurrectToPosition = INVALID_POSITION;
   2420         }
   2421 
   2422         if (needToRedraw) {
   2423             if (selectedView != null) {
   2424                 positionSelector(selectedPos, selectedView);
   2425                 mSelectedTop = selectedView.getTop();
   2426             }
   2427             if (!awakenScrollBars()) {
   2428                 invalidate();
   2429             }
   2430             invokeOnItemScrollListener();
   2431             return true;
   2432         }
   2433 
   2434         return false;
   2435     }
   2436 
   2437     /**
   2438      * When selection changes, it is possible that the previously selected or the
   2439      * next selected item will change its size.  If so, we need to offset some folks,
   2440      * and re-layout the items as appropriate.
   2441      *
   2442      * @param selectedView The currently selected view (before changing selection).
   2443      *   should be <code>null</code> if there was no previous selection.
   2444      * @param direction Either {@link android.view.View#FOCUS_UP} or
   2445      *        {@link android.view.View#FOCUS_DOWN}.
   2446      * @param newSelectedPosition The position of the next selection.
   2447      * @param newFocusAssigned whether new focus was assigned.  This matters because
   2448      *        when something has focus, we don't want to show selection (ugh).
   2449      */
   2450     private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
   2451             boolean newFocusAssigned) {
   2452         if (newSelectedPosition == INVALID_POSITION) {
   2453             throw new IllegalArgumentException("newSelectedPosition needs to be valid");
   2454         }
   2455 
   2456         // whether or not we are moving down or up, we want to preserve the
   2457         // top of whatever view is on top:
   2458         // - moving down: the view that had selection
   2459         // - moving up: the view that is getting selection
   2460         View topView;
   2461         View bottomView;
   2462         int topViewIndex, bottomViewIndex;
   2463         boolean topSelected = false;
   2464         final int selectedIndex = mSelectedPosition - mFirstPosition;
   2465         final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
   2466         if (direction == View.FOCUS_UP) {
   2467             topViewIndex = nextSelectedIndex;
   2468             bottomViewIndex = selectedIndex;
   2469             topView = getChildAt(topViewIndex);
   2470             bottomView = selectedView;
   2471             topSelected = true;
   2472         } else {
   2473             topViewIndex = selectedIndex;
   2474             bottomViewIndex = nextSelectedIndex;
   2475             topView = selectedView;
   2476             bottomView = getChildAt(bottomViewIndex);
   2477         }
   2478 
   2479         final int numChildren = getChildCount();
   2480 
   2481         // start with top view: is it changing size?
   2482         if (topView != null) {
   2483             topView.setSelected(!newFocusAssigned && topSelected);
   2484             measureAndAdjustDown(topView, topViewIndex, numChildren);
   2485         }
   2486 
   2487         // is the bottom view changing size?
   2488         if (bottomView != null) {
   2489             bottomView.setSelected(!newFocusAssigned && !topSelected);
   2490             measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
   2491         }
   2492     }
   2493 
   2494     /**
   2495      * Re-measure a child, and if its height changes, lay it out preserving its
   2496      * top, and adjust the children below it appropriately.
   2497      * @param child The child
   2498      * @param childIndex The view group index of the child.
   2499      * @param numChildren The number of children in the view group.
   2500      */
   2501     private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
   2502         int oldHeight = child.getHeight();
   2503         measureItem(child);
   2504         if (child.getMeasuredHeight() != oldHeight) {
   2505             // lay out the view, preserving its top
   2506             relayoutMeasuredItem(child);
   2507 
   2508             // adjust views below appropriately
   2509             final int heightDelta = child.getMeasuredHeight() - oldHeight;
   2510             for (int i = childIndex + 1; i < numChildren; i++) {
   2511                 getChildAt(i).offsetTopAndBottom(heightDelta);
   2512             }
   2513         }
   2514     }
   2515 
   2516     /**
   2517      * Measure a particular list child.
   2518      * TODO: unify with setUpChild.
   2519      * @param child The child.
   2520      */
   2521     private void measureItem(View child) {
   2522         ViewGroup.LayoutParams p = child.getLayoutParams();
   2523         if (p == null) {
   2524             p = new ViewGroup.LayoutParams(
   2525                     ViewGroup.LayoutParams.MATCH_PARENT,
   2526                     ViewGroup.LayoutParams.WRAP_CONTENT);
   2527         }
   2528 
   2529         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   2530                 mListPadding.left + mListPadding.right, p.width);
   2531         int lpHeight = p.height;
   2532         int childHeightSpec;
   2533         if (lpHeight > 0) {
   2534             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   2535         } else {
   2536             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   2537         }
   2538         child.measure(childWidthSpec, childHeightSpec);
   2539     }
   2540 
   2541     /**
   2542      * Layout a child that has been measured, preserving its top position.
   2543      * TODO: unify with setUpChild.
   2544      * @param child The child.
   2545      */
   2546     private void relayoutMeasuredItem(View child) {
   2547         final int w = child.getMeasuredWidth();
   2548         final int h = child.getMeasuredHeight();
   2549         final int childLeft = mListPadding.left;
   2550         final int childRight = childLeft + w;
   2551         final int childTop = child.getTop();
   2552         final int childBottom = childTop + h;
   2553         child.layout(childLeft, childTop, childRight, childBottom);
   2554     }
   2555 
   2556     /**
   2557      * @return The amount to preview next items when arrow srolling.
   2558      */
   2559     private int getArrowScrollPreviewLength() {
   2560         return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
   2561     }
   2562 
   2563     /**
   2564      * Determine how much we need to scroll in order to get the next selected view
   2565      * visible, with a fading edge showing below as applicable.  The amount is
   2566      * capped at {@link #getMaxScrollAmount()} .
   2567      *
   2568      * @param direction either {@link android.view.View#FOCUS_UP} or
   2569      *        {@link android.view.View#FOCUS_DOWN}.
   2570      * @param nextSelectedPosition The position of the next selection, or
   2571      *        {@link #INVALID_POSITION} if there is no next selectable position
   2572      * @return The amount to scroll. Note: this is always positive!  Direction
   2573      *         needs to be taken into account when actually scrolling.
   2574      */
   2575     private int amountToScroll(int direction, int nextSelectedPosition) {
   2576         final int listBottom = getHeight() - mListPadding.bottom;
   2577         final int listTop = mListPadding.top;
   2578 
   2579         final int numChildren = getChildCount();
   2580 
   2581         if (direction == View.FOCUS_DOWN) {
   2582             int indexToMakeVisible = numChildren - 1;
   2583             if (nextSelectedPosition != INVALID_POSITION) {
   2584                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2585             }
   2586 
   2587             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2588             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2589 
   2590             int goalBottom = listBottom;
   2591             if (positionToMakeVisible < mItemCount - 1) {
   2592                 goalBottom -= getArrowScrollPreviewLength();
   2593             }
   2594 
   2595             if (viewToMakeVisible.getBottom() <= goalBottom) {
   2596                 // item is fully visible.
   2597                 return 0;
   2598             }
   2599 
   2600             if (nextSelectedPosition != INVALID_POSITION
   2601                     && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
   2602                 // item already has enough of it visible, changing selection is good enough
   2603                 return 0;
   2604             }
   2605 
   2606             int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
   2607 
   2608             if ((mFirstPosition + numChildren) == mItemCount) {
   2609                 // last is last in list -> make sure we don't scroll past it
   2610                 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
   2611                 amountToScroll = Math.min(amountToScroll, max);
   2612             }
   2613 
   2614             return Math.min(amountToScroll, getMaxScrollAmount());
   2615         } else {
   2616             int indexToMakeVisible = 0;
   2617             if (nextSelectedPosition != INVALID_POSITION) {
   2618                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2619             }
   2620             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2621             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2622             int goalTop = listTop;
   2623             if (positionToMakeVisible > 0) {
   2624                 goalTop += getArrowScrollPreviewLength();
   2625             }
   2626             if (viewToMakeVisible.getTop() >= goalTop) {
   2627                 // item is fully visible.
   2628                 return 0;
   2629             }
   2630 
   2631             if (nextSelectedPosition != INVALID_POSITION &&
   2632                     (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
   2633                 // item already has enough of it visible, changing selection is good enough
   2634                 return 0;
   2635             }
   2636 
   2637             int amountToScroll = (goalTop - viewToMakeVisible.getTop());
   2638             if (mFirstPosition == 0) {
   2639                 // first is first in list -> make sure we don't scroll past it
   2640                 final int max = listTop - getChildAt(0).getTop();
   2641                 amountToScroll = Math.min(amountToScroll,  max);
   2642             }
   2643             return Math.min(amountToScroll, getMaxScrollAmount());
   2644         }
   2645     }
   2646 
   2647     /**
   2648      * Holds results of focus aware arrow scrolling.
   2649      */
   2650     static private class ArrowScrollFocusResult {
   2651         private int mSelectedPosition;
   2652         private int mAmountToScroll;
   2653 
   2654         /**
   2655          * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
   2656          */
   2657         void populate(int selectedPosition, int amountToScroll) {
   2658             mSelectedPosition = selectedPosition;
   2659             mAmountToScroll = amountToScroll;
   2660         }
   2661 
   2662         public int getSelectedPosition() {
   2663             return mSelectedPosition;
   2664         }
   2665 
   2666         public int getAmountToScroll() {
   2667             return mAmountToScroll;
   2668         }
   2669     }
   2670 
   2671     /**
   2672      * @param direction either {@link android.view.View#FOCUS_UP} or
   2673      *        {@link android.view.View#FOCUS_DOWN}.
   2674      * @return The position of the next selectable position of the views that
   2675      *         are currently visible, taking into account the fact that there might
   2676      *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
   2677      *         selectable view on screen in the given direction.
   2678      */
   2679     private int lookForSelectablePositionOnScreen(int direction) {
   2680         final int firstPosition = mFirstPosition;
   2681         if (direction == View.FOCUS_DOWN) {
   2682             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2683                     mSelectedPosition + 1 :
   2684                     firstPosition;
   2685             if (startPos >= mAdapter.getCount()) {
   2686                 return INVALID_POSITION;
   2687             }
   2688             if (startPos < firstPosition) {
   2689                 startPos = firstPosition;
   2690             }
   2691 
   2692             final int lastVisiblePos = getLastVisiblePosition();
   2693             final ListAdapter adapter = getAdapter();
   2694             for (int pos = startPos; pos <= lastVisiblePos; pos++) {
   2695                 if (adapter.isEnabled(pos)
   2696                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2697                     return pos;
   2698                 }
   2699             }
   2700         } else {
   2701             int last = firstPosition + getChildCount() - 1;
   2702             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2703                     mSelectedPosition - 1 :
   2704                     firstPosition + getChildCount() - 1;
   2705             if (startPos < 0 || startPos >= mAdapter.getCount()) {
   2706                 return INVALID_POSITION;
   2707             }
   2708             if (startPos > last) {
   2709                 startPos = last;
   2710             }
   2711 
   2712             final ListAdapter adapter = getAdapter();
   2713             for (int pos = startPos; pos >= firstPosition; pos--) {
   2714                 if (adapter.isEnabled(pos)
   2715                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2716                     return pos;
   2717                 }
   2718             }
   2719         }
   2720         return INVALID_POSITION;
   2721     }
   2722 
   2723     /**
   2724      * Do an arrow scroll based on focus searching.  If a new view is
   2725      * given focus, return the selection delta and amount to scroll via
   2726      * an {@link ArrowScrollFocusResult}, otherwise, return null.
   2727      *
   2728      * @param direction either {@link android.view.View#FOCUS_UP} or
   2729      *        {@link android.view.View#FOCUS_DOWN}.
   2730      * @return The result if focus has changed, or <code>null</code>.
   2731      */
   2732     private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
   2733         final View selectedView = getSelectedView();
   2734         View newFocus;
   2735         if (selectedView != null && selectedView.hasFocus()) {
   2736             View oldFocus = selectedView.findFocus();
   2737             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
   2738         } else {
   2739             if (direction == View.FOCUS_DOWN) {
   2740                 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
   2741                 final int listTop = mListPadding.top +
   2742                         (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2743                 final int ySearchPoint =
   2744                         (selectedView != null && selectedView.getTop() > listTop) ?
   2745                                 selectedView.getTop() :
   2746                                 listTop;
   2747                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2748             } else {
   2749                 final boolean bottomFadingEdgeShowing =
   2750                         (mFirstPosition + getChildCount() - 1) < mItemCount;
   2751                 final int listBottom = getHeight() - mListPadding.bottom -
   2752                         (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2753                 final int ySearchPoint =
   2754                         (selectedView != null && selectedView.getBottom() < listBottom) ?
   2755                                 selectedView.getBottom() :
   2756                                 listBottom;
   2757                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2758             }
   2759             newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
   2760         }
   2761 
   2762         if (newFocus != null) {
   2763             final int positionOfNewFocus = positionOfNewFocus(newFocus);
   2764 
   2765             // if the focus change is in a different new position, make sure
   2766             // we aren't jumping over another selectable position
   2767             if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
   2768                 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
   2769                 if (selectablePosition != INVALID_POSITION &&
   2770                         ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
   2771                         (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
   2772                     return null;
   2773                 }
   2774             }
   2775 
   2776             int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
   2777 
   2778             final int maxScrollAmount = getMaxScrollAmount();
   2779             if (focusScroll < maxScrollAmount) {
   2780                 // not moving too far, safe to give next view focus
   2781                 newFocus.requestFocus(direction);
   2782                 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
   2783                 return mArrowScrollFocusResult;
   2784             } else if (distanceToView(newFocus) < maxScrollAmount){
   2785                 // Case to consider:
   2786                 // too far to get entire next focusable on screen, but by going
   2787                 // max scroll amount, we are getting it at least partially in view,
   2788                 // so give it focus and scroll the max ammount.
   2789                 newFocus.requestFocus(direction);
   2790                 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
   2791                 return mArrowScrollFocusResult;
   2792             }
   2793         }
   2794         return null;
   2795     }
   2796 
   2797     /**
   2798      * @param newFocus The view that would have focus.
   2799      * @return the position that contains newFocus
   2800      */
   2801     private int positionOfNewFocus(View newFocus) {
   2802         final int numChildren = getChildCount();
   2803         for (int i = 0; i < numChildren; i++) {
   2804             final View child = getChildAt(i);
   2805             if (isViewAncestorOf(newFocus, child)) {
   2806                 return mFirstPosition + i;
   2807             }
   2808         }
   2809         throw new IllegalArgumentException("newFocus is not a child of any of the"
   2810                 + " children of the list!");
   2811     }
   2812 
   2813     /**
   2814      * Return true if child is an ancestor of parent, (or equal to the parent).
   2815      */
   2816     private boolean isViewAncestorOf(View child, View parent) {
   2817         if (child == parent) {
   2818             return true;
   2819         }
   2820 
   2821         final ViewParent theParent = child.getParent();
   2822         return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
   2823     }
   2824 
   2825     /**
   2826      * Determine how much we need to scroll in order to get newFocus in view.
   2827      * @param direction either {@link android.view.View#FOCUS_UP} or
   2828      *        {@link android.view.View#FOCUS_DOWN}.
   2829      * @param newFocus The view that would take focus.
   2830      * @param positionOfNewFocus The position of the list item containing newFocus
   2831      * @return The amount to scroll.  Note: this is always positive!  Direction
   2832      *   needs to be taken into account when actually scrolling.
   2833      */
   2834     private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
   2835         int amountToScroll = 0;
   2836         newFocus.getDrawingRect(mTempRect);
   2837         offsetDescendantRectToMyCoords(newFocus, mTempRect);
   2838         if (direction == View.FOCUS_UP) {
   2839             if (mTempRect.top < mListPadding.top) {
   2840                 amountToScroll = mListPadding.top - mTempRect.top;
   2841                 if (positionOfNewFocus > 0) {
   2842                     amountToScroll += getArrowScrollPreviewLength();
   2843                 }
   2844             }
   2845         } else {
   2846             final int listBottom = getHeight() - mListPadding.bottom;
   2847             if (mTempRect.bottom > listBottom) {
   2848                 amountToScroll = mTempRect.bottom - listBottom;
   2849                 if (positionOfNewFocus < mItemCount - 1) {
   2850                     amountToScroll += getArrowScrollPreviewLength();
   2851                 }
   2852             }
   2853         }
   2854         return amountToScroll;
   2855     }
   2856 
   2857     /**
   2858      * Determine the distance to the nearest edge of a view in a particular
   2859      * direction.
   2860      *
   2861      * @param descendant A descendant of this list.
   2862      * @return The distance, or 0 if the nearest edge is already on screen.
   2863      */
   2864     private int distanceToView(View descendant) {
   2865         int distance = 0;
   2866         descendant.getDrawingRect(mTempRect);
   2867         offsetDescendantRectToMyCoords(descendant, mTempRect);
   2868         final int listBottom = mBottom - mTop - mListPadding.bottom;
   2869         if (mTempRect.bottom < mListPadding.top) {
   2870             distance = mListPadding.top - mTempRect.bottom;
   2871         } else if (mTempRect.top > listBottom) {
   2872             distance = mTempRect.top - listBottom;
   2873         }
   2874         return distance;
   2875     }
   2876 
   2877 
   2878     /**
   2879      * Scroll the children by amount, adding a view at the end and removing
   2880      * views that fall off as necessary.
   2881      *
   2882      * @param amount The amount (positive or negative) to scroll.
   2883      */
   2884     private void scrollListItemsBy(int amount) {
   2885         offsetChildrenTopAndBottom(amount);
   2886 
   2887         final int listBottom = getHeight() - mListPadding.bottom;
   2888         final int listTop = mListPadding.top;
   2889         final AbsListView.RecycleBin recycleBin = mRecycler;
   2890 
   2891         if (amount < 0) {
   2892             // shifted items up
   2893 
   2894             // may need to pan views into the bottom space
   2895             int numChildren = getChildCount();
   2896             View last = getChildAt(numChildren - 1);
   2897             while (last.getBottom() < listBottom) {
   2898                 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
   2899                 if (lastVisiblePosition < mItemCount - 1) {
   2900                     last = addViewBelow(last, lastVisiblePosition);
   2901                     numChildren++;
   2902                 } else {
   2903                     break;
   2904                 }
   2905             }
   2906 
   2907             // may have brought in the last child of the list that is skinnier
   2908             // than the fading edge, thereby leaving space at the end.  need
   2909             // to shift back
   2910             if (last.getBottom() < listBottom) {
   2911                 offsetChildrenTopAndBottom(listBottom - last.getBottom());
   2912             }
   2913 
   2914             // top views may be panned off screen
   2915             View first = getChildAt(0);
   2916             while (first.getBottom() < listTop) {
   2917                 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
   2918                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   2919                     detachViewFromParent(first);
   2920                     recycleBin.addScrapView(first, mFirstPosition);
   2921                 } else {
   2922                     removeViewInLayout(first);
   2923                 }
   2924                 first = getChildAt(0);
   2925                 mFirstPosition++;
   2926             }
   2927         } else {
   2928             // shifted items down
   2929             View first = getChildAt(0);
   2930 
   2931             // may need to pan views into top
   2932             while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
   2933                 first = addViewAbove(first, mFirstPosition);
   2934                 mFirstPosition--;
   2935             }
   2936 
   2937             // may have brought the very first child of the list in too far and
   2938             // need to shift it back
   2939             if (first.getTop() > listTop) {
   2940                 offsetChildrenTopAndBottom(listTop - first.getTop());
   2941             }
   2942 
   2943             int lastIndex = getChildCount() - 1;
   2944             View last = getChildAt(lastIndex);
   2945 
   2946             // bottom view may be panned off screen
   2947             while (last.getTop() > listBottom) {
   2948                 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
   2949                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   2950                     detachViewFromParent(last);
   2951                     recycleBin.addScrapView(last, mFirstPosition+lastIndex);
   2952                 } else {
   2953                     removeViewInLayout(last);
   2954                 }
   2955                 last = getChildAt(--lastIndex);
   2956             }
   2957         }
   2958     }
   2959 
   2960     private View addViewAbove(View theView, int position) {
   2961         int abovePosition = position - 1;
   2962         View view = obtainView(abovePosition, mIsScrap);
   2963         int edgeOfNewChild = theView.getTop() - mDividerHeight;
   2964         setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
   2965                 false, mIsScrap[0]);
   2966         return view;
   2967     }
   2968 
   2969     private View addViewBelow(View theView, int position) {
   2970         int belowPosition = position + 1;
   2971         View view = obtainView(belowPosition, mIsScrap);
   2972         int edgeOfNewChild = theView.getBottom() + mDividerHeight;
   2973         setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
   2974                 false, mIsScrap[0]);
   2975         return view;
   2976     }
   2977 
   2978     /**
   2979      * Indicates that the views created by the ListAdapter can contain focusable
   2980      * items.
   2981      *
   2982      * @param itemsCanFocus true if items can get focus, false otherwise
   2983      */
   2984     public void setItemsCanFocus(boolean itemsCanFocus) {
   2985         mItemsCanFocus = itemsCanFocus;
   2986         if (!itemsCanFocus) {
   2987             setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
   2988         }
   2989     }
   2990 
   2991     /**
   2992      * @return Whether the views created by the ListAdapter can contain focusable
   2993      * items.
   2994      */
   2995     public boolean getItemsCanFocus() {
   2996         return mItemsCanFocus;
   2997     }
   2998 
   2999     @Override
   3000     public boolean isOpaque() {
   3001         boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
   3002                 hasOpaqueScrollbars()) || super.isOpaque();
   3003         if (retValue) {
   3004             // only return true if the list items cover the entire area of the view
   3005             final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
   3006             View first = getChildAt(0);
   3007             if (first == null || first.getTop() > listTop) {
   3008                 return false;
   3009             }
   3010             final int listBottom = getHeight() -
   3011                     (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
   3012             View last = getChildAt(getChildCount() - 1);
   3013             if (last == null || last.getBottom() < listBottom) {
   3014                 return false;
   3015             }
   3016         }
   3017         return retValue;
   3018     }
   3019 
   3020     @Override
   3021     public void setCacheColorHint(int color) {
   3022         final boolean opaque = (color >>> 24) == 0xFF;
   3023         mIsCacheColorOpaque = opaque;
   3024         if (opaque) {
   3025             if (mDividerPaint == null) {
   3026                 mDividerPaint = new Paint();
   3027             }
   3028             mDividerPaint.setColor(color);
   3029         }
   3030         super.setCacheColorHint(color);
   3031     }
   3032 
   3033     void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
   3034         final int height = drawable.getMinimumHeight();
   3035 
   3036         canvas.save();
   3037         canvas.clipRect(bounds);
   3038 
   3039         final int span = bounds.bottom - bounds.top;
   3040         if (span < height) {
   3041             bounds.top = bounds.bottom - height;
   3042         }
   3043 
   3044         drawable.setBounds(bounds);
   3045         drawable.draw(canvas);
   3046 
   3047         canvas.restore();
   3048     }
   3049 
   3050     void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
   3051         final int height = drawable.getMinimumHeight();
   3052 
   3053         canvas.save();
   3054         canvas.clipRect(bounds);
   3055 
   3056         final int span = bounds.bottom - bounds.top;
   3057         if (span < height) {
   3058             bounds.bottom = bounds.top + height;
   3059         }
   3060 
   3061         drawable.setBounds(bounds);
   3062         drawable.draw(canvas);
   3063 
   3064         canvas.restore();
   3065     }
   3066 
   3067     @Override
   3068     protected void dispatchDraw(Canvas canvas) {
   3069         if (mCachingStarted) {
   3070             mCachingActive = true;
   3071         }
   3072 
   3073         // Draw the dividers
   3074         final int dividerHeight = mDividerHeight;
   3075         final Drawable overscrollHeader = mOverScrollHeader;
   3076         final Drawable overscrollFooter = mOverScrollFooter;
   3077         final boolean drawOverscrollHeader = overscrollHeader != null;
   3078         final boolean drawOverscrollFooter = overscrollFooter != null;
   3079         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
   3080 
   3081         if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
   3082             // Only modify the top and bottom in the loop, we set the left and right here
   3083             final Rect bounds = mTempRect;
   3084             bounds.left = mPaddingLeft;
   3085             bounds.right = mRight - mLeft - mPaddingRight;
   3086 
   3087             final int count = getChildCount();
   3088             final int headerCount = mHeaderViewInfos.size();
   3089             final int itemCount = mItemCount;
   3090             final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
   3091             final boolean headerDividers = mHeaderDividersEnabled;
   3092             final boolean footerDividers = mFooterDividersEnabled;
   3093             final int first = mFirstPosition;
   3094             final boolean areAllItemsSelectable = mAreAllItemsSelectable;
   3095             final ListAdapter adapter = mAdapter;
   3096             // If the list is opaque *and* the background is not, we want to
   3097             // fill a rect where the dividers would be for non-selectable items
   3098             // If the list is opaque and the background is also opaque, we don't
   3099             // need to draw anything since the background will do it for us
   3100             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
   3101 
   3102             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
   3103                 mDividerPaint = new Paint();
   3104                 mDividerPaint.setColor(getCacheColorHint());
   3105             }
   3106             final Paint paint = mDividerPaint;
   3107 
   3108             int effectivePaddingTop = 0;
   3109             int effectivePaddingBottom = 0;
   3110             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   3111                 effectivePaddingTop = mListPadding.top;
   3112                 effectivePaddingBottom = mListPadding.bottom;
   3113             }
   3114 
   3115             final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
   3116             if (!mStackFromBottom) {
   3117                 int bottom = 0;
   3118 
   3119                 // Draw top divider or header for overscroll
   3120                 final int scrollY = mScrollY;
   3121                 if (count > 0 && scrollY < 0) {
   3122                     if (drawOverscrollHeader) {
   3123                         bounds.bottom = 0;
   3124                         bounds.top = scrollY;
   3125                         drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3126                     } else if (drawDividers) {
   3127                         bounds.bottom = 0;
   3128                         bounds.top = -dividerHeight;
   3129                         drawDivider(canvas, bounds, -1);
   3130                     }
   3131                 }
   3132 
   3133                 for (int i = 0; i < count; i++) {
   3134                     if ((headerDividers || first + i >= headerCount) &&
   3135                             (footerDividers || first + i < footerLimit)) {
   3136                         View child = getChildAt(i);
   3137                         bottom = child.getBottom();
   3138                         // Don't draw dividers next to items that are not enabled
   3139 
   3140                         if (drawDividers &&
   3141                                 (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
   3142                             if ((areAllItemsSelectable ||
   3143                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
   3144                                             adapter.isEnabled(first + i + 1))))) {
   3145                                 bounds.top = bottom;
   3146                                 bounds.bottom = bottom + dividerHeight;
   3147                                 drawDivider(canvas, bounds, i);
   3148                             } else if (fillForMissingDividers) {
   3149                                 bounds.top = bottom;
   3150                                 bounds.bottom = bottom + dividerHeight;
   3151                                 canvas.drawRect(bounds, paint);
   3152                             }
   3153                         }
   3154                     }
   3155                 }
   3156 
   3157                 final int overFooterBottom = mBottom + mScrollY;
   3158                 if (drawOverscrollFooter && first + count == itemCount &&
   3159                         overFooterBottom > bottom) {
   3160                     bounds.top = bottom;
   3161                     bounds.bottom = overFooterBottom;
   3162                     drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3163                 }
   3164             } else {
   3165                 int top;
   3166 
   3167                 final int scrollY = mScrollY;
   3168 
   3169                 if (count > 0 && drawOverscrollHeader) {
   3170                     bounds.top = scrollY;
   3171                     bounds.bottom = getChildAt(0).getTop();
   3172                     drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3173                 }
   3174 
   3175                 final int start = drawOverscrollHeader ? 1 : 0;
   3176                 for (int i = start; i < count; i++) {
   3177                     if ((headerDividers || first + i >= headerCount) &&
   3178                             (footerDividers || first + i < footerLimit)) {
   3179                         View child = getChildAt(i);
   3180                         top = child.getTop();
   3181                         // Don't draw dividers next to items that are not enabled
   3182                         if (top > effectivePaddingTop) {
   3183                             if ((areAllItemsSelectable ||
   3184                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
   3185                                             adapter.isEnabled(first + i + 1))))) {
   3186                                 bounds.top = top - dividerHeight;
   3187                                 bounds.bottom = top;
   3188                                 // Give the method the child ABOVE the divider, so we
   3189                                 // subtract one from our child
   3190                                 // position. Give -1 when there is no child above the
   3191                                 // divider.
   3192                                 drawDivider(canvas, bounds, i - 1);
   3193                             } else if (fillForMissingDividers) {
   3194                                 bounds.top = top - dividerHeight;
   3195                                 bounds.bottom = top;
   3196                                 canvas.drawRect(bounds, paint);
   3197                             }
   3198                         }
   3199                     }
   3200                 }
   3201 
   3202                 if (count > 0 && scrollY > 0) {
   3203                     if (drawOverscrollFooter) {
   3204                         final int absListBottom = mBottom;
   3205                         bounds.top = absListBottom;
   3206                         bounds.bottom = absListBottom + scrollY;
   3207                         drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3208                     } else if (drawDividers) {
   3209                         bounds.top = listBottom;
   3210                         bounds.bottom = listBottom + dividerHeight;
   3211                         drawDivider(canvas, bounds, -1);
   3212                     }
   3213                 }
   3214             }
   3215         }
   3216 
   3217         // Draw the indicators (these should be drawn above the dividers) and children
   3218         super.dispatchDraw(canvas);
   3219     }
   3220 
   3221     @Override
   3222     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   3223         boolean more = super.drawChild(canvas, child, drawingTime);
   3224         if (mCachingActive && child.mCachingFailed) {
   3225             mCachingActive = false;
   3226         }
   3227         return more;
   3228     }
   3229 
   3230     /**
   3231      * Draws a divider for the given child in the given bounds.
   3232      *
   3233      * @param canvas The canvas to draw to.
   3234      * @param bounds The bounds of the divider.
   3235      * @param childIndex The index of child (of the View) above the divider.
   3236      *            This will be -1 if there is no child above the divider to be
   3237      *            drawn.
   3238      */
   3239     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
   3240         // This widget draws the same divider for all children
   3241         final Drawable divider = mDivider;
   3242 
   3243         divider.setBounds(bounds);
   3244         divider.draw(canvas);
   3245     }
   3246 
   3247     /**
   3248      * Returns the drawable that will be drawn between each item in the list.
   3249      *
   3250      * @return the current drawable drawn between list elements
   3251      */
   3252     public Drawable getDivider() {
   3253         return mDivider;
   3254     }
   3255 
   3256     /**
   3257      * Sets the drawable that will be drawn between each item in the list. If the drawable does
   3258      * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
   3259      *
   3260      * @param divider The drawable to use.
   3261      */
   3262     public void setDivider(Drawable divider) {
   3263         if (divider != null) {
   3264             mDividerHeight = divider.getIntrinsicHeight();
   3265         } else {
   3266             mDividerHeight = 0;
   3267         }
   3268         mDivider = divider;
   3269         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
   3270         requestLayout();
   3271         invalidate();
   3272     }
   3273 
   3274     /**
   3275      * @return Returns the height of the divider that will be drawn between each item in the list.
   3276      */
   3277     public int getDividerHeight() {
   3278         return mDividerHeight;
   3279     }
   3280 
   3281     /**
   3282      * Sets the height of the divider that will be drawn between each item in the list. Calling
   3283      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
   3284      *
   3285      * @param height The new height of the divider in pixels.
   3286      */
   3287     public void setDividerHeight(int height) {
   3288         mDividerHeight = height;
   3289         requestLayout();
   3290         invalidate();
   3291     }
   3292 
   3293     /**
   3294      * Enables or disables the drawing of the divider for header views.
   3295      *
   3296      * @param headerDividersEnabled True to draw the headers, false otherwise.
   3297      *
   3298      * @see #setFooterDividersEnabled(boolean)
   3299      * @see #addHeaderView(android.view.View)
   3300      */
   3301     public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
   3302         mHeaderDividersEnabled = headerDividersEnabled;
   3303         invalidate();
   3304     }
   3305 
   3306     /**
   3307      * Enables or disables the drawing of the divider for footer views.
   3308      *
   3309      * @param footerDividersEnabled True to draw the footers, false otherwise.
   3310      *
   3311      * @see #setHeaderDividersEnabled(boolean)
   3312      * @see #addFooterView(android.view.View)
   3313      */
   3314     public void setFooterDividersEnabled(boolean footerDividersEnabled) {
   3315         mFooterDividersEnabled = footerDividersEnabled;
   3316         invalidate();
   3317     }
   3318 
   3319     /**
   3320      * Sets the drawable that will be drawn above all other list content.
   3321      * This area can become visible when the user overscrolls the list.
   3322      *
   3323      * @param header The drawable to use
   3324      */
   3325     public void setOverscrollHeader(Drawable header) {
   3326         mOverScrollHeader = header;
   3327         if (mScrollY < 0) {
   3328             invalidate();
   3329         }
   3330     }
   3331 
   3332     /**
   3333      * @return The drawable that will be drawn above all other list content
   3334      */
   3335     public Drawable getOverscrollHeader() {
   3336         return mOverScrollHeader;
   3337     }
   3338 
   3339     /**
   3340      * Sets the drawable that will be drawn below all other list content.
   3341      * This area can become visible when the user overscrolls the list,
   3342      * or when the list's content does not fully fill the container area.
   3343      *
   3344      * @param footer The drawable to use
   3345      */
   3346     public void setOverscrollFooter(Drawable footer) {
   3347         mOverScrollFooter = footer;
   3348         invalidate();
   3349     }
   3350 
   3351     /**
   3352      * @return The drawable that will be drawn below all other list content
   3353      */
   3354     public Drawable getOverscrollFooter() {
   3355         return mOverScrollFooter;
   3356     }
   3357 
   3358     @Override
   3359     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   3360         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   3361 
   3362         final ListAdapter adapter = mAdapter;
   3363         int closetChildIndex = -1;
   3364         int closestChildTop = 0;
   3365         if (adapter != null && gainFocus && previouslyFocusedRect != null) {
   3366             previouslyFocusedRect.offset(mScrollX, mScrollY);
   3367 
   3368             // Don't cache the result of getChildCount or mFirstPosition here,
   3369             // it could change in layoutChildren.
   3370             if (adapter.getCount() < getChildCount() + mFirstPosition) {
   3371                 mLayoutMode = LAYOUT_NORMAL;
   3372                 layoutChildren();
   3373             }
   3374 
   3375             // figure out which item should be selected based on previously
   3376             // focused rect
   3377             Rect otherRect = mTempRect;
   3378             int minDistance = Integer.MAX_VALUE;
   3379             final int childCount = getChildCount();
   3380             final int firstPosition = mFirstPosition;
   3381 
   3382             for (int i = 0; i < childCount; i++) {
   3383                 // only consider selectable views
   3384                 if (!adapter.isEnabled(firstPosition + i)) {
   3385                     continue;
   3386                 }
   3387 
   3388                 View other = getChildAt(i);
   3389                 other.getDrawingRect(otherRect);
   3390                 offsetDescendantRectToMyCoords(other, otherRect);
   3391                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
   3392 
   3393                 if (distance < minDistance) {
   3394                     minDistance = distance;
   3395                     closetChildIndex = i;
   3396                     closestChildTop = other.getTop();
   3397                 }
   3398             }
   3399         }
   3400 
   3401         if (closetChildIndex >= 0) {
   3402             setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
   3403         } else {
   3404             requestLayout();
   3405         }
   3406     }
   3407 
   3408 
   3409     /*
   3410      * (non-Javadoc)
   3411      *
   3412      * Children specified in XML are assumed to be header views. After we have
   3413      * parsed them move them out of the children list and into mHeaderViews.
   3414      */
   3415     @Override
   3416     protected void onFinishInflate() {
   3417         super.onFinishInflate();
   3418 
   3419         int count = getChildCount();
   3420         if (count > 0) {
   3421             for (int i = 0; i < count; ++i) {
   3422                 addHeaderView(getChildAt(i));
   3423             }
   3424             removeAllViews();
   3425         }
   3426     }
   3427 
   3428     /* (non-Javadoc)
   3429      * @see android.view.View#findViewById(int)
   3430      * First look in our children, then in any header and footer views that may be scrolled off.
   3431      */
   3432     @Override
   3433     protected View findViewTraversal(int id) {
   3434         View v;
   3435         v = super.findViewTraversal(id);
   3436         if (v == null) {
   3437             v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
   3438             if (v != null) {
   3439                 return v;
   3440             }
   3441             v = findViewInHeadersOrFooters(mFooterViewInfos, id);
   3442             if (v != null) {
   3443                 return v;
   3444             }
   3445         }
   3446         return v;
   3447     }
   3448 
   3449     /* (non-Javadoc)
   3450      *
   3451      * Look in the passed in list of headers or footers for the view.
   3452      */
   3453     View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
   3454         if (where != null) {
   3455             int len = where.size();
   3456             View v;
   3457 
   3458             for (int i = 0; i < len; i++) {
   3459                 v = where.get(i).view;
   3460 
   3461                 if (!v.isRootNamespace()) {
   3462                     v = v.findViewById(id);
   3463 
   3464                     if (v != null) {
   3465                         return v;
   3466                     }
   3467                 }
   3468             }
   3469         }
   3470         return null;
   3471     }
   3472 
   3473     /* (non-Javadoc)
   3474      * @see android.view.View#findViewWithTag(Object)
   3475      * First look in our children, then in any header and footer views that may be scrolled off.
   3476      */
   3477     @Override
   3478     protected View findViewWithTagTraversal(Object tag) {
   3479         View v;
   3480         v = super.findViewWithTagTraversal(tag);
   3481         if (v == null) {
   3482             v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
   3483             if (v != null) {
   3484                 return v;
   3485             }
   3486 
   3487             v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
   3488             if (v != null) {
   3489                 return v;
   3490             }
   3491         }
   3492         return v;
   3493     }
   3494 
   3495     /* (non-Javadoc)
   3496      *
   3497      * Look in the passed in list of headers or footers for the view with the tag.
   3498      */
   3499     View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
   3500         if (where != null) {
   3501             int len = where.size();
   3502             View v;
   3503 
   3504             for (int i = 0; i < len; i++) {
   3505                 v = where.get(i).view;
   3506 
   3507                 if (!v.isRootNamespace()) {
   3508                     v = v.findViewWithTag(tag);
   3509 
   3510                     if (v != null) {
   3511                         return v;
   3512                     }
   3513                 }
   3514             }
   3515         }
   3516         return null;
   3517     }
   3518 
   3519     /**
   3520      * @hide
   3521      * @see android.view.View#findViewByPredicate(Predicate)
   3522      * First look in our children, then in any header and footer views that may be scrolled off.
   3523      */
   3524     @Override
   3525     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
   3526         View v;
   3527         v = super.findViewByPredicateTraversal(predicate, childToSkip);
   3528         if (v == null) {
   3529             v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
   3530             if (v != null) {
   3531                 return v;
   3532             }
   3533 
   3534             v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
   3535             if (v != null) {
   3536                 return v;
   3537             }
   3538         }
   3539         return v;
   3540     }
   3541 
   3542     /* (non-Javadoc)
   3543      *
   3544      * Look in the passed in list of headers or footers for the first view that matches
   3545      * the predicate.
   3546      */
   3547     View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
   3548             Predicate<View> predicate, View childToSkip) {
   3549         if (where != null) {
   3550             int len = where.size();
   3551             View v;
   3552 
   3553             for (int i = 0; i < len; i++) {
   3554                 v = where.get(i).view;
   3555 
   3556                 if (v != childToSkip && !v.isRootNamespace()) {
   3557                     v = v.findViewByPredicate(predicate);
   3558 
   3559                     if (v != null) {
   3560                         return v;
   3561                     }
   3562                 }
   3563             }
   3564         }
   3565         return null;
   3566     }
   3567 
   3568     /**
   3569      * Returns the set of checked items ids. The result is only valid if the
   3570      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
   3571      *
   3572      * @return A new array which contains the id of each checked item in the
   3573      *         list.
   3574      *
   3575      * @deprecated Use {@link #getCheckedItemIds()} instead.
   3576      */
   3577     @Deprecated
   3578     public long[] getCheckItemIds() {
   3579         // Use new behavior that correctly handles stable ID mapping.
   3580         if (mAdapter != null && mAdapter.hasStableIds()) {
   3581             return getCheckedItemIds();
   3582         }
   3583 
   3584         // Old behavior was buggy, but would sort of work for adapters without stable IDs.
   3585         // Fall back to it to support legacy apps.
   3586         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
   3587             final SparseBooleanArray states = mCheckStates;
   3588             final int count = states.size();
   3589             final long[] ids = new long[count];
   3590             final ListAdapter adapter = mAdapter;
   3591 
   3592             int checkedCount = 0;
   3593             for (int i = 0; i < count; i++) {
   3594                 if (states.valueAt(i)) {
   3595                     ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
   3596                 }
   3597             }
   3598 
   3599             // Trim array if needed. mCheckStates may contain false values
   3600             // resulting in checkedCount being smaller than count.
   3601             if (checkedCount == count) {
   3602                 return ids;
   3603             } else {
   3604                 final long[] result = new long[checkedCount];
   3605                 System.arraycopy(ids, 0, result, 0, checkedCount);
   3606 
   3607                 return result;
   3608             }
   3609         }
   3610         return new long[0];
   3611     }
   3612 }
   3613