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