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