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