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