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