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