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