Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 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.IntDef;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Rect;
     24 import android.os.Trace;
     25 import android.util.AttributeSet;
     26 import android.util.MathUtils;
     27 import android.view.Gravity;
     28 import android.view.KeyEvent;
     29 import android.view.SoundEffectConstants;
     30 import android.view.View;
     31 import android.view.ViewDebug;
     32 import android.view.ViewGroup;
     33 import android.view.ViewRootImpl;
     34 import android.view.accessibility.AccessibilityEvent;
     35 import android.view.accessibility.AccessibilityNodeInfo;
     36 import android.view.accessibility.AccessibilityNodeProvider;
     37 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
     38 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
     39 import android.view.animation.GridLayoutAnimationController;
     40 import android.widget.RemoteViews.RemoteView;
     41 
     42 import java.lang.annotation.Retention;
     43 import java.lang.annotation.RetentionPolicy;
     44 
     45 
     46 /**
     47  * A view that shows items in two-dimensional scrolling grid. The items in the
     48  * grid come from the {@link ListAdapter} associated with this view.
     49  *
     50  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
     51  * View</a> guide.</p>
     52  *
     53  * @attr ref android.R.styleable#GridView_horizontalSpacing
     54  * @attr ref android.R.styleable#GridView_verticalSpacing
     55  * @attr ref android.R.styleable#GridView_stretchMode
     56  * @attr ref android.R.styleable#GridView_columnWidth
     57  * @attr ref android.R.styleable#GridView_numColumns
     58  * @attr ref android.R.styleable#GridView_gravity
     59  */
     60 @RemoteView
     61 public class GridView extends AbsListView {
     62     /** @hide */
     63     @IntDef({NO_STRETCH, STRETCH_SPACING, STRETCH_COLUMN_WIDTH, STRETCH_SPACING_UNIFORM})
     64     @Retention(RetentionPolicy.SOURCE)
     65     public @interface StretchMode {}
     66 
     67     /**
     68      * Disables stretching.
     69      *
     70      * @see #setStretchMode(int)
     71      */
     72     public static final int NO_STRETCH = 0;
     73     /**
     74      * Stretches the spacing between columns.
     75      *
     76      * @see #setStretchMode(int)
     77      */
     78     public static final int STRETCH_SPACING = 1;
     79     /**
     80      * Stretches columns.
     81      *
     82      * @see #setStretchMode(int)
     83      */
     84     public static final int STRETCH_COLUMN_WIDTH = 2;
     85     /**
     86      * Stretches the spacing between columns. The spacing is uniform.
     87      *
     88      * @see #setStretchMode(int)
     89      */
     90     public static final int STRETCH_SPACING_UNIFORM = 3;
     91 
     92     /**
     93      * Creates as many columns as can fit on screen.
     94      *
     95      * @see #setNumColumns(int)
     96      */
     97     public static final int AUTO_FIT = -1;
     98 
     99     private int mNumColumns = AUTO_FIT;
    100 
    101     private int mHorizontalSpacing = 0;
    102     private int mRequestedHorizontalSpacing;
    103     private int mVerticalSpacing = 0;
    104     private int mStretchMode = STRETCH_COLUMN_WIDTH;
    105     private int mColumnWidth;
    106     private int mRequestedColumnWidth;
    107     private int mRequestedNumColumns;
    108 
    109     private View mReferenceView = null;
    110     private View mReferenceViewInSelectedRow = null;
    111 
    112     private int mGravity = Gravity.START;
    113 
    114     private final Rect mTempRect = new Rect();
    115 
    116     public GridView(Context context) {
    117         this(context, null);
    118     }
    119 
    120     public GridView(Context context, AttributeSet attrs) {
    121         this(context, attrs, com.android.internal.R.attr.gridViewStyle);
    122     }
    123 
    124     public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
    125         this(context, attrs, defStyleAttr, 0);
    126     }
    127 
    128     public GridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    129         super(context, attrs, defStyleAttr, defStyleRes);
    130 
    131         final TypedArray a = context.obtainStyledAttributes(
    132                 attrs, com.android.internal.R.styleable.GridView, defStyleAttr, defStyleRes);
    133 
    134         int hSpacing = a.getDimensionPixelOffset(
    135                 com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
    136         setHorizontalSpacing(hSpacing);
    137 
    138         int vSpacing = a.getDimensionPixelOffset(
    139                 com.android.internal.R.styleable.GridView_verticalSpacing, 0);
    140         setVerticalSpacing(vSpacing);
    141 
    142         int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
    143         if (index >= 0) {
    144             setStretchMode(index);
    145         }
    146 
    147         int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
    148         if (columnWidth > 0) {
    149             setColumnWidth(columnWidth);
    150         }
    151 
    152         int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
    153         setNumColumns(numColumns);
    154 
    155         index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
    156         if (index >= 0) {
    157             setGravity(index);
    158         }
    159 
    160         a.recycle();
    161     }
    162 
    163     @Override
    164     public ListAdapter getAdapter() {
    165         return mAdapter;
    166     }
    167 
    168     /**
    169      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
    170      * through the specified intent.
    171      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
    172      */
    173     @android.view.RemotableViewMethod
    174     public void setRemoteViewsAdapter(Intent intent) {
    175         super.setRemoteViewsAdapter(intent);
    176     }
    177 
    178     /**
    179      * Sets the data behind this GridView.
    180      *
    181      * @param adapter the adapter providing the grid's data
    182      */
    183     @Override
    184     public void setAdapter(ListAdapter adapter) {
    185         if (mAdapter != null && mDataSetObserver != null) {
    186             mAdapter.unregisterDataSetObserver(mDataSetObserver);
    187         }
    188 
    189         resetList();
    190         mRecycler.clear();
    191         mAdapter = adapter;
    192 
    193         mOldSelectedPosition = INVALID_POSITION;
    194         mOldSelectedRowId = INVALID_ROW_ID;
    195 
    196         // AbsListView#setAdapter will update choice mode states.
    197         super.setAdapter(adapter);
    198 
    199         if (mAdapter != null) {
    200             mOldItemCount = mItemCount;
    201             mItemCount = mAdapter.getCount();
    202             mDataChanged = true;
    203             checkFocus();
    204 
    205             mDataSetObserver = new AdapterDataSetObserver();
    206             mAdapter.registerDataSetObserver(mDataSetObserver);
    207 
    208             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    209 
    210             int position;
    211             if (mStackFromBottom) {
    212                 position = lookForSelectablePosition(mItemCount - 1, false);
    213             } else {
    214                 position = lookForSelectablePosition(0, true);
    215             }
    216             setSelectedPositionInt(position);
    217             setNextSelectedPositionInt(position);
    218             checkSelectionChanged();
    219         } else {
    220             checkFocus();
    221             // Nothing selected
    222             checkSelectionChanged();
    223         }
    224 
    225         requestLayout();
    226     }
    227 
    228     @Override
    229     int lookForSelectablePosition(int position, boolean lookDown) {
    230         final ListAdapter adapter = mAdapter;
    231         if (adapter == null || isInTouchMode()) {
    232             return INVALID_POSITION;
    233         }
    234 
    235         if (position < 0 || position >= mItemCount) {
    236             return INVALID_POSITION;
    237         }
    238         return position;
    239     }
    240 
    241     /**
    242      * {@inheritDoc}
    243      */
    244     @Override
    245     void fillGap(boolean down) {
    246         final int numColumns = mNumColumns;
    247         final int verticalSpacing = mVerticalSpacing;
    248 
    249         final int count = getChildCount();
    250 
    251         if (down) {
    252             int paddingTop = 0;
    253             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    254                 paddingTop = getListPaddingTop();
    255             }
    256             final int startOffset = count > 0 ?
    257                     getChildAt(count - 1).getBottom() + verticalSpacing : paddingTop;
    258             int position = mFirstPosition + count;
    259             if (mStackFromBottom) {
    260                 position += numColumns - 1;
    261             }
    262             fillDown(position, startOffset);
    263             correctTooHigh(numColumns, verticalSpacing, getChildCount());
    264         } else {
    265             int paddingBottom = 0;
    266             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    267                 paddingBottom = getListPaddingBottom();
    268             }
    269             final int startOffset = count > 0 ?
    270                     getChildAt(0).getTop() - verticalSpacing : getHeight() - paddingBottom;
    271             int position = mFirstPosition;
    272             if (!mStackFromBottom) {
    273                 position -= numColumns;
    274             } else {
    275                 position--;
    276             }
    277             fillUp(position, startOffset);
    278             correctTooLow(numColumns, verticalSpacing, getChildCount());
    279         }
    280     }
    281 
    282     /**
    283      * Fills the list from pos down to the end of the list view.
    284      *
    285      * @param pos The first position to put in the list
    286      *
    287      * @param nextTop The location where the top of the item associated with pos
    288      *        should be drawn
    289      *
    290      * @return The view that is currently selected, if it happens to be in the
    291      *         range that we draw.
    292      */
    293     private View fillDown(int pos, int nextTop) {
    294         View selectedView = null;
    295 
    296         int end = (mBottom - mTop);
    297         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    298             end -= mListPadding.bottom;
    299         }
    300 
    301         while (nextTop < end && pos < mItemCount) {
    302             View temp = makeRow(pos, nextTop, true);
    303             if (temp != null) {
    304                 selectedView = temp;
    305             }
    306 
    307             // mReferenceView will change with each call to makeRow()
    308             // do not cache in a local variable outside of this loop
    309             nextTop = mReferenceView.getBottom() + mVerticalSpacing;
    310 
    311             pos += mNumColumns;
    312         }
    313 
    314         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    315         return selectedView;
    316     }
    317 
    318     private View makeRow(int startPos, int y, boolean flow) {
    319         final int columnWidth = mColumnWidth;
    320         final int horizontalSpacing = mHorizontalSpacing;
    321 
    322         final boolean isLayoutRtl = isLayoutRtl();
    323 
    324         int last;
    325         int nextLeft;
    326 
    327         if (isLayoutRtl) {
    328             nextLeft = getWidth() - mListPadding.right - columnWidth -
    329                     ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
    330         } else {
    331             nextLeft = mListPadding.left +
    332                     ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
    333         }
    334 
    335         if (!mStackFromBottom) {
    336             last = Math.min(startPos + mNumColumns, mItemCount);
    337         } else {
    338             last = startPos + 1;
    339             startPos = Math.max(0, startPos - mNumColumns + 1);
    340 
    341             if (last - startPos < mNumColumns) {
    342                 final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
    343                 nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft;
    344             }
    345         }
    346 
    347         View selectedView = null;
    348 
    349         final boolean hasFocus = shouldShowSelector();
    350         final boolean inClick = touchModeDrawsInPressedState();
    351         final int selectedPosition = mSelectedPosition;
    352 
    353         View child = null;
    354         final int nextChildDir = isLayoutRtl ? -1 : +1;
    355         for (int pos = startPos; pos < last; pos++) {
    356             // is this the selected item?
    357             boolean selected = pos == selectedPosition;
    358             // does the list view have focus or contain focus
    359 
    360             final int where = flow ? -1 : pos - startPos;
    361             child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
    362 
    363             nextLeft += nextChildDir * columnWidth;
    364             if (pos < last - 1) {
    365                 nextLeft += nextChildDir * horizontalSpacing;
    366             }
    367 
    368             if (selected && (hasFocus || inClick)) {
    369                 selectedView = child;
    370             }
    371         }
    372 
    373         mReferenceView = child;
    374 
    375         if (selectedView != null) {
    376             mReferenceViewInSelectedRow = mReferenceView;
    377         }
    378 
    379         return selectedView;
    380     }
    381 
    382     /**
    383      * Fills the list from pos up to the top of the list view.
    384      *
    385      * @param pos The first position to put in the list
    386      *
    387      * @param nextBottom The location where the bottom of the item associated
    388      *        with pos should be drawn
    389      *
    390      * @return The view that is currently selected
    391      */
    392     private View fillUp(int pos, int nextBottom) {
    393         View selectedView = null;
    394 
    395         int end = 0;
    396         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    397             end = mListPadding.top;
    398         }
    399 
    400         while (nextBottom > end && pos >= 0) {
    401 
    402             View temp = makeRow(pos, nextBottom, false);
    403             if (temp != null) {
    404                 selectedView = temp;
    405             }
    406 
    407             nextBottom = mReferenceView.getTop() - mVerticalSpacing;
    408 
    409             mFirstPosition = pos;
    410 
    411             pos -= mNumColumns;
    412         }
    413 
    414         if (mStackFromBottom) {
    415             mFirstPosition = Math.max(0, pos + 1);
    416         }
    417 
    418         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    419         return selectedView;
    420     }
    421 
    422     /**
    423      * Fills the list from top to bottom, starting with mFirstPosition
    424      *
    425      * @param nextTop The location where the top of the first item should be
    426      *        drawn
    427      *
    428      * @return The view that is currently selected
    429      */
    430     private View fillFromTop(int nextTop) {
    431         mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    432         mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    433         if (mFirstPosition < 0) {
    434             mFirstPosition = 0;
    435         }
    436         mFirstPosition -= mFirstPosition % mNumColumns;
    437         return fillDown(mFirstPosition, nextTop);
    438     }
    439 
    440     private View fillFromBottom(int lastPosition, int nextBottom) {
    441         lastPosition = Math.max(lastPosition, mSelectedPosition);
    442         lastPosition = Math.min(lastPosition, mItemCount - 1);
    443 
    444         final int invertedPosition = mItemCount - 1 - lastPosition;
    445         lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
    446 
    447         return fillUp(lastPosition, nextBottom);
    448     }
    449 
    450     private View fillSelection(int childrenTop, int childrenBottom) {
    451         final int selectedPosition = reconcileSelectedPosition();
    452         final int numColumns = mNumColumns;
    453         final int verticalSpacing = mVerticalSpacing;
    454 
    455         int rowStart;
    456         int rowEnd = -1;
    457 
    458         if (!mStackFromBottom) {
    459             rowStart = selectedPosition - (selectedPosition % numColumns);
    460         } else {
    461             final int invertedSelection = mItemCount - 1 - selectedPosition;
    462 
    463             rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
    464             rowStart = Math.max(0, rowEnd - numColumns + 1);
    465         }
    466 
    467         final int fadingEdgeLength = getVerticalFadingEdgeLength();
    468         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
    469 
    470         final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
    471         mFirstPosition = rowStart;
    472 
    473         final View referenceView = mReferenceView;
    474 
    475         if (!mStackFromBottom) {
    476             fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
    477             pinToBottom(childrenBottom);
    478             fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
    479             adjustViewsUpOrDown();
    480         } else {
    481             final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
    482                     fadingEdgeLength, numColumns, rowStart);
    483             final int offset = bottomSelectionPixel - referenceView.getBottom();
    484             offsetChildrenTopAndBottom(offset);
    485             fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
    486             pinToTop(childrenTop);
    487             fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
    488             adjustViewsUpOrDown();
    489         }
    490 
    491         return sel;
    492     }
    493 
    494     private void pinToTop(int childrenTop) {
    495         if (mFirstPosition == 0) {
    496             final int top = getChildAt(0).getTop();
    497             final int offset = childrenTop - top;
    498             if (offset < 0) {
    499                 offsetChildrenTopAndBottom(offset);
    500             }
    501         }
    502     }
    503 
    504     private void pinToBottom(int childrenBottom) {
    505         final int count = getChildCount();
    506         if (mFirstPosition + count == mItemCount) {
    507             final int bottom = getChildAt(count - 1).getBottom();
    508             final int offset = childrenBottom - bottom;
    509             if (offset > 0) {
    510                 offsetChildrenTopAndBottom(offset);
    511             }
    512         }
    513     }
    514 
    515     @Override
    516     int findMotionRow(int y) {
    517         final int childCount = getChildCount();
    518         if (childCount > 0) {
    519 
    520             final int numColumns = mNumColumns;
    521             if (!mStackFromBottom) {
    522                 for (int i = 0; i < childCount; i += numColumns) {
    523                     if (y <= getChildAt(i).getBottom()) {
    524                         return mFirstPosition + i;
    525                     }
    526                 }
    527             } else {
    528                 for (int i = childCount - 1; i >= 0; i -= numColumns) {
    529                     if (y >= getChildAt(i).getTop()) {
    530                         return mFirstPosition + i;
    531                     }
    532                 }
    533             }
    534         }
    535         return INVALID_POSITION;
    536     }
    537 
    538     /**
    539      * Layout during a scroll that results from tracking motion events. Places
    540      * the mMotionPosition view at the offset specified by mMotionViewTop, and
    541      * then build surrounding views from there.
    542      *
    543      * @param position the position at which to start filling
    544      * @param top the top of the view at that position
    545      * @return The selected view, or null if the selected view is outside the
    546      *         visible area.
    547      */
    548     private View fillSpecific(int position, int top) {
    549         final int numColumns = mNumColumns;
    550 
    551         int motionRowStart;
    552         int motionRowEnd = -1;
    553 
    554         if (!mStackFromBottom) {
    555             motionRowStart = position - (position % numColumns);
    556         } else {
    557             final int invertedSelection = mItemCount - 1 - position;
    558 
    559             motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
    560             motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
    561         }
    562 
    563         final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
    564 
    565         // Possibly changed again in fillUp if we add rows above this one.
    566         mFirstPosition = motionRowStart;
    567 
    568         final View referenceView = mReferenceView;
    569         // We didn't have anything to layout, bail out
    570         if (referenceView == null) {
    571             return null;
    572         }
    573 
    574         final int verticalSpacing = mVerticalSpacing;
    575 
    576         View above;
    577         View below;
    578 
    579         if (!mStackFromBottom) {
    580             above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
    581             adjustViewsUpOrDown();
    582             below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
    583             // Check if we have dragged the bottom of the grid too high
    584             final int childCount = getChildCount();
    585             if (childCount > 0) {
    586                 correctTooHigh(numColumns, verticalSpacing, childCount);
    587             }
    588         } else {
    589             below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
    590             adjustViewsUpOrDown();
    591             above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
    592             // Check if we have dragged the bottom of the grid too high
    593             final int childCount = getChildCount();
    594             if (childCount > 0) {
    595                 correctTooLow(numColumns, verticalSpacing, childCount);
    596             }
    597         }
    598 
    599         if (temp != null) {
    600             return temp;
    601         } else if (above != null) {
    602             return above;
    603         } else {
    604             return below;
    605         }
    606     }
    607 
    608     private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
    609         // First see if the last item is visible
    610         final int lastPosition = mFirstPosition + childCount - 1;
    611         if (lastPosition == mItemCount - 1 && childCount > 0) {
    612             // Get the last child ...
    613             final View lastChild = getChildAt(childCount - 1);
    614 
    615             // ... and its bottom edge
    616             final int lastBottom = lastChild.getBottom();
    617             // This is bottom of our drawable area
    618             final int end = (mBottom - mTop) - mListPadding.bottom;
    619 
    620             // This is how far the bottom edge of the last view is from the bottom of the
    621             // drawable area
    622             int bottomOffset = end - lastBottom;
    623 
    624             final View firstChild = getChildAt(0);
    625             final int firstTop = firstChild.getTop();
    626 
    627             // Make sure we are 1) Too high, and 2) Either there are more rows above the
    628             // first row or the first row is scrolled off the top of the drawable area
    629             if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
    630                 if (mFirstPosition == 0) {
    631                     // Don't pull the top too far down
    632                     bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
    633                 }
    634 
    635                 // Move everything down
    636                 offsetChildrenTopAndBottom(bottomOffset);
    637                 if (mFirstPosition > 0) {
    638                     // Fill the gap that was opened above mFirstPosition with more rows, if
    639                     // possible
    640                     fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
    641                             firstChild.getTop() - verticalSpacing);
    642                     // Close up the remaining gap
    643                     adjustViewsUpOrDown();
    644                 }
    645             }
    646         }
    647     }
    648 
    649     private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
    650         if (mFirstPosition == 0 && childCount > 0) {
    651             // Get the first child ...
    652             final View firstChild = getChildAt(0);
    653 
    654             // ... and its top edge
    655             final int firstTop = firstChild.getTop();
    656 
    657             // This is top of our drawable area
    658             final int start = mListPadding.top;
    659 
    660             // This is bottom of our drawable area
    661             final int end = (mBottom - mTop) - mListPadding.bottom;
    662 
    663             // This is how far the top edge of the first view is from the top of the
    664             // drawable area
    665             int topOffset = firstTop - start;
    666             final View lastChild = getChildAt(childCount - 1);
    667             final int lastBottom = lastChild.getBottom();
    668             final int lastPosition = mFirstPosition + childCount - 1;
    669 
    670             // Make sure we are 1) Too low, and 2) Either there are more rows below the
    671             // last row or the last row is scrolled off the bottom of the drawable area
    672             if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end))  {
    673                 if (lastPosition == mItemCount - 1 ) {
    674                     // Don't pull the bottom too far up
    675                     topOffset = Math.min(topOffset, lastBottom - end);
    676                 }
    677 
    678                 // Move everything up
    679                 offsetChildrenTopAndBottom(-topOffset);
    680                 if (lastPosition < mItemCount - 1) {
    681                     // Fill the gap that was opened below the last position with more rows, if
    682                     // possible
    683                     fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
    684                             lastChild.getBottom() + verticalSpacing);
    685                     // Close up the remaining gap
    686                     adjustViewsUpOrDown();
    687                 }
    688             }
    689         }
    690     }
    691 
    692     /**
    693      * Fills the grid based on positioning the new selection at a specific
    694      * location. The selection may be moved so that it does not intersect the
    695      * faded edges. The grid is then filled upwards and downwards from there.
    696      *
    697      * @param selectedTop Where the selected item should be
    698      * @param childrenTop Where to start drawing children
    699      * @param childrenBottom Last pixel where children can be drawn
    700      * @return The view that currently has selection
    701      */
    702     private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
    703         final int fadingEdgeLength = getVerticalFadingEdgeLength();
    704         final int selectedPosition = mSelectedPosition;
    705         final int numColumns = mNumColumns;
    706         final int verticalSpacing = mVerticalSpacing;
    707 
    708         int rowStart;
    709         int rowEnd = -1;
    710 
    711         if (!mStackFromBottom) {
    712             rowStart = selectedPosition - (selectedPosition % numColumns);
    713         } else {
    714             int invertedSelection = mItemCount - 1 - selectedPosition;
    715 
    716             rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
    717             rowStart = Math.max(0, rowEnd - numColumns + 1);
    718         }
    719 
    720         View sel;
    721         View referenceView;
    722 
    723         int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
    724         int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
    725                 numColumns, rowStart);
    726 
    727         sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
    728         // Possibly changed again in fillUp if we add rows above this one.
    729         mFirstPosition = rowStart;
    730 
    731         referenceView = mReferenceView;
    732         adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
    733         adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
    734 
    735         if (!mStackFromBottom) {
    736             fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
    737             adjustViewsUpOrDown();
    738             fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
    739         } else {
    740             fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
    741             adjustViewsUpOrDown();
    742             fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
    743         }
    744 
    745 
    746         return sel;
    747     }
    748 
    749     /**
    750      * Calculate the bottom-most pixel we can draw the selection into
    751      *
    752      * @param childrenBottom Bottom pixel were children can be drawn
    753      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    754      * @param numColumns Number of columns in the grid
    755      * @param rowStart The start of the row that will contain the selection
    756      * @return The bottom-most pixel we can draw the selection into
    757      */
    758     private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
    759             int numColumns, int rowStart) {
    760         // Last pixel we can draw the selection into
    761         int bottomSelectionPixel = childrenBottom;
    762         if (rowStart + numColumns - 1 < mItemCount - 1) {
    763             bottomSelectionPixel -= fadingEdgeLength;
    764         }
    765         return bottomSelectionPixel;
    766     }
    767 
    768     /**
    769      * Calculate the top-most pixel we can draw the selection into
    770      *
    771      * @param childrenTop Top pixel were children can be drawn
    772      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    773      * @param rowStart The start of the row that will contain the selection
    774      * @return The top-most pixel we can draw the selection into
    775      */
    776     private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
    777         // first pixel we can draw the selection into
    778         int topSelectionPixel = childrenTop;
    779         if (rowStart > 0) {
    780             topSelectionPixel += fadingEdgeLength;
    781         }
    782         return topSelectionPixel;
    783     }
    784 
    785     /**
    786      * Move all views upwards so the selected row does not interesect the bottom
    787      * fading edge (if necessary).
    788      *
    789      * @param childInSelectedRow A child in the row that contains the selection
    790      * @param topSelectionPixel The topmost pixel we can draw the selection into
    791      * @param bottomSelectionPixel The bottommost pixel we can draw the
    792      *        selection into
    793      */
    794     private void adjustForBottomFadingEdge(View childInSelectedRow,
    795             int topSelectionPixel, int bottomSelectionPixel) {
    796         // Some of the newly selected item extends below the bottom of the
    797         // list
    798         if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
    799 
    800             // Find space available above the selection into which we can
    801             // scroll upwards
    802             int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
    803 
    804             // Find space required to bring the bottom of the selected item
    805             // fully into view
    806             int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
    807             int offset = Math.min(spaceAbove, spaceBelow);
    808 
    809             // Now offset the selected item to get it into view
    810             offsetChildrenTopAndBottom(-offset);
    811         }
    812     }
    813 
    814     /**
    815      * Move all views upwards so the selected row does not interesect the top
    816      * fading edge (if necessary).
    817      *
    818      * @param childInSelectedRow A child in the row that contains the selection
    819      * @param topSelectionPixel The topmost pixel we can draw the selection into
    820      * @param bottomSelectionPixel The bottommost pixel we can draw the
    821      *        selection into
    822      */
    823     private void adjustForTopFadingEdge(View childInSelectedRow,
    824             int topSelectionPixel, int bottomSelectionPixel) {
    825         // Some of the newly selected item extends above the top of the list
    826         if (childInSelectedRow.getTop() < topSelectionPixel) {
    827             // Find space required to bring the top of the selected item
    828             // fully into view
    829             int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
    830 
    831             // Find space available below the selection into which we can
    832             // scroll downwards
    833             int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
    834             int offset = Math.min(spaceAbove, spaceBelow);
    835 
    836             // Now offset the selected item to get it into view
    837             offsetChildrenTopAndBottom(offset);
    838         }
    839     }
    840 
    841     /**
    842      * Smoothly scroll to the specified adapter position. The view will
    843      * scroll such that the indicated position is displayed.
    844      * @param position Scroll to this adapter position.
    845      */
    846     @android.view.RemotableViewMethod
    847     public void smoothScrollToPosition(int position) {
    848         super.smoothScrollToPosition(position);
    849     }
    850 
    851     /**
    852      * Smoothly scroll to the specified adapter position offset. The view will
    853      * scroll such that the indicated position is displayed.
    854      * @param offset The amount to offset from the adapter position to scroll to.
    855      */
    856     @android.view.RemotableViewMethod
    857     public void smoothScrollByOffset(int offset) {
    858         super.smoothScrollByOffset(offset);
    859     }
    860 
    861     /**
    862      * Fills the grid based on positioning the new selection relative to the old
    863      * selection. The new selection will be placed at, above, or below the
    864      * location of the new selection depending on how the selection is moving.
    865      * The selection will then be pinned to the visible part of the screen,
    866      * excluding the edges that are faded. The grid is then filled upwards and
    867      * downwards from there.
    868      *
    869      * @param delta Which way we are moving
    870      * @param childrenTop Where to start drawing children
    871      * @param childrenBottom Last pixel where children can be drawn
    872      * @return The view that currently has selection
    873      */
    874     private View moveSelection(int delta, int childrenTop, int childrenBottom) {
    875         final int fadingEdgeLength = getVerticalFadingEdgeLength();
    876         final int selectedPosition = mSelectedPosition;
    877         final int numColumns = mNumColumns;
    878         final int verticalSpacing = mVerticalSpacing;
    879 
    880         int oldRowStart;
    881         int rowStart;
    882         int rowEnd = -1;
    883 
    884         if (!mStackFromBottom) {
    885             oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
    886 
    887             rowStart = selectedPosition - (selectedPosition % numColumns);
    888         } else {
    889             int invertedSelection = mItemCount - 1 - selectedPosition;
    890 
    891             rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
    892             rowStart = Math.max(0, rowEnd - numColumns + 1);
    893 
    894             invertedSelection = mItemCount - 1 - (selectedPosition - delta);
    895             oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
    896             oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
    897         }
    898 
    899         final int rowDelta = rowStart - oldRowStart;
    900 
    901         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
    902         final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
    903                 numColumns, rowStart);
    904 
    905         // Possibly changed again in fillUp if we add rows above this one.
    906         mFirstPosition = rowStart;
    907 
    908         View sel;
    909         View referenceView;
    910 
    911         if (rowDelta > 0) {
    912             /*
    913              * Case 1: Scrolling down.
    914              */
    915 
    916             final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
    917                     mReferenceViewInSelectedRow.getBottom();
    918 
    919             sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
    920             referenceView = mReferenceView;
    921 
    922             adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
    923         } else if (rowDelta < 0) {
    924             /*
    925              * Case 2: Scrolling up.
    926              */
    927             final int oldTop = mReferenceViewInSelectedRow == null ?
    928                     0 : mReferenceViewInSelectedRow .getTop();
    929 
    930             sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
    931             referenceView = mReferenceView;
    932 
    933             adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
    934         } else {
    935             /*
    936              * Keep selection where it was
    937              */
    938             final int oldTop = mReferenceViewInSelectedRow == null ?
    939                     0 : mReferenceViewInSelectedRow .getTop();
    940 
    941             sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
    942             referenceView = mReferenceView;
    943         }
    944 
    945         if (!mStackFromBottom) {
    946             fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
    947             adjustViewsUpOrDown();
    948             fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
    949         } else {
    950             fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
    951             adjustViewsUpOrDown();
    952             fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
    953         }
    954 
    955         return sel;
    956     }
    957 
    958     private boolean determineColumns(int availableSpace) {
    959         final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
    960         final int stretchMode = mStretchMode;
    961         final int requestedColumnWidth = mRequestedColumnWidth;
    962         boolean didNotInitiallyFit = false;
    963 
    964         if (mRequestedNumColumns == AUTO_FIT) {
    965             if (requestedColumnWidth > 0) {
    966                 // Client told us to pick the number of columns
    967                 mNumColumns = (availableSpace + requestedHorizontalSpacing) /
    968                         (requestedColumnWidth + requestedHorizontalSpacing);
    969             } else {
    970                 // Just make up a number if we don't have enough info
    971                 mNumColumns = 2;
    972             }
    973         } else {
    974             // We picked the columns
    975             mNumColumns = mRequestedNumColumns;
    976         }
    977 
    978         if (mNumColumns <= 0) {
    979             mNumColumns = 1;
    980         }
    981 
    982         switch (stretchMode) {
    983         case NO_STRETCH:
    984             // Nobody stretches
    985             mColumnWidth = requestedColumnWidth;
    986             mHorizontalSpacing = requestedHorizontalSpacing;
    987             break;
    988 
    989         default:
    990             int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
    991                     ((mNumColumns - 1) * requestedHorizontalSpacing);
    992 
    993             if (spaceLeftOver < 0) {
    994                 didNotInitiallyFit = true;
    995             }
    996 
    997             switch (stretchMode) {
    998             case STRETCH_COLUMN_WIDTH:
    999                 // Stretch the columns
   1000                 mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
   1001                 mHorizontalSpacing = requestedHorizontalSpacing;
   1002                 break;
   1003 
   1004             case STRETCH_SPACING:
   1005                 // Stretch the spacing between columns
   1006                 mColumnWidth = requestedColumnWidth;
   1007                 if (mNumColumns > 1) {
   1008                     mHorizontalSpacing = requestedHorizontalSpacing +
   1009                         spaceLeftOver / (mNumColumns - 1);
   1010                 } else {
   1011                     mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
   1012                 }
   1013                 break;
   1014 
   1015             case STRETCH_SPACING_UNIFORM:
   1016                 // Stretch the spacing between columns
   1017                 mColumnWidth = requestedColumnWidth;
   1018                 if (mNumColumns > 1) {
   1019                     mHorizontalSpacing = requestedHorizontalSpacing +
   1020                         spaceLeftOver / (mNumColumns + 1);
   1021                 } else {
   1022                     mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
   1023                 }
   1024                 break;
   1025             }
   1026 
   1027             break;
   1028         }
   1029         return didNotInitiallyFit;
   1030     }
   1031 
   1032     @Override
   1033     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1034         // Sets up mListPadding
   1035         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1036 
   1037         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   1038         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   1039         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   1040         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   1041 
   1042         if (widthMode == MeasureSpec.UNSPECIFIED) {
   1043             if (mColumnWidth > 0) {
   1044                 widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
   1045             } else {
   1046                 widthSize = mListPadding.left + mListPadding.right;
   1047             }
   1048             widthSize += getVerticalScrollbarWidth();
   1049         }
   1050 
   1051         int childWidth = widthSize - mListPadding.left - mListPadding.right;
   1052         boolean didNotInitiallyFit = determineColumns(childWidth);
   1053 
   1054         int childHeight = 0;
   1055         int childState = 0;
   1056 
   1057         mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
   1058         final int count = mItemCount;
   1059         if (count > 0) {
   1060             final View child = obtainView(0, mIsScrap);
   1061 
   1062             AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
   1063             if (p == null) {
   1064                 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1065                 child.setLayoutParams(p);
   1066             }
   1067             p.viewType = mAdapter.getItemViewType(0);
   1068             p.forceAdd = true;
   1069 
   1070             int childHeightSpec = getChildMeasureSpec(
   1071                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
   1072             int childWidthSpec = getChildMeasureSpec(
   1073                     MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
   1074             child.measure(childWidthSpec, childHeightSpec);
   1075 
   1076             childHeight = child.getMeasuredHeight();
   1077             childState = combineMeasuredStates(childState, child.getMeasuredState());
   1078 
   1079             if (mRecycler.shouldRecycleViewType(p.viewType)) {
   1080                 mRecycler.addScrapView(child, -1);
   1081             }
   1082         }
   1083 
   1084         if (heightMode == MeasureSpec.UNSPECIFIED) {
   1085             heightSize = mListPadding.top + mListPadding.bottom + childHeight +
   1086                     getVerticalFadingEdgeLength() * 2;
   1087         }
   1088 
   1089         if (heightMode == MeasureSpec.AT_MOST) {
   1090             int ourSize =  mListPadding.top + mListPadding.bottom;
   1091 
   1092             final int numColumns = mNumColumns;
   1093             for (int i = 0; i < count; i += numColumns) {
   1094                 ourSize += childHeight;
   1095                 if (i + numColumns < count) {
   1096                     ourSize += mVerticalSpacing;
   1097                 }
   1098                 if (ourSize >= heightSize) {
   1099                     ourSize = heightSize;
   1100                     break;
   1101                 }
   1102             }
   1103             heightSize = ourSize;
   1104         }
   1105 
   1106         if (widthMode == MeasureSpec.AT_MOST && mRequestedNumColumns != AUTO_FIT) {
   1107             int ourSize = (mRequestedNumColumns*mColumnWidth)
   1108                     + ((mRequestedNumColumns-1)*mHorizontalSpacing)
   1109                     + mListPadding.left + mListPadding.right;
   1110             if (ourSize > widthSize || didNotInitiallyFit) {
   1111                 widthSize |= MEASURED_STATE_TOO_SMALL;
   1112             }
   1113         }
   1114 
   1115         setMeasuredDimension(widthSize, heightSize);
   1116         mWidthMeasureSpec = widthMeasureSpec;
   1117     }
   1118 
   1119     @Override
   1120     protected void attachLayoutAnimationParameters(View child,
   1121             ViewGroup.LayoutParams params, int index, int count) {
   1122 
   1123         GridLayoutAnimationController.AnimationParameters animationParams =
   1124                 (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
   1125 
   1126         if (animationParams == null) {
   1127             animationParams = new GridLayoutAnimationController.AnimationParameters();
   1128             params.layoutAnimationParameters = animationParams;
   1129         }
   1130 
   1131         animationParams.count = count;
   1132         animationParams.index = index;
   1133         animationParams.columnsCount = mNumColumns;
   1134         animationParams.rowsCount = count / mNumColumns;
   1135 
   1136         if (!mStackFromBottom) {
   1137             animationParams.column = index % mNumColumns;
   1138             animationParams.row = index / mNumColumns;
   1139         } else {
   1140             final int invertedIndex = count - 1 - index;
   1141 
   1142             animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
   1143             animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
   1144         }
   1145     }
   1146 
   1147     @Override
   1148     protected void layoutChildren() {
   1149         final boolean blockLayoutRequests = mBlockLayoutRequests;
   1150         if (!blockLayoutRequests) {
   1151             mBlockLayoutRequests = true;
   1152         }
   1153 
   1154         try {
   1155             super.layoutChildren();
   1156 
   1157             invalidate();
   1158 
   1159             if (mAdapter == null) {
   1160                 resetList();
   1161                 invokeOnItemScrollListener();
   1162                 return;
   1163             }
   1164 
   1165             final int childrenTop = mListPadding.top;
   1166             final int childrenBottom = mBottom - mTop - mListPadding.bottom;
   1167 
   1168             int childCount = getChildCount();
   1169             int index;
   1170             int delta = 0;
   1171 
   1172             View sel;
   1173             View oldSel = null;
   1174             View oldFirst = null;
   1175             View newSel = null;
   1176 
   1177             // Remember stuff we will need down below
   1178             switch (mLayoutMode) {
   1179             case LAYOUT_SET_SELECTION:
   1180                 index = mNextSelectedPosition - mFirstPosition;
   1181                 if (index >= 0 && index < childCount) {
   1182                     newSel = getChildAt(index);
   1183                 }
   1184                 break;
   1185             case LAYOUT_FORCE_TOP:
   1186             case LAYOUT_FORCE_BOTTOM:
   1187             case LAYOUT_SPECIFIC:
   1188             case LAYOUT_SYNC:
   1189                 break;
   1190             case LAYOUT_MOVE_SELECTION:
   1191                 if (mNextSelectedPosition >= 0) {
   1192                     delta = mNextSelectedPosition - mSelectedPosition;
   1193                 }
   1194                 break;
   1195             default:
   1196                 // Remember the previously selected view
   1197                 index = mSelectedPosition - mFirstPosition;
   1198                 if (index >= 0 && index < childCount) {
   1199                     oldSel = getChildAt(index);
   1200                 }
   1201 
   1202                 // Remember the previous first child
   1203                 oldFirst = getChildAt(0);
   1204             }
   1205 
   1206             boolean dataChanged = mDataChanged;
   1207             if (dataChanged) {
   1208                 handleDataChanged();
   1209             }
   1210 
   1211             // Handle the empty set by removing all views that are visible
   1212             // and calling it a day
   1213             if (mItemCount == 0) {
   1214                 resetList();
   1215                 invokeOnItemScrollListener();
   1216                 return;
   1217             }
   1218 
   1219             setSelectedPositionInt(mNextSelectedPosition);
   1220 
   1221             AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
   1222             View accessibilityFocusLayoutRestoreView = null;
   1223             int accessibilityFocusPosition = INVALID_POSITION;
   1224 
   1225             // Remember which child, if any, had accessibility focus. This must
   1226             // occur before recycling any views, since that will clear
   1227             // accessibility focus.
   1228             final ViewRootImpl viewRootImpl = getViewRootImpl();
   1229             if (viewRootImpl != null) {
   1230                 final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
   1231                 if (focusHost != null) {
   1232                     final View focusChild = getAccessibilityFocusedChild(focusHost);
   1233                     if (focusChild != null) {
   1234                         if (!dataChanged || focusChild.hasTransientState()
   1235                                 || mAdapterHasStableIds) {
   1236                             // The views won't be changing, so try to maintain
   1237                             // focus on the current host and virtual view.
   1238                             accessibilityFocusLayoutRestoreView = focusHost;
   1239                             accessibilityFocusLayoutRestoreNode = viewRootImpl
   1240                                     .getAccessibilityFocusedVirtualView();
   1241                         }
   1242 
   1243                         // Try to maintain focus at the same position.
   1244                         accessibilityFocusPosition = getPositionForView(focusChild);
   1245                     }
   1246                 }
   1247             }
   1248 
   1249             // Pull all children into the RecycleBin.
   1250             // These views will be reused if possible
   1251             final int firstPosition = mFirstPosition;
   1252             final RecycleBin recycleBin = mRecycler;
   1253 
   1254             if (dataChanged) {
   1255                 for (int i = 0; i < childCount; i++) {
   1256                     recycleBin.addScrapView(getChildAt(i), firstPosition+i);
   1257                 }
   1258             } else {
   1259                 recycleBin.fillActiveViews(childCount, firstPosition);
   1260             }
   1261 
   1262             // Clear out old views
   1263             detachAllViewsFromParent();
   1264             recycleBin.removeSkippedScrap();
   1265 
   1266             switch (mLayoutMode) {
   1267             case LAYOUT_SET_SELECTION:
   1268                 if (newSel != null) {
   1269                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
   1270                 } else {
   1271                     sel = fillSelection(childrenTop, childrenBottom);
   1272                 }
   1273                 break;
   1274             case LAYOUT_FORCE_TOP:
   1275                 mFirstPosition = 0;
   1276                 sel = fillFromTop(childrenTop);
   1277                 adjustViewsUpOrDown();
   1278                 break;
   1279             case LAYOUT_FORCE_BOTTOM:
   1280                 sel = fillUp(mItemCount - 1, childrenBottom);
   1281                 adjustViewsUpOrDown();
   1282                 break;
   1283             case LAYOUT_SPECIFIC:
   1284                 sel = fillSpecific(mSelectedPosition, mSpecificTop);
   1285                 break;
   1286             case LAYOUT_SYNC:
   1287                 sel = fillSpecific(mSyncPosition, mSpecificTop);
   1288                 break;
   1289             case LAYOUT_MOVE_SELECTION:
   1290                 // Move the selection relative to its old position
   1291                 sel = moveSelection(delta, childrenTop, childrenBottom);
   1292                 break;
   1293             default:
   1294                 if (childCount == 0) {
   1295                     if (!mStackFromBottom) {
   1296                         setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
   1297                                 INVALID_POSITION : 0);
   1298                         sel = fillFromTop(childrenTop);
   1299                     } else {
   1300                         final int last = mItemCount - 1;
   1301                         setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
   1302                                 INVALID_POSITION : last);
   1303                         sel = fillFromBottom(last, childrenBottom);
   1304                     }
   1305                 } else {
   1306                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
   1307                         sel = fillSpecific(mSelectedPosition, oldSel == null ?
   1308                                 childrenTop : oldSel.getTop());
   1309                     } else if (mFirstPosition < mItemCount)  {
   1310                         sel = fillSpecific(mFirstPosition, oldFirst == null ?
   1311                                 childrenTop : oldFirst.getTop());
   1312                     } else {
   1313                         sel = fillSpecific(0, childrenTop);
   1314                     }
   1315                 }
   1316                 break;
   1317             }
   1318 
   1319             // Flush any cached views that did not get reused above
   1320             recycleBin.scrapActiveViews();
   1321 
   1322             if (sel != null) {
   1323                positionSelector(INVALID_POSITION, sel);
   1324                mSelectedTop = sel.getTop();
   1325             } else {
   1326                 final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
   1327                         && mTouchMode < TOUCH_MODE_SCROLL;
   1328                 if (inTouchMode) {
   1329                     // If the user's finger is down, select the motion position.
   1330                     final View child = getChildAt(mMotionPosition - mFirstPosition);
   1331                     if (child != null) {
   1332                         positionSelector(mMotionPosition, child);
   1333                     }
   1334                 } else if (mSelectedPosition != INVALID_POSITION) {
   1335                     // If we had previously positioned the selector somewhere,
   1336                     // put it back there. It might not match up with the data,
   1337                     // but it's transitioning out so it's not a big deal.
   1338                     final View child = getChildAt(mSelectorPosition - mFirstPosition);
   1339                     if (child != null) {
   1340                         positionSelector(mSelectorPosition, child);
   1341                     }
   1342                 } else {
   1343                     // Otherwise, clear selection.
   1344                     mSelectedTop = 0;
   1345                     mSelectorRect.setEmpty();
   1346                 }
   1347             }
   1348 
   1349             // Attempt to restore accessibility focus, if necessary.
   1350             if (viewRootImpl != null) {
   1351                 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
   1352                 if (newAccessibilityFocusedView == null) {
   1353                     if (accessibilityFocusLayoutRestoreView != null
   1354                             && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
   1355                         final AccessibilityNodeProvider provider =
   1356                                 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
   1357                         if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
   1358                             final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
   1359                                     accessibilityFocusLayoutRestoreNode.getSourceNodeId());
   1360                             provider.performAction(virtualViewId,
   1361                                     AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
   1362                         } else {
   1363                             accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
   1364                         }
   1365                     } else if (accessibilityFocusPosition != INVALID_POSITION) {
   1366                         // Bound the position within the visible children.
   1367                         final int position = MathUtils.constrain(
   1368                                 accessibilityFocusPosition - mFirstPosition, 0,
   1369                                 getChildCount() - 1);
   1370                         final View restoreView = getChildAt(position);
   1371                         if (restoreView != null) {
   1372                             restoreView.requestAccessibilityFocus();
   1373                         }
   1374                     }
   1375                 }
   1376             }
   1377 
   1378             mLayoutMode = LAYOUT_NORMAL;
   1379             mDataChanged = false;
   1380             if (mPositionScrollAfterLayout != null) {
   1381                 post(mPositionScrollAfterLayout);
   1382                 mPositionScrollAfterLayout = null;
   1383             }
   1384             mNeedSync = false;
   1385             setNextSelectedPositionInt(mSelectedPosition);
   1386 
   1387             updateScrollIndicators();
   1388 
   1389             if (mItemCount > 0) {
   1390                 checkSelectionChanged();
   1391             }
   1392 
   1393             invokeOnItemScrollListener();
   1394         } finally {
   1395             if (!blockLayoutRequests) {
   1396                 mBlockLayoutRequests = false;
   1397             }
   1398         }
   1399     }
   1400 
   1401 
   1402     /**
   1403      * Obtain the view and add it to our list of children. The view can be made
   1404      * fresh, converted from an unused view, or used as is if it was in the
   1405      * recycle bin.
   1406      *
   1407      * @param position Logical position in the list
   1408      * @param y Top or bottom edge of the view to add
   1409      * @param flow if true, align top edge to y. If false, align bottom edge to
   1410      *        y.
   1411      * @param childrenLeft Left edge where children should be positioned
   1412      * @param selected Is this position selected?
   1413      * @param where to add new item in the list
   1414      * @return View that was added
   1415      */
   1416     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
   1417             boolean selected, int where) {
   1418         View child;
   1419 
   1420         if (!mDataChanged) {
   1421             // Try to use an existing view for this position
   1422             child = mRecycler.getActiveView(position);
   1423             if (child != null) {
   1424                 // Found it -- we're using an existing child
   1425                 // This just needs to be positioned
   1426                 setupChild(child, position, y, flow, childrenLeft, selected, true, where);
   1427                 return child;
   1428             }
   1429         }
   1430 
   1431         // Make a new view for this position, or convert an unused view if
   1432         // possible
   1433         child = obtainView(position, mIsScrap);
   1434 
   1435         // This needs to be positioned and measured
   1436         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
   1437 
   1438         return child;
   1439     }
   1440 
   1441     /**
   1442      * Add a view as a child and make sure it is measured (if necessary) and
   1443      * positioned properly.
   1444      *
   1445      * @param child The view to add
   1446      * @param position The position of the view
   1447      * @param y The y position relative to which this view will be positioned
   1448      * @param flow if true, align top edge to y. If false, align bottom edge
   1449      *        to y.
   1450      * @param childrenLeft Left edge where children should be positioned
   1451      * @param selected Is this position selected?
   1452      * @param recycled Has this view been pulled from the recycle bin? If so it
   1453      *        does not need to be remeasured.
   1454      * @param where Where to add the item in the list
   1455      *
   1456      */
   1457     private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
   1458             boolean selected, boolean recycled, int where) {
   1459         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupGridItem");
   1460 
   1461         boolean isSelected = selected && shouldShowSelector();
   1462         final boolean updateChildSelected = isSelected != child.isSelected();
   1463         final int mode = mTouchMode;
   1464         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
   1465                 mMotionPosition == position;
   1466         final boolean updateChildPressed = isPressed != child.isPressed();
   1467 
   1468         boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
   1469 
   1470         // Respect layout params that are already in the view. Otherwise make
   1471         // some up...
   1472         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
   1473         if (p == null) {
   1474             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1475         }
   1476         p.viewType = mAdapter.getItemViewType(position);
   1477 
   1478         if (recycled && !p.forceAdd) {
   1479             attachViewToParent(child, where, p);
   1480         } else {
   1481             p.forceAdd = false;
   1482             addViewInLayout(child, where, p, true);
   1483         }
   1484 
   1485         if (updateChildSelected) {
   1486             child.setSelected(isSelected);
   1487             if (isSelected) {
   1488                 requestFocus();
   1489             }
   1490         }
   1491 
   1492         if (updateChildPressed) {
   1493             child.setPressed(isPressed);
   1494         }
   1495 
   1496         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
   1497             if (child instanceof Checkable) {
   1498                 ((Checkable) child).setChecked(mCheckStates.get(position));
   1499             } else if (getContext().getApplicationInfo().targetSdkVersion
   1500                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
   1501                 child.setActivated(mCheckStates.get(position));
   1502             }
   1503         }
   1504 
   1505         if (needToMeasure) {
   1506             int childHeightSpec = ViewGroup.getChildMeasureSpec(
   1507                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
   1508 
   1509             int childWidthSpec = ViewGroup.getChildMeasureSpec(
   1510                     MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
   1511             child.measure(childWidthSpec, childHeightSpec);
   1512         } else {
   1513             cleanupLayoutState(child);
   1514         }
   1515 
   1516         final int w = child.getMeasuredWidth();
   1517         final int h = child.getMeasuredHeight();
   1518 
   1519         int childLeft;
   1520         final int childTop = flow ? y : y - h;
   1521 
   1522         final int layoutDirection = getLayoutDirection();
   1523         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
   1524         switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   1525             case Gravity.LEFT:
   1526                 childLeft = childrenLeft;
   1527                 break;
   1528             case Gravity.CENTER_HORIZONTAL:
   1529                 childLeft = childrenLeft + ((mColumnWidth - w) / 2);
   1530                 break;
   1531             case Gravity.RIGHT:
   1532                 childLeft = childrenLeft + mColumnWidth - w;
   1533                 break;
   1534             default:
   1535                 childLeft = childrenLeft;
   1536                 break;
   1537         }
   1538 
   1539         if (needToMeasure) {
   1540             final int childRight = childLeft + w;
   1541             final int childBottom = childTop + h;
   1542             child.layout(childLeft, childTop, childRight, childBottom);
   1543         } else {
   1544             child.offsetLeftAndRight(childLeft - child.getLeft());
   1545             child.offsetTopAndBottom(childTop - child.getTop());
   1546         }
   1547 
   1548         if (mCachingStarted) {
   1549             child.setDrawingCacheEnabled(true);
   1550         }
   1551 
   1552         if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
   1553                 != position) {
   1554             child.jumpDrawablesToCurrentState();
   1555         }
   1556 
   1557         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   1558     }
   1559 
   1560     /**
   1561      * Sets the currently selected item
   1562      *
   1563      * @param position Index (starting at 0) of the data item to be selected.
   1564      *
   1565      * If in touch mode, the item will not be selected but it will still be positioned
   1566      * appropriately.
   1567      */
   1568     @Override
   1569     public void setSelection(int position) {
   1570         if (!isInTouchMode()) {
   1571             setNextSelectedPositionInt(position);
   1572         } else {
   1573             mResurrectToPosition = position;
   1574         }
   1575         mLayoutMode = LAYOUT_SET_SELECTION;
   1576         if (mPositionScroller != null) {
   1577             mPositionScroller.stop();
   1578         }
   1579         requestLayout();
   1580     }
   1581 
   1582     /**
   1583      * Makes the item at the supplied position selected.
   1584      *
   1585      * @param position the position of the new selection
   1586      */
   1587     @Override
   1588     void setSelectionInt(int position) {
   1589         int previousSelectedPosition = mNextSelectedPosition;
   1590 
   1591         if (mPositionScroller != null) {
   1592             mPositionScroller.stop();
   1593         }
   1594 
   1595         setNextSelectedPositionInt(position);
   1596         layoutChildren();
   1597 
   1598         final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
   1599             mNextSelectedPosition;
   1600         final int previous = mStackFromBottom ? mItemCount - 1
   1601                 - previousSelectedPosition : previousSelectedPosition;
   1602 
   1603         final int nextRow = next / mNumColumns;
   1604         final int previousRow = previous / mNumColumns;
   1605 
   1606         if (nextRow != previousRow) {
   1607             awakenScrollBars();
   1608         }
   1609 
   1610     }
   1611 
   1612     @Override
   1613     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1614         return commonKey(keyCode, 1, event);
   1615     }
   1616 
   1617     @Override
   1618     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   1619         return commonKey(keyCode, repeatCount, event);
   1620     }
   1621 
   1622     @Override
   1623     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1624         return commonKey(keyCode, 1, event);
   1625     }
   1626 
   1627     private boolean commonKey(int keyCode, int count, KeyEvent event) {
   1628         if (mAdapter == null) {
   1629             return false;
   1630         }
   1631 
   1632         if (mDataChanged) {
   1633             layoutChildren();
   1634         }
   1635 
   1636         boolean handled = false;
   1637         int action = event.getAction();
   1638 
   1639         if (action != KeyEvent.ACTION_UP) {
   1640             switch (keyCode) {
   1641                 case KeyEvent.KEYCODE_DPAD_LEFT:
   1642                     if (event.hasNoModifiers()) {
   1643                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
   1644                     }
   1645                     break;
   1646 
   1647                 case KeyEvent.KEYCODE_DPAD_RIGHT:
   1648                     if (event.hasNoModifiers()) {
   1649                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
   1650                     }
   1651                     break;
   1652 
   1653                 case KeyEvent.KEYCODE_DPAD_UP:
   1654                     if (event.hasNoModifiers()) {
   1655                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
   1656                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   1657                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   1658                     }
   1659                     break;
   1660 
   1661                 case KeyEvent.KEYCODE_DPAD_DOWN:
   1662                     if (event.hasNoModifiers()) {
   1663                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
   1664                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   1665                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   1666                     }
   1667                     break;
   1668 
   1669                 case KeyEvent.KEYCODE_DPAD_CENTER:
   1670                 case KeyEvent.KEYCODE_ENTER:
   1671                     if (event.hasNoModifiers()) {
   1672                         handled = resurrectSelectionIfNeeded();
   1673                         if (!handled
   1674                                 && event.getRepeatCount() == 0 && getChildCount() > 0) {
   1675                             keyPressed();
   1676                             handled = true;
   1677                         }
   1678                     }
   1679                     break;
   1680 
   1681                 case KeyEvent.KEYCODE_SPACE:
   1682                     if (mPopup == null || !mPopup.isShowing()) {
   1683                         if (event.hasNoModifiers()) {
   1684                             handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   1685                         } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   1686                             handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   1687                         }
   1688                     }
   1689                     break;
   1690 
   1691                 case KeyEvent.KEYCODE_PAGE_UP:
   1692                     if (event.hasNoModifiers()) {
   1693                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   1694                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   1695                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   1696                     }
   1697                     break;
   1698 
   1699                 case KeyEvent.KEYCODE_PAGE_DOWN:
   1700                     if (event.hasNoModifiers()) {
   1701                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   1702                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   1703                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   1704                     }
   1705                     break;
   1706 
   1707                 case KeyEvent.KEYCODE_MOVE_HOME:
   1708                     if (event.hasNoModifiers()) {
   1709                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   1710                     }
   1711                     break;
   1712 
   1713                 case KeyEvent.KEYCODE_MOVE_END:
   1714                     if (event.hasNoModifiers()) {
   1715                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   1716                     }
   1717                     break;
   1718 
   1719                 case KeyEvent.KEYCODE_TAB:
   1720                     // XXX Sometimes it is useful to be able to TAB through the items in
   1721                     //     a GridView sequentially.  Unfortunately this can create an
   1722                     //     asymmetry in TAB navigation order unless the list selection
   1723                     //     always reverts to the top or bottom when receiving TAB focus from
   1724                     //     another widget.  Leaving this behavior disabled for now but
   1725                     //     perhaps it should be configurable (and more comprehensive).
   1726                     if (false) {
   1727                         if (event.hasNoModifiers()) {
   1728                             handled = resurrectSelectionIfNeeded()
   1729                                     || sequenceScroll(FOCUS_FORWARD);
   1730                         } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   1731                             handled = resurrectSelectionIfNeeded()
   1732                                     || sequenceScroll(FOCUS_BACKWARD);
   1733                         }
   1734                     }
   1735                     break;
   1736             }
   1737         }
   1738 
   1739         if (handled) {
   1740             return true;
   1741         }
   1742 
   1743         if (sendToTextFilter(keyCode, count, event)) {
   1744             return true;
   1745         }
   1746 
   1747         switch (action) {
   1748             case KeyEvent.ACTION_DOWN:
   1749                 return super.onKeyDown(keyCode, event);
   1750             case KeyEvent.ACTION_UP:
   1751                 return super.onKeyUp(keyCode, event);
   1752             case KeyEvent.ACTION_MULTIPLE:
   1753                 return super.onKeyMultiple(keyCode, count, event);
   1754             default:
   1755                 return false;
   1756         }
   1757     }
   1758 
   1759     /**
   1760      * Scrolls up or down by the number of items currently present on screen.
   1761      *
   1762      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   1763      * @return whether selection was moved
   1764      */
   1765     boolean pageScroll(int direction) {
   1766         int nextPage = -1;
   1767 
   1768         if (direction == FOCUS_UP) {
   1769             nextPage = Math.max(0, mSelectedPosition - getChildCount());
   1770         } else if (direction == FOCUS_DOWN) {
   1771             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
   1772         }
   1773 
   1774         if (nextPage >= 0) {
   1775             setSelectionInt(nextPage);
   1776             invokeOnItemScrollListener();
   1777             awakenScrollBars();
   1778             return true;
   1779         }
   1780 
   1781         return false;
   1782     }
   1783 
   1784     /**
   1785      * Go to the last or first item if possible.
   1786      *
   1787      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
   1788      *
   1789      * @return Whether selection was moved.
   1790      */
   1791     boolean fullScroll(int direction) {
   1792         boolean moved = false;
   1793         if (direction == FOCUS_UP) {
   1794             mLayoutMode = LAYOUT_SET_SELECTION;
   1795             setSelectionInt(0);
   1796             invokeOnItemScrollListener();
   1797             moved = true;
   1798         } else if (direction == FOCUS_DOWN) {
   1799             mLayoutMode = LAYOUT_SET_SELECTION;
   1800             setSelectionInt(mItemCount - 1);
   1801             invokeOnItemScrollListener();
   1802             moved = true;
   1803         }
   1804 
   1805         if (moved) {
   1806             awakenScrollBars();
   1807         }
   1808 
   1809         return moved;
   1810     }
   1811 
   1812     /**
   1813      * Scrolls to the next or previous item, horizontally or vertically.
   1814      *
   1815      * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
   1816      *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   1817      *
   1818      * @return whether selection was moved
   1819      */
   1820     boolean arrowScroll(int direction) {
   1821         final int selectedPosition = mSelectedPosition;
   1822         final int numColumns = mNumColumns;
   1823 
   1824         int startOfRowPos;
   1825         int endOfRowPos;
   1826 
   1827         boolean moved = false;
   1828 
   1829         if (!mStackFromBottom) {
   1830             startOfRowPos = (selectedPosition / numColumns) * numColumns;
   1831             endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
   1832         } else {
   1833             final int invertedSelection = mItemCount - 1 - selectedPosition;
   1834             endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
   1835             startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
   1836         }
   1837 
   1838         switch (direction) {
   1839             case FOCUS_UP:
   1840                 if (startOfRowPos > 0) {
   1841                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1842                     setSelectionInt(Math.max(0, selectedPosition - numColumns));
   1843                     moved = true;
   1844                 }
   1845                 break;
   1846             case FOCUS_DOWN:
   1847                 if (endOfRowPos < mItemCount - 1) {
   1848                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1849                     setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
   1850                     moved = true;
   1851                 }
   1852                 break;
   1853             case FOCUS_LEFT:
   1854                 if (selectedPosition > startOfRowPos) {
   1855                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1856                     setSelectionInt(Math.max(0, selectedPosition - 1));
   1857                     moved = true;
   1858                 }
   1859                 break;
   1860             case FOCUS_RIGHT:
   1861                 if (selectedPosition < endOfRowPos) {
   1862                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1863                     setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
   1864                     moved = true;
   1865                 }
   1866                 break;
   1867         }
   1868 
   1869         if (moved) {
   1870             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
   1871             invokeOnItemScrollListener();
   1872         }
   1873 
   1874         if (moved) {
   1875             awakenScrollBars();
   1876         }
   1877 
   1878         return moved;
   1879     }
   1880 
   1881     /**
   1882      * Goes to the next or previous item according to the order set by the
   1883      * adapter.
   1884      */
   1885     boolean sequenceScroll(int direction) {
   1886         int selectedPosition = mSelectedPosition;
   1887         int numColumns = mNumColumns;
   1888         int count = mItemCount;
   1889 
   1890         int startOfRow;
   1891         int endOfRow;
   1892         if (!mStackFromBottom) {
   1893             startOfRow = (selectedPosition / numColumns) * numColumns;
   1894             endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
   1895         } else {
   1896             int invertedSelection = count - 1 - selectedPosition;
   1897             endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
   1898             startOfRow = Math.max(0, endOfRow - numColumns + 1);
   1899         }
   1900 
   1901         boolean moved = false;
   1902         boolean showScroll = false;
   1903         switch (direction) {
   1904             case FOCUS_FORWARD:
   1905                 if (selectedPosition < count - 1) {
   1906                     // Move to the next item.
   1907                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1908                     setSelectionInt(selectedPosition + 1);
   1909                     moved = true;
   1910                     // Show the scrollbar only if changing rows.
   1911                     showScroll = selectedPosition == endOfRow;
   1912                 }
   1913                 break;
   1914 
   1915             case FOCUS_BACKWARD:
   1916                 if (selectedPosition > 0) {
   1917                     // Move to the previous item.
   1918                     mLayoutMode = LAYOUT_MOVE_SELECTION;
   1919                     setSelectionInt(selectedPosition - 1);
   1920                     moved = true;
   1921                     // Show the scrollbar only if changing rows.
   1922                     showScroll = selectedPosition == startOfRow;
   1923                 }
   1924                 break;
   1925         }
   1926 
   1927         if (moved) {
   1928             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
   1929             invokeOnItemScrollListener();
   1930         }
   1931 
   1932         if (showScroll) {
   1933             awakenScrollBars();
   1934         }
   1935 
   1936         return moved;
   1937     }
   1938 
   1939     @Override
   1940     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   1941         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   1942 
   1943         int closestChildIndex = -1;
   1944         if (gainFocus && previouslyFocusedRect != null) {
   1945             previouslyFocusedRect.offset(mScrollX, mScrollY);
   1946 
   1947             // figure out which item should be selected based on previously
   1948             // focused rect
   1949             Rect otherRect = mTempRect;
   1950             int minDistance = Integer.MAX_VALUE;
   1951             final int childCount = getChildCount();
   1952             for (int i = 0; i < childCount; i++) {
   1953                 // only consider view's on appropriate edge of grid
   1954                 if (!isCandidateSelection(i, direction)) {
   1955                     continue;
   1956                 }
   1957 
   1958                 final View other = getChildAt(i);
   1959                 other.getDrawingRect(otherRect);
   1960                 offsetDescendantRectToMyCoords(other, otherRect);
   1961                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
   1962 
   1963                 if (distance < minDistance) {
   1964                     minDistance = distance;
   1965                     closestChildIndex = i;
   1966                 }
   1967             }
   1968         }
   1969 
   1970         if (closestChildIndex >= 0) {
   1971             setSelection(closestChildIndex + mFirstPosition);
   1972         } else {
   1973             requestLayout();
   1974         }
   1975     }
   1976 
   1977     /**
   1978      * Is childIndex a candidate for next focus given the direction the focus
   1979      * change is coming from?
   1980      * @param childIndex The index to check.
   1981      * @param direction The direction, one of
   1982      *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
   1983      * @return Whether childIndex is a candidate.
   1984      */
   1985     private boolean isCandidateSelection(int childIndex, int direction) {
   1986         final int count = getChildCount();
   1987         final int invertedIndex = count - 1 - childIndex;
   1988 
   1989         int rowStart;
   1990         int rowEnd;
   1991 
   1992         if (!mStackFromBottom) {
   1993             rowStart = childIndex - (childIndex % mNumColumns);
   1994             rowEnd = Math.max(rowStart + mNumColumns - 1, count);
   1995         } else {
   1996             rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
   1997             rowStart = Math.max(0, rowEnd - mNumColumns + 1);
   1998         }
   1999 
   2000         switch (direction) {
   2001             case View.FOCUS_RIGHT:
   2002                 // coming from left, selection is only valid if it is on left
   2003                 // edge
   2004                 return childIndex == rowStart;
   2005             case View.FOCUS_DOWN:
   2006                 // coming from top; only valid if in top row
   2007                 return rowStart == 0;
   2008             case View.FOCUS_LEFT:
   2009                 // coming from right, must be on right edge
   2010                 return childIndex == rowEnd;
   2011             case View.FOCUS_UP:
   2012                 // coming from bottom, need to be in last row
   2013                 return rowEnd == count - 1;
   2014             case View.FOCUS_FORWARD:
   2015                 // coming from top-left, need to be first in top row
   2016                 return childIndex == rowStart && rowStart == 0;
   2017             case View.FOCUS_BACKWARD:
   2018                 // coming from bottom-right, need to be last in bottom row
   2019                 return childIndex == rowEnd && rowEnd == count - 1;
   2020             default:
   2021                 throw new IllegalArgumentException("direction must be one of "
   2022                         + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
   2023                         + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
   2024         }
   2025     }
   2026 
   2027     /**
   2028      * Set the gravity for this grid. Gravity describes how the child views
   2029      * are horizontally aligned. Defaults to Gravity.LEFT
   2030      *
   2031      * @param gravity the gravity to apply to this grid's children
   2032      *
   2033      * @attr ref android.R.styleable#GridView_gravity
   2034      */
   2035     public void setGravity(int gravity) {
   2036         if (mGravity != gravity) {
   2037             mGravity = gravity;
   2038             requestLayoutIfNecessary();
   2039         }
   2040     }
   2041 
   2042     /**
   2043      * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
   2044      *
   2045      * @return the gravity that will be applied to this grid's children
   2046      *
   2047      * @attr ref android.R.styleable#GridView_gravity
   2048      */
   2049     public int getGravity() {
   2050         return mGravity;
   2051     }
   2052 
   2053     /**
   2054      * Set the amount of horizontal (x) spacing to place between each item
   2055      * in the grid.
   2056      *
   2057      * @param horizontalSpacing The amount of horizontal space between items,
   2058      * in pixels.
   2059      *
   2060      * @attr ref android.R.styleable#GridView_horizontalSpacing
   2061      */
   2062     public void setHorizontalSpacing(int horizontalSpacing) {
   2063         if (horizontalSpacing != mRequestedHorizontalSpacing) {
   2064             mRequestedHorizontalSpacing = horizontalSpacing;
   2065             requestLayoutIfNecessary();
   2066         }
   2067     }
   2068 
   2069     /**
   2070      * Returns the amount of horizontal spacing currently used between each item in the grid.
   2071      *
   2072      * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
   2073      * has been called but layout is not yet complete, this method may return a stale value.
   2074      * To get the horizontal spacing that was explicitly requested use
   2075      * {@link #getRequestedHorizontalSpacing()}.</p>
   2076      *
   2077      * @return Current horizontal spacing between each item in pixels
   2078      *
   2079      * @see #setHorizontalSpacing(int)
   2080      * @see #getRequestedHorizontalSpacing()
   2081      *
   2082      * @attr ref android.R.styleable#GridView_horizontalSpacing
   2083      */
   2084     public int getHorizontalSpacing() {
   2085         return mHorizontalSpacing;
   2086     }
   2087 
   2088     /**
   2089      * Returns the requested amount of horizontal spacing between each item in the grid.
   2090      *
   2091      * <p>The value returned may have been supplied during inflation as part of a style,
   2092      * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
   2093      * If layout is not yet complete or if GridView calculated a different horizontal spacing
   2094      * from what was requested, this may return a different value from
   2095      * {@link #getHorizontalSpacing()}.</p>
   2096      *
   2097      * @return The currently requested horizontal spacing between items, in pixels
   2098      *
   2099      * @see #setHorizontalSpacing(int)
   2100      * @see #getHorizontalSpacing()
   2101      *
   2102      * @attr ref android.R.styleable#GridView_horizontalSpacing
   2103      */
   2104     public int getRequestedHorizontalSpacing() {
   2105         return mRequestedHorizontalSpacing;
   2106     }
   2107 
   2108     /**
   2109      * Set the amount of vertical (y) spacing to place between each item
   2110      * in the grid.
   2111      *
   2112      * @param verticalSpacing The amount of vertical space between items,
   2113      * in pixels.
   2114      *
   2115      * @see #getVerticalSpacing()
   2116      *
   2117      * @attr ref android.R.styleable#GridView_verticalSpacing
   2118      */
   2119     public void setVerticalSpacing(int verticalSpacing) {
   2120         if (verticalSpacing != mVerticalSpacing) {
   2121             mVerticalSpacing = verticalSpacing;
   2122             requestLayoutIfNecessary();
   2123         }
   2124     }
   2125 
   2126     /**
   2127      * Returns the amount of vertical spacing between each item in the grid.
   2128      *
   2129      * @return The vertical spacing between items in pixels
   2130      *
   2131      * @see #setVerticalSpacing(int)
   2132      *
   2133      * @attr ref android.R.styleable#GridView_verticalSpacing
   2134      */
   2135     public int getVerticalSpacing() {
   2136         return mVerticalSpacing;
   2137     }
   2138 
   2139     /**
   2140      * Control how items are stretched to fill their space.
   2141      *
   2142      * @param stretchMode Either {@link #NO_STRETCH},
   2143      * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
   2144      *
   2145      * @attr ref android.R.styleable#GridView_stretchMode
   2146      */
   2147     public void setStretchMode(@StretchMode int stretchMode) {
   2148         if (stretchMode != mStretchMode) {
   2149             mStretchMode = stretchMode;
   2150             requestLayoutIfNecessary();
   2151         }
   2152     }
   2153 
   2154     @StretchMode
   2155     public int getStretchMode() {
   2156         return mStretchMode;
   2157     }
   2158 
   2159     /**
   2160      * Set the width of columns in the grid.
   2161      *
   2162      * @param columnWidth The column width, in pixels.
   2163      *
   2164      * @attr ref android.R.styleable#GridView_columnWidth
   2165      */
   2166     public void setColumnWidth(int columnWidth) {
   2167         if (columnWidth != mRequestedColumnWidth) {
   2168             mRequestedColumnWidth = columnWidth;
   2169             requestLayoutIfNecessary();
   2170         }
   2171     }
   2172 
   2173     /**
   2174      * Return the width of a column in the grid.
   2175      *
   2176      * <p>This may not be valid yet if a layout is pending.</p>
   2177      *
   2178      * @return The column width in pixels
   2179      *
   2180      * @see #setColumnWidth(int)
   2181      * @see #getRequestedColumnWidth()
   2182      *
   2183      * @attr ref android.R.styleable#GridView_columnWidth
   2184      */
   2185     public int getColumnWidth() {
   2186         return mColumnWidth;
   2187     }
   2188 
   2189     /**
   2190      * Return the requested width of a column in the grid.
   2191      *
   2192      * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
   2193      * to retrieve the current real width of a column.</p>
   2194      *
   2195      * @return The requested column width in pixels
   2196      *
   2197      * @see #setColumnWidth(int)
   2198      * @see #getColumnWidth()
   2199      *
   2200      * @attr ref android.R.styleable#GridView_columnWidth
   2201      */
   2202     public int getRequestedColumnWidth() {
   2203         return mRequestedColumnWidth;
   2204     }
   2205 
   2206     /**
   2207      * Set the number of columns in the grid
   2208      *
   2209      * @param numColumns The desired number of columns.
   2210      *
   2211      * @attr ref android.R.styleable#GridView_numColumns
   2212      */
   2213     public void setNumColumns(int numColumns) {
   2214         if (numColumns != mRequestedNumColumns) {
   2215             mRequestedNumColumns = numColumns;
   2216             requestLayoutIfNecessary();
   2217         }
   2218     }
   2219 
   2220     /**
   2221      * Get the number of columns in the grid.
   2222      * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
   2223      *
   2224      * @attr ref android.R.styleable#GridView_numColumns
   2225      *
   2226      * @see #setNumColumns(int)
   2227      */
   2228     @ViewDebug.ExportedProperty
   2229     public int getNumColumns() {
   2230         return mNumColumns;
   2231     }
   2232 
   2233     /**
   2234      * Make sure views are touching the top or bottom edge, as appropriate for
   2235      * our gravity
   2236      */
   2237     private void adjustViewsUpOrDown() {
   2238         final int childCount = getChildCount();
   2239 
   2240         if (childCount > 0) {
   2241             int delta;
   2242             View child;
   2243 
   2244             if (!mStackFromBottom) {
   2245                 // Uh-oh -- we came up short. Slide all views up to make them
   2246                 // align with the top
   2247                 child = getChildAt(0);
   2248                 delta = child.getTop() - mListPadding.top;
   2249                 if (mFirstPosition != 0) {
   2250                     // It's OK to have some space above the first item if it is
   2251                     // part of the vertical spacing
   2252                     delta -= mVerticalSpacing;
   2253                 }
   2254                 if (delta < 0) {
   2255                     // We only are looking to see if we are too low, not too high
   2256                     delta = 0;
   2257                 }
   2258             } else {
   2259                 // we are too high, slide all views down to align with bottom
   2260                 child = getChildAt(childCount - 1);
   2261                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
   2262 
   2263                 if (mFirstPosition + childCount < mItemCount) {
   2264                     // It's OK to have some space below the last item if it is
   2265                     // part of the vertical spacing
   2266                     delta += mVerticalSpacing;
   2267                 }
   2268 
   2269                 if (delta > 0) {
   2270                     // We only are looking to see if we are too high, not too low
   2271                     delta = 0;
   2272                 }
   2273             }
   2274 
   2275             if (delta != 0) {
   2276                 offsetChildrenTopAndBottom(-delta);
   2277             }
   2278         }
   2279     }
   2280 
   2281     @Override
   2282     protected int computeVerticalScrollExtent() {
   2283         final int count = getChildCount();
   2284         if (count > 0) {
   2285             final int numColumns = mNumColumns;
   2286             final int rowCount = (count + numColumns - 1) / numColumns;
   2287 
   2288             int extent = rowCount * 100;
   2289 
   2290             View view = getChildAt(0);
   2291             final int top = view.getTop();
   2292             int height = view.getHeight();
   2293             if (height > 0) {
   2294                 extent += (top * 100) / height;
   2295             }
   2296 
   2297             view = getChildAt(count - 1);
   2298             final int bottom = view.getBottom();
   2299             height = view.getHeight();
   2300             if (height > 0) {
   2301                 extent -= ((bottom - getHeight()) * 100) / height;
   2302             }
   2303 
   2304             return extent;
   2305         }
   2306         return 0;
   2307     }
   2308 
   2309     @Override
   2310     protected int computeVerticalScrollOffset() {
   2311         if (mFirstPosition >= 0 && getChildCount() > 0) {
   2312             final View view = getChildAt(0);
   2313             final int top = view.getTop();
   2314             int height = view.getHeight();
   2315             if (height > 0) {
   2316                 final int numColumns = mNumColumns;
   2317                 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
   2318                 // In case of stackFromBottom the calculation of whichRow needs
   2319                 // to take into account that counting from the top the first row
   2320                 // might not be entirely filled.
   2321                 final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
   2322                         mItemCount) : 0;
   2323                 final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
   2324                 return Math.max(whichRow * 100 - (top * 100) / height +
   2325                         (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
   2326             }
   2327         }
   2328         return 0;
   2329     }
   2330 
   2331     @Override
   2332     protected int computeVerticalScrollRange() {
   2333         // TODO: Account for vertical spacing too
   2334         final int numColumns = mNumColumns;
   2335         final int rowCount = (mItemCount + numColumns - 1) / numColumns;
   2336         int result = Math.max(rowCount * 100, 0);
   2337         if (mScrollY != 0) {
   2338             // Compensate for overscroll
   2339             result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
   2340         }
   2341         return result;
   2342     }
   2343 
   2344     @Override
   2345     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   2346         super.onInitializeAccessibilityEvent(event);
   2347         event.setClassName(GridView.class.getName());
   2348     }
   2349 
   2350     @Override
   2351     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   2352         super.onInitializeAccessibilityNodeInfo(info);
   2353         info.setClassName(GridView.class.getName());
   2354 
   2355         final int columnsCount = getNumColumns();
   2356         final int rowsCount = getCount() / columnsCount;
   2357         final int selectionMode = getSelectionModeForAccessibility();
   2358         final CollectionInfo collectionInfo = CollectionInfo.obtain(
   2359                 rowsCount, columnsCount, false, selectionMode);
   2360         info.setCollectionInfo(collectionInfo);
   2361     }
   2362 
   2363     @Override
   2364     public void onInitializeAccessibilityNodeInfoForItem(
   2365             View view, int position, AccessibilityNodeInfo info) {
   2366         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
   2367 
   2368         final int count = getCount();
   2369         final int columnsCount = getNumColumns();
   2370         final int rowsCount = count / columnsCount;
   2371 
   2372         final int row;
   2373         final int column;
   2374         if (!mStackFromBottom) {
   2375             column = position % columnsCount;
   2376             row = position / columnsCount;
   2377         } else {
   2378             final int invertedIndex = count - 1 - position;
   2379 
   2380             column = columnsCount - 1 - (invertedIndex % columnsCount);
   2381             row = rowsCount - 1 - invertedIndex / columnsCount;
   2382         }
   2383 
   2384         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2385         final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
   2386         final boolean isSelected = isItemChecked(position);
   2387         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
   2388                 row, 1, column, 1, isHeading, isSelected);
   2389         info.setCollectionItemInfo(itemInfo);
   2390     }
   2391 }
   2392