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