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