GridView.java revision 1fa179ef41c06bd19da70fd9bf95741238791940
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        this(context, null);
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        if (mPositionScroller != null) {
1467            mPositionScroller.stop();
1468        }
1469        requestLayout();
1470    }
1471
1472    /**
1473     * Makes the item at the supplied position selected.
1474     *
1475     * @param position the position of the new selection
1476     */
1477    @Override
1478    void setSelectionInt(int position) {
1479        int previousSelectedPosition = mNextSelectedPosition;
1480
1481        if (mPositionScroller != null) {
1482            mPositionScroller.stop();
1483        }
1484
1485        setNextSelectedPositionInt(position);
1486        layoutChildren();
1487
1488        final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
1489            mNextSelectedPosition;
1490        final int previous = mStackFromBottom ? mItemCount - 1
1491                - previousSelectedPosition : previousSelectedPosition;
1492
1493        final int nextRow = next / mNumColumns;
1494        final int previousRow = previous / mNumColumns;
1495
1496        if (nextRow != previousRow) {
1497            awakenScrollBars();
1498        }
1499
1500    }
1501
1502    @Override
1503    public boolean onKeyDown(int keyCode, KeyEvent event) {
1504        return commonKey(keyCode, 1, event);
1505    }
1506
1507    @Override
1508    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1509        return commonKey(keyCode, repeatCount, event);
1510    }
1511
1512    @Override
1513    public boolean onKeyUp(int keyCode, KeyEvent event) {
1514        return commonKey(keyCode, 1, event);
1515    }
1516
1517    private boolean commonKey(int keyCode, int count, KeyEvent event) {
1518        if (mAdapter == null) {
1519            return false;
1520        }
1521
1522        if (mDataChanged) {
1523            layoutChildren();
1524        }
1525
1526        boolean handled = false;
1527        int action = event.getAction();
1528
1529        if (action != KeyEvent.ACTION_UP) {
1530            switch (keyCode) {
1531                case KeyEvent.KEYCODE_DPAD_LEFT:
1532                    if (event.hasNoModifiers()) {
1533                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
1534                    }
1535                    break;
1536
1537                case KeyEvent.KEYCODE_DPAD_RIGHT:
1538                    if (event.hasNoModifiers()) {
1539                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
1540                    }
1541                    break;
1542
1543                case KeyEvent.KEYCODE_DPAD_UP:
1544                    if (event.hasNoModifiers()) {
1545                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
1546                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1547                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1548                    }
1549                    break;
1550
1551                case KeyEvent.KEYCODE_DPAD_DOWN:
1552                    if (event.hasNoModifiers()) {
1553                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
1554                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1555                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1556                    }
1557                    break;
1558
1559                case KeyEvent.KEYCODE_DPAD_CENTER:
1560                case KeyEvent.KEYCODE_ENTER:
1561                    if (event.hasNoModifiers()) {
1562                        handled = resurrectSelectionIfNeeded();
1563                        if (!handled
1564                                && event.getRepeatCount() == 0 && getChildCount() > 0) {
1565                            keyPressed();
1566                            handled = true;
1567                        }
1568                    }
1569                    break;
1570
1571                case KeyEvent.KEYCODE_SPACE:
1572                    if (mPopup == null || !mPopup.isShowing()) {
1573                        if (event.hasNoModifiers()) {
1574                            handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
1575                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1576                            handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
1577                        }
1578                    }
1579                    break;
1580
1581                case KeyEvent.KEYCODE_PAGE_UP:
1582                    if (event.hasNoModifiers()) {
1583                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
1584                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1585                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1586                    }
1587                    break;
1588
1589                case KeyEvent.KEYCODE_PAGE_DOWN:
1590                    if (event.hasNoModifiers()) {
1591                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
1592                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1593                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1594                    }
1595                    break;
1596
1597                case KeyEvent.KEYCODE_MOVE_HOME:
1598                    if (event.hasNoModifiers()) {
1599                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1600                    }
1601                    break;
1602
1603                case KeyEvent.KEYCODE_MOVE_END:
1604                    if (event.hasNoModifiers()) {
1605                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1606                    }
1607                    break;
1608
1609                case KeyEvent.KEYCODE_TAB:
1610                    // XXX Sometimes it is useful to be able to TAB through the items in
1611                    //     a GridView sequentially.  Unfortunately this can create an
1612                    //     asymmetry in TAB navigation order unless the list selection
1613                    //     always reverts to the top or bottom when receiving TAB focus from
1614                    //     another widget.  Leaving this behavior disabled for now but
1615                    //     perhaps it should be configurable (and more comprehensive).
1616                    if (false) {
1617                        if (event.hasNoModifiers()) {
1618                            handled = resurrectSelectionIfNeeded()
1619                                    || sequenceScroll(FOCUS_FORWARD);
1620                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1621                            handled = resurrectSelectionIfNeeded()
1622                                    || sequenceScroll(FOCUS_BACKWARD);
1623                        }
1624                    }
1625                    break;
1626            }
1627        }
1628
1629        if (handled) {
1630            return true;
1631        }
1632
1633        if (sendToTextFilter(keyCode, count, event)) {
1634            return true;
1635        }
1636
1637        switch (action) {
1638            case KeyEvent.ACTION_DOWN:
1639                return super.onKeyDown(keyCode, event);
1640            case KeyEvent.ACTION_UP:
1641                return super.onKeyUp(keyCode, event);
1642            case KeyEvent.ACTION_MULTIPLE:
1643                return super.onKeyMultiple(keyCode, count, event);
1644            default:
1645                return false;
1646        }
1647    }
1648
1649    /**
1650     * Scrolls up or down by the number of items currently present on screen.
1651     *
1652     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1653     * @return whether selection was moved
1654     */
1655    boolean pageScroll(int direction) {
1656        int nextPage = -1;
1657
1658        if (direction == FOCUS_UP) {
1659            nextPage = Math.max(0, mSelectedPosition - getChildCount());
1660        } else if (direction == FOCUS_DOWN) {
1661            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
1662        }
1663
1664        if (nextPage >= 0) {
1665            setSelectionInt(nextPage);
1666            invokeOnItemScrollListener();
1667            awakenScrollBars();
1668            return true;
1669        }
1670
1671        return false;
1672    }
1673
1674    /**
1675     * Go to the last or first item if possible.
1676     *
1677     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1678     *
1679     * @return Whether selection was moved.
1680     */
1681    boolean fullScroll(int direction) {
1682        boolean moved = false;
1683        if (direction == FOCUS_UP) {
1684            mLayoutMode = LAYOUT_SET_SELECTION;
1685            setSelectionInt(0);
1686            invokeOnItemScrollListener();
1687            moved = true;
1688        } else if (direction == FOCUS_DOWN) {
1689            mLayoutMode = LAYOUT_SET_SELECTION;
1690            setSelectionInt(mItemCount - 1);
1691            invokeOnItemScrollListener();
1692            moved = true;
1693        }
1694
1695        if (moved) {
1696            awakenScrollBars();
1697        }
1698
1699        return moved;
1700    }
1701
1702    /**
1703     * Scrolls to the next or previous item, horizontally or vertically.
1704     *
1705     * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1706     *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1707     *
1708     * @return whether selection was moved
1709     */
1710    boolean arrowScroll(int direction) {
1711        final int selectedPosition = mSelectedPosition;
1712        final int numColumns = mNumColumns;
1713
1714        int startOfRowPos;
1715        int endOfRowPos;
1716
1717        boolean moved = false;
1718
1719        if (!mStackFromBottom) {
1720            startOfRowPos = (selectedPosition / numColumns) * numColumns;
1721            endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1722        } else {
1723            final int invertedSelection = mItemCount - 1 - selectedPosition;
1724            endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1725            startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1726        }
1727
1728        switch (direction) {
1729            case FOCUS_UP:
1730                if (startOfRowPos > 0) {
1731                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1732                    setSelectionInt(Math.max(0, selectedPosition - numColumns));
1733                    moved = true;
1734                }
1735                break;
1736            case FOCUS_DOWN:
1737                if (endOfRowPos < mItemCount - 1) {
1738                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1739                    setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1740                    moved = true;
1741                }
1742                break;
1743            case FOCUS_LEFT:
1744                if (selectedPosition > startOfRowPos) {
1745                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1746                    setSelectionInt(Math.max(0, selectedPosition - 1));
1747                    moved = true;
1748                }
1749                break;
1750            case FOCUS_RIGHT:
1751                if (selectedPosition < endOfRowPos) {
1752                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1753                    setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
1754                    moved = true;
1755                }
1756                break;
1757        }
1758
1759        if (moved) {
1760            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1761            invokeOnItemScrollListener();
1762        }
1763
1764        if (moved) {
1765            awakenScrollBars();
1766        }
1767
1768        return moved;
1769    }
1770
1771    /**
1772     * Goes to the next or previous item according to the order set by the
1773     * adapter.
1774     */
1775    boolean sequenceScroll(int direction) {
1776        int selectedPosition = mSelectedPosition;
1777        int numColumns = mNumColumns;
1778        int count = mItemCount;
1779
1780        int startOfRow;
1781        int endOfRow;
1782        if (!mStackFromBottom) {
1783            startOfRow = (selectedPosition / numColumns) * numColumns;
1784            endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1785        } else {
1786            int invertedSelection = count - 1 - selectedPosition;
1787            endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1788            startOfRow = Math.max(0, endOfRow - numColumns + 1);
1789        }
1790
1791        boolean moved = false;
1792        boolean showScroll = false;
1793        switch (direction) {
1794            case FOCUS_FORWARD:
1795                if (selectedPosition < count - 1) {
1796                    // Move to the next item.
1797                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1798                    setSelectionInt(selectedPosition + 1);
1799                    moved = true;
1800                    // Show the scrollbar only if changing rows.
1801                    showScroll = selectedPosition == endOfRow;
1802                }
1803                break;
1804
1805            case FOCUS_BACKWARD:
1806                if (selectedPosition > 0) {
1807                    // Move to the previous item.
1808                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1809                    setSelectionInt(selectedPosition - 1);
1810                    moved = true;
1811                    // Show the scrollbar only if changing rows.
1812                    showScroll = selectedPosition == startOfRow;
1813                }
1814                break;
1815        }
1816
1817        if (moved) {
1818            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1819            invokeOnItemScrollListener();
1820        }
1821
1822        if (showScroll) {
1823            awakenScrollBars();
1824        }
1825
1826        return moved;
1827    }
1828
1829    @Override
1830    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1831        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1832
1833        int closestChildIndex = -1;
1834        if (gainFocus && previouslyFocusedRect != null) {
1835            previouslyFocusedRect.offset(mScrollX, mScrollY);
1836
1837            // figure out which item should be selected based on previously
1838            // focused rect
1839            Rect otherRect = mTempRect;
1840            int minDistance = Integer.MAX_VALUE;
1841            final int childCount = getChildCount();
1842            for (int i = 0; i < childCount; i++) {
1843                // only consider view's on appropriate edge of grid
1844                if (!isCandidateSelection(i, direction)) {
1845                    continue;
1846                }
1847
1848                final View other = getChildAt(i);
1849                other.getDrawingRect(otherRect);
1850                offsetDescendantRectToMyCoords(other, otherRect);
1851                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1852
1853                if (distance < minDistance) {
1854                    minDistance = distance;
1855                    closestChildIndex = i;
1856                }
1857            }
1858        }
1859
1860        if (closestChildIndex >= 0) {
1861            setSelection(closestChildIndex + mFirstPosition);
1862        } else {
1863            requestLayout();
1864        }
1865    }
1866
1867    /**
1868     * Is childIndex a candidate for next focus given the direction the focus
1869     * change is coming from?
1870     * @param childIndex The index to check.
1871     * @param direction The direction, one of
1872     *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
1873     * @return Whether childIndex is a candidate.
1874     */
1875    private boolean isCandidateSelection(int childIndex, int direction) {
1876        final int count = getChildCount();
1877        final int invertedIndex = count - 1 - childIndex;
1878
1879        int rowStart;
1880        int rowEnd;
1881
1882        if (!mStackFromBottom) {
1883            rowStart = childIndex - (childIndex % mNumColumns);
1884            rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1885        } else {
1886            rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1887            rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1888        }
1889
1890        switch (direction) {
1891            case View.FOCUS_RIGHT:
1892                // coming from left, selection is only valid if it is on left
1893                // edge
1894                return childIndex == rowStart;
1895            case View.FOCUS_DOWN:
1896                // coming from top; only valid if in top row
1897                return rowStart == 0;
1898            case View.FOCUS_LEFT:
1899                // coming from right, must be on right edge
1900                return childIndex == rowEnd;
1901            case View.FOCUS_UP:
1902                // coming from bottom, need to be in last row
1903                return rowEnd == count - 1;
1904            case View.FOCUS_FORWARD:
1905                // coming from top-left, need to be first in top row
1906                return childIndex == rowStart && rowStart == 0;
1907            case View.FOCUS_BACKWARD:
1908                // coming from bottom-right, need to be last in bottom row
1909                return childIndex == rowEnd && rowEnd == count - 1;
1910            default:
1911                throw new IllegalArgumentException("direction must be one of "
1912                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
1913                        + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
1914        }
1915    }
1916
1917    /**
1918     * Set the gravity for this grid. Gravity describes how the child views
1919     * are horizontally aligned. Defaults to Gravity.LEFT
1920     *
1921     * @param gravity the gravity to apply to this grid's children
1922     *
1923     * @attr ref android.R.styleable#GridView_gravity
1924     */
1925    public void setGravity(int gravity) {
1926        if (mGravity != gravity) {
1927            mGravity = gravity;
1928            requestLayoutIfNecessary();
1929        }
1930    }
1931
1932    /**
1933     * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
1934     *
1935     * @return the gravity that will be applied to this grid's children
1936     *
1937     * @attr ref android.R.styleable#GridView_gravity
1938     */
1939    public int getGravity() {
1940        return mGravity;
1941    }
1942
1943    /**
1944     * Set the amount of horizontal (x) spacing to place between each item
1945     * in the grid.
1946     *
1947     * @param horizontalSpacing The amount of horizontal space between items,
1948     * in pixels.
1949     *
1950     * @attr ref android.R.styleable#GridView_horizontalSpacing
1951     */
1952    public void setHorizontalSpacing(int horizontalSpacing) {
1953        if (horizontalSpacing != mRequestedHorizontalSpacing) {
1954            mRequestedHorizontalSpacing = horizontalSpacing;
1955            requestLayoutIfNecessary();
1956        }
1957    }
1958
1959    /**
1960     * Returns the amount of horizontal spacing currently used between each item in the grid.
1961     *
1962     * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
1963     * has been called but layout is not yet complete, this method may return a stale value.
1964     * To get the horizontal spacing that was explicitly requested use
1965     * {@link #getRequestedHorizontalSpacing()}.</p>
1966     *
1967     * @return Current horizontal spacing between each item in pixels
1968     *
1969     * @see #setHorizontalSpacing(int)
1970     * @see #getRequestedHorizontalSpacing()
1971     *
1972     * @attr ref android.R.styleable#GridView_horizontalSpacing
1973     */
1974    public int getHorizontalSpacing() {
1975        return mHorizontalSpacing;
1976    }
1977
1978    /**
1979     * Returns the requested amount of horizontal spacing between each item in the grid.
1980     *
1981     * <p>The value returned may have been supplied during inflation as part of a style,
1982     * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
1983     * If layout is not yet complete or if GridView calculated a different horizontal spacing
1984     * from what was requested, this may return a different value from
1985     * {@link #getHorizontalSpacing()}.</p>
1986     *
1987     * @return The currently requested horizontal spacing between items, in pixels
1988     *
1989     * @see #setHorizontalSpacing(int)
1990     * @see #getHorizontalSpacing()
1991     *
1992     * @attr ref android.R.styleable#GridView_horizontalSpacing
1993     */
1994    public int getRequestedHorizontalSpacing() {
1995        return mRequestedHorizontalSpacing;
1996    }
1997
1998    /**
1999     * Set the amount of vertical (y) spacing to place between each item
2000     * in the grid.
2001     *
2002     * @param verticalSpacing The amount of vertical space between items,
2003     * in pixels.
2004     *
2005     * @see #getVerticalSpacing()
2006     *
2007     * @attr ref android.R.styleable#GridView_verticalSpacing
2008     */
2009    public void setVerticalSpacing(int verticalSpacing) {
2010        if (verticalSpacing != mVerticalSpacing) {
2011            mVerticalSpacing = verticalSpacing;
2012            requestLayoutIfNecessary();
2013        }
2014    }
2015
2016    /**
2017     * Returns the amount of vertical spacing between each item in the grid.
2018     *
2019     * @return The vertical spacing between items in pixels
2020     *
2021     * @see #setVerticalSpacing(int)
2022     *
2023     * @attr ref android.R.styleable#GridView_verticalSpacing
2024     */
2025    public int getVerticalSpacing() {
2026        return mVerticalSpacing;
2027    }
2028
2029    /**
2030     * Control how items are stretched to fill their space.
2031     *
2032     * @param stretchMode Either {@link #NO_STRETCH},
2033     * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2034     *
2035     * @attr ref android.R.styleable#GridView_stretchMode
2036     */
2037    public void setStretchMode(int stretchMode) {
2038        if (stretchMode != mStretchMode) {
2039            mStretchMode = stretchMode;
2040            requestLayoutIfNecessary();
2041        }
2042    }
2043
2044    public int getStretchMode() {
2045        return mStretchMode;
2046    }
2047
2048    /**
2049     * Set the width of columns in the grid.
2050     *
2051     * @param columnWidth The column width, in pixels.
2052     *
2053     * @attr ref android.R.styleable#GridView_columnWidth
2054     */
2055    public void setColumnWidth(int columnWidth) {
2056        if (columnWidth != mRequestedColumnWidth) {
2057            mRequestedColumnWidth = columnWidth;
2058            requestLayoutIfNecessary();
2059        }
2060    }
2061
2062    /**
2063     * Return the width of a column in the grid.
2064     *
2065     * <p>This may not be valid yet if a layout is pending.</p>
2066     *
2067     * @return The column width in pixels
2068     *
2069     * @see #setColumnWidth(int)
2070     * @see #getRequestedColumnWidth()
2071     *
2072     * @attr ref android.R.styleable#GridView_columnWidth
2073     */
2074    public int getColumnWidth() {
2075        return mColumnWidth;
2076    }
2077
2078    /**
2079     * Return the requested width of a column in the grid.
2080     *
2081     * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2082     * to retrieve the current real width of a column.</p>
2083     *
2084     * @return The requested column width in pixels
2085     *
2086     * @see #setColumnWidth(int)
2087     * @see #getColumnWidth()
2088     *
2089     * @attr ref android.R.styleable#GridView_columnWidth
2090     */
2091    public int getRequestedColumnWidth() {
2092        return mRequestedColumnWidth;
2093    }
2094
2095    /**
2096     * Set the number of columns in the grid
2097     *
2098     * @param numColumns The desired number of columns.
2099     *
2100     * @attr ref android.R.styleable#GridView_numColumns
2101     */
2102    public void setNumColumns(int numColumns) {
2103        if (numColumns != mRequestedNumColumns) {
2104            mRequestedNumColumns = numColumns;
2105            requestLayoutIfNecessary();
2106        }
2107    }
2108
2109    /**
2110     * Get the number of columns in the grid.
2111     * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2112     *
2113     * @attr ref android.R.styleable#GridView_numColumns
2114     *
2115     * @see #setNumColumns(int)
2116     */
2117    @ViewDebug.ExportedProperty
2118    public int getNumColumns() {
2119        return mNumColumns;
2120    }
2121
2122    /**
2123     * Make sure views are touching the top or bottom edge, as appropriate for
2124     * our gravity
2125     */
2126    private void adjustViewsUpOrDown() {
2127        final int childCount = getChildCount();
2128
2129        if (childCount > 0) {
2130            int delta;
2131            View child;
2132
2133            if (!mStackFromBottom) {
2134                // Uh-oh -- we came up short. Slide all views up to make them
2135                // align with the top
2136                child = getChildAt(0);
2137                delta = child.getTop() - mListPadding.top;
2138                if (mFirstPosition != 0) {
2139                    // It's OK to have some space above the first item if it is
2140                    // part of the vertical spacing
2141                    delta -= mVerticalSpacing;
2142                }
2143                if (delta < 0) {
2144                    // We only are looking to see if we are too low, not too high
2145                    delta = 0;
2146                }
2147            } else {
2148                // we are too high, slide all views down to align with bottom
2149                child = getChildAt(childCount - 1);
2150                delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2151
2152                if (mFirstPosition + childCount < mItemCount) {
2153                    // It's OK to have some space below the last item if it is
2154                    // part of the vertical spacing
2155                    delta += mVerticalSpacing;
2156                }
2157
2158                if (delta > 0) {
2159                    // We only are looking to see if we are too high, not too low
2160                    delta = 0;
2161                }
2162            }
2163
2164            if (delta != 0) {
2165                offsetChildrenTopAndBottom(-delta);
2166            }
2167        }
2168    }
2169
2170    @Override
2171    protected int computeVerticalScrollExtent() {
2172        final int count = getChildCount();
2173        if (count > 0) {
2174            final int numColumns = mNumColumns;
2175            final int rowCount = (count + numColumns - 1) / numColumns;
2176
2177            int extent = rowCount * 100;
2178
2179            View view = getChildAt(0);
2180            final int top = view.getTop();
2181            int height = view.getHeight();
2182            if (height > 0) {
2183                extent += (top * 100) / height;
2184            }
2185
2186            view = getChildAt(count - 1);
2187            final int bottom = view.getBottom();
2188            height = view.getHeight();
2189            if (height > 0) {
2190                extent -= ((bottom - getHeight()) * 100) / height;
2191            }
2192
2193            return extent;
2194        }
2195        return 0;
2196    }
2197
2198    @Override
2199    protected int computeVerticalScrollOffset() {
2200        if (mFirstPosition >= 0 && getChildCount() > 0) {
2201            final View view = getChildAt(0);
2202            final int top = view.getTop();
2203            int height = view.getHeight();
2204            if (height > 0) {
2205                final int numColumns = mNumColumns;
2206                final int whichRow = mFirstPosition / numColumns;
2207                final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2208                return Math.max(whichRow * 100 - (top * 100) / height +
2209                        (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
2210            }
2211        }
2212        return 0;
2213    }
2214
2215    @Override
2216    protected int computeVerticalScrollRange() {
2217        // TODO: Account for vertical spacing too
2218        final int numColumns = mNumColumns;
2219        final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2220        int result = Math.max(rowCount * 100, 0);
2221        if (mScrollY != 0) {
2222            // Compensate for overscroll
2223            result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2224        }
2225        return result;
2226    }
2227
2228    @Override
2229    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2230        super.onInitializeAccessibilityEvent(event);
2231        event.setClassName(GridView.class.getName());
2232    }
2233
2234    @Override
2235    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2236        super.onInitializeAccessibilityNodeInfo(info);
2237        info.setClassName(GridView.class.getName());
2238    }
2239}
2240