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