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