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