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