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