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