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            if (mPositionScrollAfterLayout != null) {
1279                post(mPositionScrollAfterLayout);
1280                mPositionScrollAfterLayout = null;
1281            }
1282            mNeedSync = false;
1283            setNextSelectedPositionInt(mSelectedPosition);
1284
1285            updateScrollIndicators();
1286
1287            if (mItemCount > 0) {
1288                checkSelectionChanged();
1289            }
1290
1291            invokeOnItemScrollListener();
1292        } finally {
1293            if (!blockLayoutRequests) {
1294                mBlockLayoutRequests = false;
1295            }
1296        }
1297    }
1298
1299
1300    /**
1301     * Obtain the view and add it to our list of children. The view can be made
1302     * fresh, converted from an unused view, or used as is if it was in the
1303     * recycle bin.
1304     *
1305     * @param position Logical position in the list
1306     * @param y Top or bottom edge of the view to add
1307     * @param flow if true, align top edge to y. If false, align bottom edge to
1308     *        y.
1309     * @param childrenLeft Left edge where children should be positioned
1310     * @param selected Is this position selected?
1311     * @param where to add new item in the list
1312     * @return View that was added
1313     */
1314    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1315            boolean selected, int where) {
1316        View child;
1317
1318        if (!mDataChanged) {
1319            // Try to use an existing view for this position
1320            child = mRecycler.getActiveView(position);
1321            if (child != null) {
1322                // Found it -- we're using an existing child
1323                // This just needs to be positioned
1324                setupChild(child, position, y, flow, childrenLeft, selected, true, where);
1325                return child;
1326            }
1327        }
1328
1329        // Make a new view for this position, or convert an unused view if
1330        // possible
1331        child = obtainView(position, mIsScrap);
1332
1333        // This needs to be positioned and measured
1334        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
1335
1336        return child;
1337    }
1338
1339    /**
1340     * Add a view as a child and make sure it is measured (if necessary) and
1341     * positioned properly.
1342     *
1343     * @param child The view to add
1344     * @param position The position of the view
1345     * @param y The y position relative to which this view will be positioned
1346     * @param flow if true, align top edge to y. If false, align bottom edge
1347     *        to y.
1348     * @param childrenLeft Left edge where children should be positioned
1349     * @param selected Is this position selected?
1350     * @param recycled Has this view been pulled from the recycle bin? If so it
1351     *        does not need to be remeasured.
1352     * @param where Where to add the item in the list
1353     *
1354     */
1355    private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
1356            boolean selected, boolean recycled, int where) {
1357        boolean isSelected = selected && shouldShowSelector();
1358        final boolean updateChildSelected = isSelected != child.isSelected();
1359        final int mode = mTouchMode;
1360        final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1361                mMotionPosition == position;
1362        final boolean updateChildPressed = isPressed != child.isPressed();
1363
1364        boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1365
1366        // Respect layout params that are already in the view. Otherwise make
1367        // some up...
1368        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1369        if (p == null) {
1370            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
1371        }
1372        p.viewType = mAdapter.getItemViewType(position);
1373
1374        if (recycled && !p.forceAdd) {
1375            attachViewToParent(child, where, p);
1376        } else {
1377            p.forceAdd = false;
1378            addViewInLayout(child, where, p, true);
1379        }
1380
1381        if (updateChildSelected) {
1382            child.setSelected(isSelected);
1383            if (isSelected) {
1384                requestFocus();
1385            }
1386        }
1387
1388        if (updateChildPressed) {
1389            child.setPressed(isPressed);
1390        }
1391
1392        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1393            if (child instanceof Checkable) {
1394                ((Checkable) child).setChecked(mCheckStates.get(position));
1395            } else if (getContext().getApplicationInfo().targetSdkVersion
1396                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1397                child.setActivated(mCheckStates.get(position));
1398            }
1399        }
1400
1401        if (needToMeasure) {
1402            int childHeightSpec = ViewGroup.getChildMeasureSpec(
1403                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1404
1405            int childWidthSpec = ViewGroup.getChildMeasureSpec(
1406                    MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1407            child.measure(childWidthSpec, childHeightSpec);
1408        } else {
1409            cleanupLayoutState(child);
1410        }
1411
1412        final int w = child.getMeasuredWidth();
1413        final int h = child.getMeasuredHeight();
1414
1415        int childLeft;
1416        final int childTop = flow ? y : y - h;
1417
1418        final int layoutDirection = getResolvedLayoutDirection();
1419        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
1420        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
1421            case Gravity.LEFT:
1422                childLeft = childrenLeft;
1423                break;
1424            case Gravity.CENTER_HORIZONTAL:
1425                childLeft = childrenLeft + ((mColumnWidth - w) / 2);
1426                break;
1427            case Gravity.RIGHT:
1428                childLeft = childrenLeft + mColumnWidth - w;
1429                break;
1430            default:
1431                childLeft = childrenLeft;
1432                break;
1433        }
1434
1435        if (needToMeasure) {
1436            final int childRight = childLeft + w;
1437            final int childBottom = childTop + h;
1438            child.layout(childLeft, childTop, childRight, childBottom);
1439        } else {
1440            child.offsetLeftAndRight(childLeft - child.getLeft());
1441            child.offsetTopAndBottom(childTop - child.getTop());
1442        }
1443
1444        if (mCachingStarted) {
1445            child.setDrawingCacheEnabled(true);
1446        }
1447
1448        if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1449                != position) {
1450            child.jumpDrawablesToCurrentState();
1451        }
1452    }
1453
1454    /**
1455     * Sets the currently selected item
1456     *
1457     * @param position Index (starting at 0) of the data item to be selected.
1458     *
1459     * If in touch mode, the item will not be selected but it will still be positioned
1460     * appropriately.
1461     */
1462    @Override
1463    public void setSelection(int position) {
1464        if (!isInTouchMode()) {
1465            setNextSelectedPositionInt(position);
1466        } else {
1467            mResurrectToPosition = position;
1468        }
1469        mLayoutMode = LAYOUT_SET_SELECTION;
1470        if (mPositionScroller != null) {
1471            mPositionScroller.stop();
1472        }
1473        requestLayout();
1474    }
1475
1476    /**
1477     * Makes the item at the supplied position selected.
1478     *
1479     * @param position the position of the new selection
1480     */
1481    @Override
1482    void setSelectionInt(int position) {
1483        int previousSelectedPosition = mNextSelectedPosition;
1484
1485        if (mPositionScroller != null) {
1486            mPositionScroller.stop();
1487        }
1488
1489        setNextSelectedPositionInt(position);
1490        layoutChildren();
1491
1492        final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
1493            mNextSelectedPosition;
1494        final int previous = mStackFromBottom ? mItemCount - 1
1495                - previousSelectedPosition : previousSelectedPosition;
1496
1497        final int nextRow = next / mNumColumns;
1498        final int previousRow = previous / mNumColumns;
1499
1500        if (nextRow != previousRow) {
1501            awakenScrollBars();
1502        }
1503
1504    }
1505
1506    @Override
1507    public boolean onKeyDown(int keyCode, KeyEvent event) {
1508        return commonKey(keyCode, 1, event);
1509    }
1510
1511    @Override
1512    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1513        return commonKey(keyCode, repeatCount, event);
1514    }
1515
1516    @Override
1517    public boolean onKeyUp(int keyCode, KeyEvent event) {
1518        return commonKey(keyCode, 1, event);
1519    }
1520
1521    private boolean commonKey(int keyCode, int count, KeyEvent event) {
1522        if (mAdapter == null) {
1523            return false;
1524        }
1525
1526        if (mDataChanged) {
1527            layoutChildren();
1528        }
1529
1530        boolean handled = false;
1531        int action = event.getAction();
1532
1533        if (action != KeyEvent.ACTION_UP) {
1534            switch (keyCode) {
1535                case KeyEvent.KEYCODE_DPAD_LEFT:
1536                    if (event.hasNoModifiers()) {
1537                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
1538                    }
1539                    break;
1540
1541                case KeyEvent.KEYCODE_DPAD_RIGHT:
1542                    if (event.hasNoModifiers()) {
1543                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
1544                    }
1545                    break;
1546
1547                case KeyEvent.KEYCODE_DPAD_UP:
1548                    if (event.hasNoModifiers()) {
1549                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
1550                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1551                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1552                    }
1553                    break;
1554
1555                case KeyEvent.KEYCODE_DPAD_DOWN:
1556                    if (event.hasNoModifiers()) {
1557                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
1558                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1559                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1560                    }
1561                    break;
1562
1563                case KeyEvent.KEYCODE_DPAD_CENTER:
1564                case KeyEvent.KEYCODE_ENTER:
1565                    if (event.hasNoModifiers()) {
1566                        handled = resurrectSelectionIfNeeded();
1567                        if (!handled
1568                                && event.getRepeatCount() == 0 && getChildCount() > 0) {
1569                            keyPressed();
1570                            handled = true;
1571                        }
1572                    }
1573                    break;
1574
1575                case KeyEvent.KEYCODE_SPACE:
1576                    if (mPopup == null || !mPopup.isShowing()) {
1577                        if (event.hasNoModifiers()) {
1578                            handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
1579                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1580                            handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
1581                        }
1582                    }
1583                    break;
1584
1585                case KeyEvent.KEYCODE_PAGE_UP:
1586                    if (event.hasNoModifiers()) {
1587                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
1588                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1589                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1590                    }
1591                    break;
1592
1593                case KeyEvent.KEYCODE_PAGE_DOWN:
1594                    if (event.hasNoModifiers()) {
1595                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
1596                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1597                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1598                    }
1599                    break;
1600
1601                case KeyEvent.KEYCODE_MOVE_HOME:
1602                    if (event.hasNoModifiers()) {
1603                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1604                    }
1605                    break;
1606
1607                case KeyEvent.KEYCODE_MOVE_END:
1608                    if (event.hasNoModifiers()) {
1609                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1610                    }
1611                    break;
1612
1613                case KeyEvent.KEYCODE_TAB:
1614                    // XXX Sometimes it is useful to be able to TAB through the items in
1615                    //     a GridView sequentially.  Unfortunately this can create an
1616                    //     asymmetry in TAB navigation order unless the list selection
1617                    //     always reverts to the top or bottom when receiving TAB focus from
1618                    //     another widget.  Leaving this behavior disabled for now but
1619                    //     perhaps it should be configurable (and more comprehensive).
1620                    if (false) {
1621                        if (event.hasNoModifiers()) {
1622                            handled = resurrectSelectionIfNeeded()
1623                                    || sequenceScroll(FOCUS_FORWARD);
1624                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1625                            handled = resurrectSelectionIfNeeded()
1626                                    || sequenceScroll(FOCUS_BACKWARD);
1627                        }
1628                    }
1629                    break;
1630            }
1631        }
1632
1633        if (handled) {
1634            return true;
1635        }
1636
1637        if (sendToTextFilter(keyCode, count, event)) {
1638            return true;
1639        }
1640
1641        switch (action) {
1642            case KeyEvent.ACTION_DOWN:
1643                return super.onKeyDown(keyCode, event);
1644            case KeyEvent.ACTION_UP:
1645                return super.onKeyUp(keyCode, event);
1646            case KeyEvent.ACTION_MULTIPLE:
1647                return super.onKeyMultiple(keyCode, count, event);
1648            default:
1649                return false;
1650        }
1651    }
1652
1653    /**
1654     * Scrolls up or down by the number of items currently present on screen.
1655     *
1656     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1657     * @return whether selection was moved
1658     */
1659    boolean pageScroll(int direction) {
1660        int nextPage = -1;
1661
1662        if (direction == FOCUS_UP) {
1663            nextPage = Math.max(0, mSelectedPosition - getChildCount());
1664        } else if (direction == FOCUS_DOWN) {
1665            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
1666        }
1667
1668        if (nextPage >= 0) {
1669            setSelectionInt(nextPage);
1670            invokeOnItemScrollListener();
1671            awakenScrollBars();
1672            return true;
1673        }
1674
1675        return false;
1676    }
1677
1678    /**
1679     * Go to the last or first item if possible.
1680     *
1681     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1682     *
1683     * @return Whether selection was moved.
1684     */
1685    boolean fullScroll(int direction) {
1686        boolean moved = false;
1687        if (direction == FOCUS_UP) {
1688            mLayoutMode = LAYOUT_SET_SELECTION;
1689            setSelectionInt(0);
1690            invokeOnItemScrollListener();
1691            moved = true;
1692        } else if (direction == FOCUS_DOWN) {
1693            mLayoutMode = LAYOUT_SET_SELECTION;
1694            setSelectionInt(mItemCount - 1);
1695            invokeOnItemScrollListener();
1696            moved = true;
1697        }
1698
1699        if (moved) {
1700            awakenScrollBars();
1701        }
1702
1703        return moved;
1704    }
1705
1706    /**
1707     * Scrolls to the next or previous item, horizontally or vertically.
1708     *
1709     * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1710     *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1711     *
1712     * @return whether selection was moved
1713     */
1714    boolean arrowScroll(int direction) {
1715        final int selectedPosition = mSelectedPosition;
1716        final int numColumns = mNumColumns;
1717
1718        int startOfRowPos;
1719        int endOfRowPos;
1720
1721        boolean moved = false;
1722
1723        if (!mStackFromBottom) {
1724            startOfRowPos = (selectedPosition / numColumns) * numColumns;
1725            endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1726        } else {
1727            final int invertedSelection = mItemCount - 1 - selectedPosition;
1728            endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1729            startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1730        }
1731
1732        switch (direction) {
1733            case FOCUS_UP:
1734                if (startOfRowPos > 0) {
1735                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1736                    setSelectionInt(Math.max(0, selectedPosition - numColumns));
1737                    moved = true;
1738                }
1739                break;
1740            case FOCUS_DOWN:
1741                if (endOfRowPos < mItemCount - 1) {
1742                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1743                    setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1744                    moved = true;
1745                }
1746                break;
1747            case FOCUS_LEFT:
1748                if (selectedPosition > startOfRowPos) {
1749                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1750                    setSelectionInt(Math.max(0, selectedPosition - 1));
1751                    moved = true;
1752                }
1753                break;
1754            case FOCUS_RIGHT:
1755                if (selectedPosition < endOfRowPos) {
1756                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1757                    setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
1758                    moved = true;
1759                }
1760                break;
1761        }
1762
1763        if (moved) {
1764            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1765            invokeOnItemScrollListener();
1766        }
1767
1768        if (moved) {
1769            awakenScrollBars();
1770        }
1771
1772        return moved;
1773    }
1774
1775    /**
1776     * Goes to the next or previous item according to the order set by the
1777     * adapter.
1778     */
1779    boolean sequenceScroll(int direction) {
1780        int selectedPosition = mSelectedPosition;
1781        int numColumns = mNumColumns;
1782        int count = mItemCount;
1783
1784        int startOfRow;
1785        int endOfRow;
1786        if (!mStackFromBottom) {
1787            startOfRow = (selectedPosition / numColumns) * numColumns;
1788            endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1789        } else {
1790            int invertedSelection = count - 1 - selectedPosition;
1791            endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1792            startOfRow = Math.max(0, endOfRow - numColumns + 1);
1793        }
1794
1795        boolean moved = false;
1796        boolean showScroll = false;
1797        switch (direction) {
1798            case FOCUS_FORWARD:
1799                if (selectedPosition < count - 1) {
1800                    // Move to the next item.
1801                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1802                    setSelectionInt(selectedPosition + 1);
1803                    moved = true;
1804                    // Show the scrollbar only if changing rows.
1805                    showScroll = selectedPosition == endOfRow;
1806                }
1807                break;
1808
1809            case FOCUS_BACKWARD:
1810                if (selectedPosition > 0) {
1811                    // Move to the previous item.
1812                    mLayoutMode = LAYOUT_MOVE_SELECTION;
1813                    setSelectionInt(selectedPosition - 1);
1814                    moved = true;
1815                    // Show the scrollbar only if changing rows.
1816                    showScroll = selectedPosition == startOfRow;
1817                }
1818                break;
1819        }
1820
1821        if (moved) {
1822            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1823            invokeOnItemScrollListener();
1824        }
1825
1826        if (showScroll) {
1827            awakenScrollBars();
1828        }
1829
1830        return moved;
1831    }
1832
1833    @Override
1834    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1835        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1836
1837        int closestChildIndex = -1;
1838        if (gainFocus && previouslyFocusedRect != null) {
1839            previouslyFocusedRect.offset(mScrollX, mScrollY);
1840
1841            // figure out which item should be selected based on previously
1842            // focused rect
1843            Rect otherRect = mTempRect;
1844            int minDistance = Integer.MAX_VALUE;
1845            final int childCount = getChildCount();
1846            for (int i = 0; i < childCount; i++) {
1847                // only consider view's on appropriate edge of grid
1848                if (!isCandidateSelection(i, direction)) {
1849                    continue;
1850                }
1851
1852                final View other = getChildAt(i);
1853                other.getDrawingRect(otherRect);
1854                offsetDescendantRectToMyCoords(other, otherRect);
1855                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1856
1857                if (distance < minDistance) {
1858                    minDistance = distance;
1859                    closestChildIndex = i;
1860                }
1861            }
1862        }
1863
1864        if (closestChildIndex >= 0) {
1865            setSelection(closestChildIndex + mFirstPosition);
1866        } else {
1867            requestLayout();
1868        }
1869    }
1870
1871    /**
1872     * Is childIndex a candidate for next focus given the direction the focus
1873     * change is coming from?
1874     * @param childIndex The index to check.
1875     * @param direction The direction, one of
1876     *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
1877     * @return Whether childIndex is a candidate.
1878     */
1879    private boolean isCandidateSelection(int childIndex, int direction) {
1880        final int count = getChildCount();
1881        final int invertedIndex = count - 1 - childIndex;
1882
1883        int rowStart;
1884        int rowEnd;
1885
1886        if (!mStackFromBottom) {
1887            rowStart = childIndex - (childIndex % mNumColumns);
1888            rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1889        } else {
1890            rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1891            rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1892        }
1893
1894        switch (direction) {
1895            case View.FOCUS_RIGHT:
1896                // coming from left, selection is only valid if it is on left
1897                // edge
1898                return childIndex == rowStart;
1899            case View.FOCUS_DOWN:
1900                // coming from top; only valid if in top row
1901                return rowStart == 0;
1902            case View.FOCUS_LEFT:
1903                // coming from right, must be on right edge
1904                return childIndex == rowEnd;
1905            case View.FOCUS_UP:
1906                // coming from bottom, need to be in last row
1907                return rowEnd == count - 1;
1908            case View.FOCUS_FORWARD:
1909                // coming from top-left, need to be first in top row
1910                return childIndex == rowStart && rowStart == 0;
1911            case View.FOCUS_BACKWARD:
1912                // coming from bottom-right, need to be last in bottom row
1913                return childIndex == rowEnd && rowEnd == count - 1;
1914            default:
1915                throw new IllegalArgumentException("direction must be one of "
1916                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
1917                        + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
1918        }
1919    }
1920
1921    /**
1922     * Set the gravity for this grid. Gravity describes how the child views
1923     * are horizontally aligned. Defaults to Gravity.LEFT
1924     *
1925     * @param gravity the gravity to apply to this grid's children
1926     *
1927     * @attr ref android.R.styleable#GridView_gravity
1928     */
1929    public void setGravity(int gravity) {
1930        if (mGravity != gravity) {
1931            mGravity = gravity;
1932            requestLayoutIfNecessary();
1933        }
1934    }
1935
1936    /**
1937     * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
1938     *
1939     * @return the gravity that will be applied to this grid's children
1940     *
1941     * @attr ref android.R.styleable#GridView_gravity
1942     */
1943    public int getGravity() {
1944        return mGravity;
1945    }
1946
1947    /**
1948     * Set the amount of horizontal (x) spacing to place between each item
1949     * in the grid.
1950     *
1951     * @param horizontalSpacing The amount of horizontal space between items,
1952     * in pixels.
1953     *
1954     * @attr ref android.R.styleable#GridView_horizontalSpacing
1955     */
1956    public void setHorizontalSpacing(int horizontalSpacing) {
1957        if (horizontalSpacing != mRequestedHorizontalSpacing) {
1958            mRequestedHorizontalSpacing = horizontalSpacing;
1959            requestLayoutIfNecessary();
1960        }
1961    }
1962
1963    /**
1964     * Returns the amount of horizontal spacing currently used between each item in the grid.
1965     *
1966     * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
1967     * has been called but layout is not yet complete, this method may return a stale value.
1968     * To get the horizontal spacing that was explicitly requested use
1969     * {@link #getRequestedHorizontalSpacing()}.</p>
1970     *
1971     * @return Current horizontal spacing between each item in pixels
1972     *
1973     * @see #setHorizontalSpacing(int)
1974     * @see #getRequestedHorizontalSpacing()
1975     *
1976     * @attr ref android.R.styleable#GridView_horizontalSpacing
1977     */
1978    public int getHorizontalSpacing() {
1979        return mHorizontalSpacing;
1980    }
1981
1982    /**
1983     * Returns the requested amount of horizontal spacing between each item in the grid.
1984     *
1985     * <p>The value returned may have been supplied during inflation as part of a style,
1986     * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
1987     * If layout is not yet complete or if GridView calculated a different horizontal spacing
1988     * from what was requested, this may return a different value from
1989     * {@link #getHorizontalSpacing()}.</p>
1990     *
1991     * @return The currently requested horizontal spacing between items, in pixels
1992     *
1993     * @see #setHorizontalSpacing(int)
1994     * @see #getHorizontalSpacing()
1995     *
1996     * @attr ref android.R.styleable#GridView_horizontalSpacing
1997     */
1998    public int getRequestedHorizontalSpacing() {
1999        return mRequestedHorizontalSpacing;
2000    }
2001
2002    /**
2003     * Set the amount of vertical (y) spacing to place between each item
2004     * in the grid.
2005     *
2006     * @param verticalSpacing The amount of vertical space between items,
2007     * in pixels.
2008     *
2009     * @see #getVerticalSpacing()
2010     *
2011     * @attr ref android.R.styleable#GridView_verticalSpacing
2012     */
2013    public void setVerticalSpacing(int verticalSpacing) {
2014        if (verticalSpacing != mVerticalSpacing) {
2015            mVerticalSpacing = verticalSpacing;
2016            requestLayoutIfNecessary();
2017        }
2018    }
2019
2020    /**
2021     * Returns the amount of vertical spacing between each item in the grid.
2022     *
2023     * @return The vertical spacing between items in pixels
2024     *
2025     * @see #setVerticalSpacing(int)
2026     *
2027     * @attr ref android.R.styleable#GridView_verticalSpacing
2028     */
2029    public int getVerticalSpacing() {
2030        return mVerticalSpacing;
2031    }
2032
2033    /**
2034     * Control how items are stretched to fill their space.
2035     *
2036     * @param stretchMode Either {@link #NO_STRETCH},
2037     * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2038     *
2039     * @attr ref android.R.styleable#GridView_stretchMode
2040     */
2041    public void setStretchMode(int stretchMode) {
2042        if (stretchMode != mStretchMode) {
2043            mStretchMode = stretchMode;
2044            requestLayoutIfNecessary();
2045        }
2046    }
2047
2048    public int getStretchMode() {
2049        return mStretchMode;
2050    }
2051
2052    /**
2053     * Set the width of columns in the grid.
2054     *
2055     * @param columnWidth The column width, in pixels.
2056     *
2057     * @attr ref android.R.styleable#GridView_columnWidth
2058     */
2059    public void setColumnWidth(int columnWidth) {
2060        if (columnWidth != mRequestedColumnWidth) {
2061            mRequestedColumnWidth = columnWidth;
2062            requestLayoutIfNecessary();
2063        }
2064    }
2065
2066    /**
2067     * Return the width of a column in the grid.
2068     *
2069     * <p>This may not be valid yet if a layout is pending.</p>
2070     *
2071     * @return The column width in pixels
2072     *
2073     * @see #setColumnWidth(int)
2074     * @see #getRequestedColumnWidth()
2075     *
2076     * @attr ref android.R.styleable#GridView_columnWidth
2077     */
2078    public int getColumnWidth() {
2079        return mColumnWidth;
2080    }
2081
2082    /**
2083     * Return the requested width of a column in the grid.
2084     *
2085     * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2086     * to retrieve the current real width of a column.</p>
2087     *
2088     * @return The requested column width in pixels
2089     *
2090     * @see #setColumnWidth(int)
2091     * @see #getColumnWidth()
2092     *
2093     * @attr ref android.R.styleable#GridView_columnWidth
2094     */
2095    public int getRequestedColumnWidth() {
2096        return mRequestedColumnWidth;
2097    }
2098
2099    /**
2100     * Set the number of columns in the grid
2101     *
2102     * @param numColumns The desired number of columns.
2103     *
2104     * @attr ref android.R.styleable#GridView_numColumns
2105     */
2106    public void setNumColumns(int numColumns) {
2107        if (numColumns != mRequestedNumColumns) {
2108            mRequestedNumColumns = numColumns;
2109            requestLayoutIfNecessary();
2110        }
2111    }
2112
2113    /**
2114     * Get the number of columns in the grid.
2115     * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2116     *
2117     * @attr ref android.R.styleable#GridView_numColumns
2118     *
2119     * @see #setNumColumns(int)
2120     */
2121    @ViewDebug.ExportedProperty
2122    public int getNumColumns() {
2123        return mNumColumns;
2124    }
2125
2126    /**
2127     * Make sure views are touching the top or bottom edge, as appropriate for
2128     * our gravity
2129     */
2130    private void adjustViewsUpOrDown() {
2131        final int childCount = getChildCount();
2132
2133        if (childCount > 0) {
2134            int delta;
2135            View child;
2136
2137            if (!mStackFromBottom) {
2138                // Uh-oh -- we came up short. Slide all views up to make them
2139                // align with the top
2140                child = getChildAt(0);
2141                delta = child.getTop() - mListPadding.top;
2142                if (mFirstPosition != 0) {
2143                    // It's OK to have some space above the first item if it is
2144                    // part of the vertical spacing
2145                    delta -= mVerticalSpacing;
2146                }
2147                if (delta < 0) {
2148                    // We only are looking to see if we are too low, not too high
2149                    delta = 0;
2150                }
2151            } else {
2152                // we are too high, slide all views down to align with bottom
2153                child = getChildAt(childCount - 1);
2154                delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2155
2156                if (mFirstPosition + childCount < mItemCount) {
2157                    // It's OK to have some space below the last item if it is
2158                    // part of the vertical spacing
2159                    delta += mVerticalSpacing;
2160                }
2161
2162                if (delta > 0) {
2163                    // We only are looking to see if we are too high, not too low
2164                    delta = 0;
2165                }
2166            }
2167
2168            if (delta != 0) {
2169                offsetChildrenTopAndBottom(-delta);
2170            }
2171        }
2172    }
2173
2174    @Override
2175    protected int computeVerticalScrollExtent() {
2176        final int count = getChildCount();
2177        if (count > 0) {
2178            final int numColumns = mNumColumns;
2179            final int rowCount = (count + numColumns - 1) / numColumns;
2180
2181            int extent = rowCount * 100;
2182
2183            View view = getChildAt(0);
2184            final int top = view.getTop();
2185            int height = view.getHeight();
2186            if (height > 0) {
2187                extent += (top * 100) / height;
2188            }
2189
2190            view = getChildAt(count - 1);
2191            final int bottom = view.getBottom();
2192            height = view.getHeight();
2193            if (height > 0) {
2194                extent -= ((bottom - getHeight()) * 100) / height;
2195            }
2196
2197            return extent;
2198        }
2199        return 0;
2200    }
2201
2202    @Override
2203    protected int computeVerticalScrollOffset() {
2204        if (mFirstPosition >= 0 && getChildCount() > 0) {
2205            final View view = getChildAt(0);
2206            final int top = view.getTop();
2207            int height = view.getHeight();
2208            if (height > 0) {
2209                final int numColumns = mNumColumns;
2210                final int whichRow = mFirstPosition / numColumns;
2211                final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2212                return Math.max(whichRow * 100 - (top * 100) / height +
2213                        (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
2214            }
2215        }
2216        return 0;
2217    }
2218
2219    @Override
2220    protected int computeVerticalScrollRange() {
2221        // TODO: Account for vertical spacing too
2222        final int numColumns = mNumColumns;
2223        final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2224        int result = Math.max(rowCount * 100, 0);
2225        if (mScrollY != 0) {
2226            // Compensate for overscroll
2227            result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2228        }
2229        return result;
2230    }
2231
2232    @Override
2233    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2234        super.onInitializeAccessibilityEvent(event);
2235        event.setClassName(GridView.class.getName());
2236    }
2237
2238    @Override
2239    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2240        super.onInitializeAccessibilityNodeInfo(info);
2241        info.setClassName(GridView.class.getName());
2242    }
2243}
2244