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