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