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