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