• 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      * Obtains the view and adds it to our list of children. The view can be
1411      * made fresh, converted from an unused view, or used as is if it was in
1412      * the 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 {@code true} to align top edge to y, {@code false} to align
1417      *             bottom edge to y
1418      * @param childrenLeft left edge where children should be positioned
1419      * @param selected {@code true} if the position is selected, {@code false}
1420      *                 otherwise
1421      * @param where position at which to add new item in the list
1422      * @return View that was added
1423      */
makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected, int where)1424     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1425             boolean selected, int where) {
1426         if (!mDataChanged) {
1427             // Try to use an existing view for this position
1428             final View activeView = mRecycler.getActiveView(position);
1429             if (activeView != null) {
1430                 // Found it -- we're using an existing child
1431                 // This just needs to be positioned
1432                 setupChild(activeView, position, y, flow, childrenLeft, selected, true, where);
1433                 return activeView;
1434             }
1435         }
1436 
1437         // Make a new view for this position, or convert an unused view if
1438         // possible.
1439         final View child = obtainView(position, mIsScrap);
1440 
1441         // This needs to be positioned and measured.
1442         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
1443 
1444         return child;
1445     }
1446 
1447     /**
1448      * Adds a view as a child and make sure it is measured (if necessary) and
1449      * positioned properly.
1450      *
1451      * @param child the view to add
1452      * @param position the position of this child
1453      * @param y the y position relative to which this view will be positioned
1454      * @param flowDown {@code true} to align top edge to y, {@code false} to
1455      *                 align bottom edge to y
1456      * @param childrenLeft left edge where children should be positioned
1457      * @param selected {@code true} if the position is selected, {@code false}
1458      *                 otherwise
1459      * @param isAttachedToWindow {@code true} if the view is already attached
1460      *                           to the window, e.g. whether it was reused, or
1461      *                           {@code false} otherwise
1462      * @param where position at which to add new item in the list
1463      *
1464      */
setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean isAttachedToWindow, int where)1465     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1466             boolean selected, boolean isAttachedToWindow, int where) {
1467         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupGridItem");
1468 
1469         boolean isSelected = selected && shouldShowSelector();
1470         final boolean updateChildSelected = isSelected != child.isSelected();
1471         final int mode = mTouchMode;
1472         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
1473                 && mMotionPosition == position;
1474         final boolean updateChildPressed = isPressed != child.isPressed();
1475         final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
1476                 || child.isLayoutRequested();
1477 
1478         // Respect layout params that are already in the view. Otherwise make
1479         // some up...
1480         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1481         if (p == null) {
1482             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
1483         }
1484         p.viewType = mAdapter.getItemViewType(position);
1485         p.isEnabled = mAdapter.isEnabled(position);
1486 
1487         // Set up view state before attaching the view, since we may need to
1488         // rely on the jumpDrawablesToCurrentState() call that occurs as part
1489         // of view attachment.
1490         if (updateChildSelected) {
1491             child.setSelected(isSelected);
1492             if (isSelected) {
1493                 requestFocus();
1494             }
1495         }
1496 
1497         if (updateChildPressed) {
1498             child.setPressed(isPressed);
1499         }
1500 
1501         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1502             if (child instanceof Checkable) {
1503                 ((Checkable) child).setChecked(mCheckStates.get(position));
1504             } else if (getContext().getApplicationInfo().targetSdkVersion
1505                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1506                 child.setActivated(mCheckStates.get(position));
1507             }
1508         }
1509 
1510         if (isAttachedToWindow && !p.forceAdd) {
1511             attachViewToParent(child, where, p);
1512 
1513             // If the view isn't attached, or if it's attached but for a different
1514             // position, then jump the drawables.
1515             if (!isAttachedToWindow
1516                     || (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
1517                             != position) {
1518                 child.jumpDrawablesToCurrentState();
1519             }
1520         } else {
1521             p.forceAdd = false;
1522             addViewInLayout(child, where, p, true);
1523         }
1524 
1525         if (needToMeasure) {
1526             int childHeightSpec = ViewGroup.getChildMeasureSpec(
1527                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1528 
1529             int childWidthSpec = ViewGroup.getChildMeasureSpec(
1530                     MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1531             child.measure(childWidthSpec, childHeightSpec);
1532         } else {
1533             cleanupLayoutState(child);
1534         }
1535 
1536         final int w = child.getMeasuredWidth();
1537         final int h = child.getMeasuredHeight();
1538 
1539         int childLeft;
1540         final int childTop = flowDown ? y : y - h;
1541 
1542         final int layoutDirection = getLayoutDirection();
1543         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
1544         switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
1545             case Gravity.LEFT:
1546                 childLeft = childrenLeft;
1547                 break;
1548             case Gravity.CENTER_HORIZONTAL:
1549                 childLeft = childrenLeft + ((mColumnWidth - w) / 2);
1550                 break;
1551             case Gravity.RIGHT:
1552                 childLeft = childrenLeft + mColumnWidth - w;
1553                 break;
1554             default:
1555                 childLeft = childrenLeft;
1556                 break;
1557         }
1558 
1559         if (needToMeasure) {
1560             final int childRight = childLeft + w;
1561             final int childBottom = childTop + h;
1562             child.layout(childLeft, childTop, childRight, childBottom);
1563         } else {
1564             child.offsetLeftAndRight(childLeft - child.getLeft());
1565             child.offsetTopAndBottom(childTop - child.getTop());
1566         }
1567 
1568         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1569             child.setDrawingCacheEnabled(true);
1570         }
1571 
1572         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
1573     }
1574 
1575     /**
1576      * Sets the currently selected item
1577      *
1578      * @param position Index (starting at 0) of the data item to be selected.
1579      *
1580      * If in touch mode, the item will not be selected but it will still be positioned
1581      * appropriately.
1582      */
1583     @Override
setSelection(int position)1584     public void setSelection(int position) {
1585         if (!isInTouchMode()) {
1586             setNextSelectedPositionInt(position);
1587         } else {
1588             mResurrectToPosition = position;
1589         }
1590         mLayoutMode = LAYOUT_SET_SELECTION;
1591         if (mPositionScroller != null) {
1592             mPositionScroller.stop();
1593         }
1594         requestLayout();
1595     }
1596 
1597     /**
1598      * Makes the item at the supplied position selected.
1599      *
1600      * @param position the position of the new selection
1601      */
1602     @Override
setSelectionInt(int position)1603     void setSelectionInt(int position) {
1604         int previousSelectedPosition = mNextSelectedPosition;
1605 
1606         if (mPositionScroller != null) {
1607             mPositionScroller.stop();
1608         }
1609 
1610         setNextSelectedPositionInt(position);
1611         layoutChildren();
1612 
1613         final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
1614             mNextSelectedPosition;
1615         final int previous = mStackFromBottom ? mItemCount - 1
1616                 - previousSelectedPosition : previousSelectedPosition;
1617 
1618         final int nextRow = next / mNumColumns;
1619         final int previousRow = previous / mNumColumns;
1620 
1621         if (nextRow != previousRow) {
1622             awakenScrollBars();
1623         }
1624 
1625     }
1626 
1627     @Override
onKeyDown(int keyCode, KeyEvent event)1628     public boolean onKeyDown(int keyCode, KeyEvent event) {
1629         return commonKey(keyCode, 1, event);
1630     }
1631 
1632     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)1633     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1634         return commonKey(keyCode, repeatCount, event);
1635     }
1636 
1637     @Override
onKeyUp(int keyCode, KeyEvent event)1638     public boolean onKeyUp(int keyCode, KeyEvent event) {
1639         return commonKey(keyCode, 1, event);
1640     }
1641 
commonKey(int keyCode, int count, KeyEvent event)1642     private boolean commonKey(int keyCode, int count, KeyEvent event) {
1643         if (mAdapter == null) {
1644             return false;
1645         }
1646 
1647         if (mDataChanged) {
1648             layoutChildren();
1649         }
1650 
1651         boolean handled = false;
1652         int action = event.getAction();
1653         if (KeyEvent.isConfirmKey(keyCode)
1654                 && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
1655             handled = resurrectSelectionIfNeeded();
1656             if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
1657                 keyPressed();
1658                 handled = true;
1659             }
1660         }
1661 
1662         if (!handled && action != KeyEvent.ACTION_UP) {
1663             switch (keyCode) {
1664                 case KeyEvent.KEYCODE_DPAD_LEFT:
1665                     if (event.hasNoModifiers()) {
1666                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
1667                     }
1668                     break;
1669 
1670                 case KeyEvent.KEYCODE_DPAD_RIGHT:
1671                     if (event.hasNoModifiers()) {
1672                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
1673                     }
1674                     break;
1675 
1676                 case KeyEvent.KEYCODE_DPAD_UP:
1677                     if (event.hasNoModifiers()) {
1678                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
1679                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1680                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1681                     }
1682                     break;
1683 
1684                 case KeyEvent.KEYCODE_DPAD_DOWN:
1685                     if (event.hasNoModifiers()) {
1686                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
1687                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1688                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1689                     }
1690                     break;
1691 
1692                 case KeyEvent.KEYCODE_PAGE_UP:
1693                     if (event.hasNoModifiers()) {
1694                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
1695                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1696                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1697                     }
1698                     break;
1699 
1700                 case KeyEvent.KEYCODE_PAGE_DOWN:
1701                     if (event.hasNoModifiers()) {
1702                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
1703                     } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
1704                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1705                     }
1706                     break;
1707 
1708                 case KeyEvent.KEYCODE_MOVE_HOME:
1709                     if (event.hasNoModifiers()) {
1710                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
1711                     }
1712                     break;
1713 
1714                 case KeyEvent.KEYCODE_MOVE_END:
1715                     if (event.hasNoModifiers()) {
1716                         handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
1717                     }
1718                     break;
1719 
1720                 case KeyEvent.KEYCODE_TAB:
1721                     // XXX Sometimes it is useful to be able to TAB through the items in
1722                     //     a GridView sequentially.  Unfortunately this can create an
1723                     //     asymmetry in TAB navigation order unless the list selection
1724                     //     always reverts to the top or bottom when receiving TAB focus from
1725                     //     another widget.  Leaving this behavior disabled for now but
1726                     //     perhaps it should be configurable (and more comprehensive).
1727                     if (false) {
1728                         if (event.hasNoModifiers()) {
1729                             handled = resurrectSelectionIfNeeded()
1730                                     || sequenceScroll(FOCUS_FORWARD);
1731                         } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1732                             handled = resurrectSelectionIfNeeded()
1733                                     || sequenceScroll(FOCUS_BACKWARD);
1734                         }
1735                     }
1736                     break;
1737             }
1738         }
1739 
1740         if (handled) {
1741             return true;
1742         }
1743 
1744         if (sendToTextFilter(keyCode, count, event)) {
1745             return true;
1746         }
1747 
1748         switch (action) {
1749             case KeyEvent.ACTION_DOWN:
1750                 return super.onKeyDown(keyCode, event);
1751             case KeyEvent.ACTION_UP:
1752                 return super.onKeyUp(keyCode, event);
1753             case KeyEvent.ACTION_MULTIPLE:
1754                 return super.onKeyMultiple(keyCode, count, event);
1755             default:
1756                 return false;
1757         }
1758     }
1759 
1760     /**
1761      * Scrolls up or down by the number of items currently present on screen.
1762      *
1763      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1764      * @return whether selection was moved
1765      */
pageScroll(int direction)1766     boolean pageScroll(int direction) {
1767         int nextPage = -1;
1768 
1769         if (direction == FOCUS_UP) {
1770             nextPage = Math.max(0, mSelectedPosition - getChildCount());
1771         } else if (direction == FOCUS_DOWN) {
1772             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
1773         }
1774 
1775         if (nextPage >= 0) {
1776             setSelectionInt(nextPage);
1777             invokeOnItemScrollListener();
1778             awakenScrollBars();
1779             return true;
1780         }
1781 
1782         return false;
1783     }
1784 
1785     /**
1786      * Go to the last or first item if possible.
1787      *
1788      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1789      *
1790      * @return Whether selection was moved.
1791      */
fullScroll(int direction)1792     boolean fullScroll(int direction) {
1793         boolean moved = false;
1794         if (direction == FOCUS_UP) {
1795             mLayoutMode = LAYOUT_SET_SELECTION;
1796             setSelectionInt(0);
1797             invokeOnItemScrollListener();
1798             moved = true;
1799         } else if (direction == FOCUS_DOWN) {
1800             mLayoutMode = LAYOUT_SET_SELECTION;
1801             setSelectionInt(mItemCount - 1);
1802             invokeOnItemScrollListener();
1803             moved = true;
1804         }
1805 
1806         if (moved) {
1807             awakenScrollBars();
1808         }
1809 
1810         return moved;
1811     }
1812 
1813     /**
1814      * Scrolls to the next or previous item, horizontally or vertically.
1815      *
1816      * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1817      *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1818      *
1819      * @return whether selection was moved
1820      */
arrowScroll(int direction)1821     boolean arrowScroll(int direction) {
1822         final int selectedPosition = mSelectedPosition;
1823         final int numColumns = mNumColumns;
1824 
1825         int startOfRowPos;
1826         int endOfRowPos;
1827 
1828         boolean moved = false;
1829 
1830         if (!mStackFromBottom) {
1831             startOfRowPos = (selectedPosition / numColumns) * numColumns;
1832             endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1833         } else {
1834             final int invertedSelection = mItemCount - 1 - selectedPosition;
1835             endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1836             startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1837         }
1838 
1839         switch (direction) {
1840             case FOCUS_UP:
1841                 if (startOfRowPos > 0) {
1842                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1843                     setSelectionInt(Math.max(0, selectedPosition - numColumns));
1844                     moved = true;
1845                 }
1846                 break;
1847             case FOCUS_DOWN:
1848                 if (endOfRowPos < mItemCount - 1) {
1849                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1850                     setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1851                     moved = true;
1852                 }
1853                 break;
1854         }
1855 
1856         final boolean isLayoutRtl = isLayoutRtl();
1857         if (selectedPosition > startOfRowPos && ((direction == FOCUS_LEFT && !isLayoutRtl) ||
1858                 (direction == FOCUS_RIGHT && isLayoutRtl))) {
1859             mLayoutMode = LAYOUT_MOVE_SELECTION;
1860             setSelectionInt(Math.max(0, selectedPosition - 1));
1861             moved = true;
1862         } else if (selectedPosition < endOfRowPos && ((direction == FOCUS_LEFT && isLayoutRtl) ||
1863                 (direction == FOCUS_RIGHT && !isLayoutRtl))) {
1864             mLayoutMode = LAYOUT_MOVE_SELECTION;
1865             setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
1866             moved = true;
1867         }
1868 
1869         if (moved) {
1870             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1871             invokeOnItemScrollListener();
1872         }
1873 
1874         if (moved) {
1875             awakenScrollBars();
1876         }
1877 
1878         return moved;
1879     }
1880 
1881     /**
1882      * Goes to the next or previous item according to the order set by the
1883      * adapter.
1884      */
sequenceScroll(int direction)1885     boolean sequenceScroll(int direction) {
1886         int selectedPosition = mSelectedPosition;
1887         int numColumns = mNumColumns;
1888         int count = mItemCount;
1889 
1890         int startOfRow;
1891         int endOfRow;
1892         if (!mStackFromBottom) {
1893             startOfRow = (selectedPosition / numColumns) * numColumns;
1894             endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1895         } else {
1896             int invertedSelection = count - 1 - selectedPosition;
1897             endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1898             startOfRow = Math.max(0, endOfRow - numColumns + 1);
1899         }
1900 
1901         boolean moved = false;
1902         boolean showScroll = false;
1903         switch (direction) {
1904             case FOCUS_FORWARD:
1905                 if (selectedPosition < count - 1) {
1906                     // Move to the next item.
1907                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1908                     setSelectionInt(selectedPosition + 1);
1909                     moved = true;
1910                     // Show the scrollbar only if changing rows.
1911                     showScroll = selectedPosition == endOfRow;
1912                 }
1913                 break;
1914 
1915             case FOCUS_BACKWARD:
1916                 if (selectedPosition > 0) {
1917                     // Move to the previous item.
1918                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1919                     setSelectionInt(selectedPosition - 1);
1920                     moved = true;
1921                     // Show the scrollbar only if changing rows.
1922                     showScroll = selectedPosition == startOfRow;
1923                 }
1924                 break;
1925         }
1926 
1927         if (moved) {
1928             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1929             invokeOnItemScrollListener();
1930         }
1931 
1932         if (showScroll) {
1933             awakenScrollBars();
1934         }
1935 
1936         return moved;
1937     }
1938 
1939     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)1940     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1941         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1942 
1943         int closestChildIndex = -1;
1944         if (gainFocus && previouslyFocusedRect != null) {
1945             previouslyFocusedRect.offset(mScrollX, mScrollY);
1946 
1947             // figure out which item should be selected based on previously
1948             // focused rect
1949             Rect otherRect = mTempRect;
1950             int minDistance = Integer.MAX_VALUE;
1951             final int childCount = getChildCount();
1952             for (int i = 0; i < childCount; i++) {
1953                 // only consider view's on appropriate edge of grid
1954                 if (!isCandidateSelection(i, direction)) {
1955                     continue;
1956                 }
1957 
1958                 final View other = getChildAt(i);
1959                 other.getDrawingRect(otherRect);
1960                 offsetDescendantRectToMyCoords(other, otherRect);
1961                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1962 
1963                 if (distance < minDistance) {
1964                     minDistance = distance;
1965                     closestChildIndex = i;
1966                 }
1967             }
1968         }
1969 
1970         if (closestChildIndex >= 0) {
1971             setSelection(closestChildIndex + mFirstPosition);
1972         } else {
1973             requestLayout();
1974         }
1975     }
1976 
1977     /**
1978      * Is childIndex a candidate for next focus given the direction the focus
1979      * change is coming from?
1980      * @param childIndex The index to check.
1981      * @param direction The direction, one of
1982      *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
1983      * @return Whether childIndex is a candidate.
1984      */
isCandidateSelection(int childIndex, int direction)1985     private boolean isCandidateSelection(int childIndex, int direction) {
1986         final int count = getChildCount();
1987         final int invertedIndex = count - 1 - childIndex;
1988 
1989         int rowStart;
1990         int rowEnd;
1991 
1992         if (!mStackFromBottom) {
1993             rowStart = childIndex - (childIndex % mNumColumns);
1994             rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1995         } else {
1996             rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1997             rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1998         }
1999 
2000         switch (direction) {
2001             case View.FOCUS_RIGHT:
2002                 // coming from left, selection is only valid if it is on left
2003                 // edge
2004                 return childIndex == rowStart;
2005             case View.FOCUS_DOWN:
2006                 // coming from top; only valid if in top row
2007                 return rowStart == 0;
2008             case View.FOCUS_LEFT:
2009                 // coming from right, must be on right edge
2010                 return childIndex == rowEnd;
2011             case View.FOCUS_UP:
2012                 // coming from bottom, need to be in last row
2013                 return rowEnd == count - 1;
2014             case View.FOCUS_FORWARD:
2015                 // coming from top-left, need to be first in top row
2016                 return childIndex == rowStart && rowStart == 0;
2017             case View.FOCUS_BACKWARD:
2018                 // coming from bottom-right, need to be last in bottom row
2019                 return childIndex == rowEnd && rowEnd == count - 1;
2020             default:
2021                 throw new IllegalArgumentException("direction must be one of "
2022                         + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
2023                         + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
2024         }
2025     }
2026 
2027     /**
2028      * Set the gravity for this grid. Gravity describes how the child views
2029      * are horizontally aligned. Defaults to Gravity.LEFT
2030      *
2031      * @param gravity the gravity to apply to this grid's children
2032      *
2033      * @attr ref android.R.styleable#GridView_gravity
2034      */
setGravity(int gravity)2035     public void setGravity(int gravity) {
2036         if (mGravity != gravity) {
2037             mGravity = gravity;
2038             requestLayoutIfNecessary();
2039         }
2040     }
2041 
2042     /**
2043      * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
2044      *
2045      * @return the gravity that will be applied to this grid's children
2046      *
2047      * @attr ref android.R.styleable#GridView_gravity
2048      */
getGravity()2049     public int getGravity() {
2050         return mGravity;
2051     }
2052 
2053     /**
2054      * Set the amount of horizontal (x) spacing to place between each item
2055      * in the grid.
2056      *
2057      * @param horizontalSpacing The amount of horizontal space between items,
2058      * in pixels.
2059      *
2060      * @attr ref android.R.styleable#GridView_horizontalSpacing
2061      */
setHorizontalSpacing(int horizontalSpacing)2062     public void setHorizontalSpacing(int horizontalSpacing) {
2063         if (horizontalSpacing != mRequestedHorizontalSpacing) {
2064             mRequestedHorizontalSpacing = horizontalSpacing;
2065             requestLayoutIfNecessary();
2066         }
2067     }
2068 
2069     /**
2070      * Returns the amount of horizontal spacing currently used between each item in the grid.
2071      *
2072      * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
2073      * has been called but layout is not yet complete, this method may return a stale value.
2074      * To get the horizontal spacing that was explicitly requested use
2075      * {@link #getRequestedHorizontalSpacing()}.</p>
2076      *
2077      * @return Current horizontal spacing between each item in pixels
2078      *
2079      * @see #setHorizontalSpacing(int)
2080      * @see #getRequestedHorizontalSpacing()
2081      *
2082      * @attr ref android.R.styleable#GridView_horizontalSpacing
2083      */
getHorizontalSpacing()2084     public int getHorizontalSpacing() {
2085         return mHorizontalSpacing;
2086     }
2087 
2088     /**
2089      * Returns the requested amount of horizontal spacing between each item in the grid.
2090      *
2091      * <p>The value returned may have been supplied during inflation as part of a style,
2092      * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
2093      * If layout is not yet complete or if GridView calculated a different horizontal spacing
2094      * from what was requested, this may return a different value from
2095      * {@link #getHorizontalSpacing()}.</p>
2096      *
2097      * @return The currently requested horizontal spacing between items, in pixels
2098      *
2099      * @see #setHorizontalSpacing(int)
2100      * @see #getHorizontalSpacing()
2101      *
2102      * @attr ref android.R.styleable#GridView_horizontalSpacing
2103      */
getRequestedHorizontalSpacing()2104     public int getRequestedHorizontalSpacing() {
2105         return mRequestedHorizontalSpacing;
2106     }
2107 
2108     /**
2109      * Set the amount of vertical (y) spacing to place between each item
2110      * in the grid.
2111      *
2112      * @param verticalSpacing The amount of vertical space between items,
2113      * in pixels.
2114      *
2115      * @see #getVerticalSpacing()
2116      *
2117      * @attr ref android.R.styleable#GridView_verticalSpacing
2118      */
setVerticalSpacing(int verticalSpacing)2119     public void setVerticalSpacing(int verticalSpacing) {
2120         if (verticalSpacing != mVerticalSpacing) {
2121             mVerticalSpacing = verticalSpacing;
2122             requestLayoutIfNecessary();
2123         }
2124     }
2125 
2126     /**
2127      * Returns the amount of vertical spacing between each item in the grid.
2128      *
2129      * @return The vertical spacing between items in pixels
2130      *
2131      * @see #setVerticalSpacing(int)
2132      *
2133      * @attr ref android.R.styleable#GridView_verticalSpacing
2134      */
getVerticalSpacing()2135     public int getVerticalSpacing() {
2136         return mVerticalSpacing;
2137     }
2138 
2139     /**
2140      * Control how items are stretched to fill their space.
2141      *
2142      * @param stretchMode Either {@link #NO_STRETCH},
2143      * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2144      *
2145      * @attr ref android.R.styleable#GridView_stretchMode
2146      */
setStretchMode(@tretchMode int stretchMode)2147     public void setStretchMode(@StretchMode int stretchMode) {
2148         if (stretchMode != mStretchMode) {
2149             mStretchMode = stretchMode;
2150             requestLayoutIfNecessary();
2151         }
2152     }
2153 
2154     @StretchMode
getStretchMode()2155     public int getStretchMode() {
2156         return mStretchMode;
2157     }
2158 
2159     /**
2160      * Set the width of columns in the grid.
2161      *
2162      * @param columnWidth The column width, in pixels.
2163      *
2164      * @attr ref android.R.styleable#GridView_columnWidth
2165      */
setColumnWidth(int columnWidth)2166     public void setColumnWidth(int columnWidth) {
2167         if (columnWidth != mRequestedColumnWidth) {
2168             mRequestedColumnWidth = columnWidth;
2169             requestLayoutIfNecessary();
2170         }
2171     }
2172 
2173     /**
2174      * Return the width of a column in the grid.
2175      *
2176      * <p>This may not be valid yet if a layout is pending.</p>
2177      *
2178      * @return The column width in pixels
2179      *
2180      * @see #setColumnWidth(int)
2181      * @see #getRequestedColumnWidth()
2182      *
2183      * @attr ref android.R.styleable#GridView_columnWidth
2184      */
getColumnWidth()2185     public int getColumnWidth() {
2186         return mColumnWidth;
2187     }
2188 
2189     /**
2190      * Return the requested width of a column in the grid.
2191      *
2192      * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2193      * to retrieve the current real width of a column.</p>
2194      *
2195      * @return The requested column width in pixels
2196      *
2197      * @see #setColumnWidth(int)
2198      * @see #getColumnWidth()
2199      *
2200      * @attr ref android.R.styleable#GridView_columnWidth
2201      */
getRequestedColumnWidth()2202     public int getRequestedColumnWidth() {
2203         return mRequestedColumnWidth;
2204     }
2205 
2206     /**
2207      * Set the number of columns in the grid
2208      *
2209      * @param numColumns The desired number of columns.
2210      *
2211      * @attr ref android.R.styleable#GridView_numColumns
2212      */
setNumColumns(int numColumns)2213     public void setNumColumns(int numColumns) {
2214         if (numColumns != mRequestedNumColumns) {
2215             mRequestedNumColumns = numColumns;
2216             requestLayoutIfNecessary();
2217         }
2218     }
2219 
2220     /**
2221      * Get the number of columns in the grid.
2222      * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2223      *
2224      * @attr ref android.R.styleable#GridView_numColumns
2225      *
2226      * @see #setNumColumns(int)
2227      */
2228     @ViewDebug.ExportedProperty
getNumColumns()2229     public int getNumColumns() {
2230         return mNumColumns;
2231     }
2232 
2233     /**
2234      * Make sure views are touching the top or bottom edge, as appropriate for
2235      * our gravity
2236      */
adjustViewsUpOrDown()2237     private void adjustViewsUpOrDown() {
2238         final int childCount = getChildCount();
2239 
2240         if (childCount > 0) {
2241             int delta;
2242             View child;
2243 
2244             if (!mStackFromBottom) {
2245                 // Uh-oh -- we came up short. Slide all views up to make them
2246                 // align with the top
2247                 child = getChildAt(0);
2248                 delta = child.getTop() - mListPadding.top;
2249                 if (mFirstPosition != 0) {
2250                     // It's OK to have some space above the first item if it is
2251                     // part of the vertical spacing
2252                     delta -= mVerticalSpacing;
2253                 }
2254                 if (delta < 0) {
2255                     // We only are looking to see if we are too low, not too high
2256                     delta = 0;
2257                 }
2258             } else {
2259                 // we are too high, slide all views down to align with bottom
2260                 child = getChildAt(childCount - 1);
2261                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2262 
2263                 if (mFirstPosition + childCount < mItemCount) {
2264                     // It's OK to have some space below the last item if it is
2265                     // part of the vertical spacing
2266                     delta += mVerticalSpacing;
2267                 }
2268 
2269                 if (delta > 0) {
2270                     // We only are looking to see if we are too high, not too low
2271                     delta = 0;
2272                 }
2273             }
2274 
2275             if (delta != 0) {
2276                 offsetChildrenTopAndBottom(-delta);
2277             }
2278         }
2279     }
2280 
2281     @Override
computeVerticalScrollExtent()2282     protected int computeVerticalScrollExtent() {
2283         final int count = getChildCount();
2284         if (count > 0) {
2285             final int numColumns = mNumColumns;
2286             final int rowCount = (count + numColumns - 1) / numColumns;
2287 
2288             int extent = rowCount * 100;
2289 
2290             View view = getChildAt(0);
2291             final int top = view.getTop();
2292             int height = view.getHeight();
2293             if (height > 0) {
2294                 extent += (top * 100) / height;
2295             }
2296 
2297             view = getChildAt(count - 1);
2298             final int bottom = view.getBottom();
2299             height = view.getHeight();
2300             if (height > 0) {
2301                 extent -= ((bottom - getHeight()) * 100) / height;
2302             }
2303 
2304             return extent;
2305         }
2306         return 0;
2307     }
2308 
2309     @Override
computeVerticalScrollOffset()2310     protected int computeVerticalScrollOffset() {
2311         if (mFirstPosition >= 0 && getChildCount() > 0) {
2312             final View view = getChildAt(0);
2313             final int top = view.getTop();
2314             int height = view.getHeight();
2315             if (height > 0) {
2316                 final int numColumns = mNumColumns;
2317                 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2318                 // In case of stackFromBottom the calculation of whichRow needs
2319                 // to take into account that counting from the top the first row
2320                 // might not be entirely filled.
2321                 final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
2322                         mItemCount) : 0;
2323                 final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
2324                 return Math.max(whichRow * 100 - (top * 100) / height +
2325                         (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
2326             }
2327         }
2328         return 0;
2329     }
2330 
2331     @Override
computeVerticalScrollRange()2332     protected int computeVerticalScrollRange() {
2333         // TODO: Account for vertical spacing too
2334         final int numColumns = mNumColumns;
2335         final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2336         int result = Math.max(rowCount * 100, 0);
2337         if (mScrollY != 0) {
2338             // Compensate for overscroll
2339             result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2340         }
2341         return result;
2342     }
2343 
2344     @Override
getAccessibilityClassName()2345     public CharSequence getAccessibilityClassName() {
2346         return GridView.class.getName();
2347     }
2348 
2349     /** @hide */
2350     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)2351     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
2352         super.onInitializeAccessibilityNodeInfoInternal(info);
2353 
2354         final int columnsCount = getNumColumns();
2355         final int rowsCount = getCount() / columnsCount;
2356         final int selectionMode = getSelectionModeForAccessibility();
2357         final CollectionInfo collectionInfo = CollectionInfo.obtain(
2358                 rowsCount, columnsCount, false, selectionMode);
2359         info.setCollectionInfo(collectionInfo);
2360 
2361         if (columnsCount > 0 || rowsCount > 0) {
2362             info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
2363         }
2364     }
2365 
2366     /** @hide */
2367     @Override
performAccessibilityActionInternal(int action, Bundle arguments)2368     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
2369         if (super.performAccessibilityActionInternal(action, arguments)) {
2370             return true;
2371         }
2372 
2373         switch (action) {
2374             case R.id.accessibilityActionScrollToPosition: {
2375                 // GridView only supports scrolling in one direction, so we can
2376                 // ignore the column argument.
2377                 final int numColumns = getNumColumns();
2378                 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
2379                 final int position = Math.min(row * numColumns, getCount() - 1);
2380                 if (row >= 0) {
2381                     // The accessibility service gets data asynchronously, so
2382                     // we'll be a little lenient by clamping the last position.
2383                     smoothScrollToPosition(position);
2384                     return true;
2385                 }
2386             } break;
2387         }
2388 
2389         return false;
2390     }
2391 
2392     @Override
onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info)2393     public void onInitializeAccessibilityNodeInfoForItem(
2394             View view, int position, AccessibilityNodeInfo info) {
2395         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
2396 
2397         final int count = getCount();
2398         final int columnsCount = getNumColumns();
2399         final int rowsCount = count / columnsCount;
2400 
2401         final int row;
2402         final int column;
2403         if (!mStackFromBottom) {
2404             column = position % columnsCount;
2405             row = position / columnsCount;
2406         } else {
2407             final int invertedIndex = count - 1 - position;
2408 
2409             column = columnsCount - 1 - (invertedIndex % columnsCount);
2410             row = rowsCount - 1 - invertedIndex / columnsCount;
2411         }
2412 
2413         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
2414         final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
2415         final boolean isSelected = isItemChecked(position);
2416         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
2417                 row, 1, column, 1, isHeading, isSelected);
2418         info.setCollectionItemInfo(itemInfo);
2419     }
2420 
2421     /** @hide */
2422     @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)2423     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
2424         super.encodeProperties(encoder);
2425         encoder.addProperty("numColumns", getNumColumns());
2426     }
2427 }
2428