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