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