• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.TypedArray;
24 import android.graphics.Rect;
25 import android.os.Bundle;
26 import android.os.Trace;
27 import android.util.AttributeSet;
28 import android.util.MathUtils;
29 import android.view.Gravity;
30 import android.view.KeyEvent;
31 import android.view.SoundEffectConstants;
32 import android.view.View;
33 import android.view.ViewDebug;
34 import android.view.ViewGroup;
35 import android.view.ViewHierarchyEncoder;
36 import android.view.ViewRootImpl;
37 import android.view.accessibility.AccessibilityNodeInfo;
38 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
39 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
40 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
41 import android.view.accessibility.AccessibilityNodeProvider;
42 import android.view.animation.GridLayoutAnimationController;
43 import android.widget.RemoteViews.RemoteView;
44 
45 import com.android.internal.R;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 
50 
51 /**
52  * A view that shows items in two-dimensional scrolling grid. The items in the
53  * grid come from the {@link ListAdapter} associated with this view.
54  *
55  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
56  * View</a> guide.</p>
57  *
58  * @attr ref android.R.styleable#GridView_horizontalSpacing
59  * @attr ref android.R.styleable#GridView_verticalSpacing
60  * @attr ref android.R.styleable#GridView_stretchMode
61  * @attr ref android.R.styleable#GridView_columnWidth
62  * @attr ref android.R.styleable#GridView_numColumns
63  * @attr ref android.R.styleable#GridView_gravity
64  */
65 @RemoteView
66 public class GridView extends AbsListView {
67     /** @hide */
68     @IntDef({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(asyncImpl="setRemoteViewsAdapterAsync")
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                     // TODO: 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.
1726                     if (event.hasNoModifiers()) {
1727                         handled = resurrectSelectionIfNeeded()
1728                                 || sequenceScroll(FOCUS_FORWARD);
1729                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
1730                         handled = resurrectSelectionIfNeeded()
1731                                 || sequenceScroll(FOCUS_BACKWARD);
1732                     }
1733                     break;
1734             }
1735         }
1736 
1737         if (handled) {
1738             return true;
1739         }
1740 
1741         if (sendToTextFilter(keyCode, count, event)) {
1742             return true;
1743         }
1744 
1745         switch (action) {
1746             case KeyEvent.ACTION_DOWN:
1747                 return super.onKeyDown(keyCode, event);
1748             case KeyEvent.ACTION_UP:
1749                 return super.onKeyUp(keyCode, event);
1750             case KeyEvent.ACTION_MULTIPLE:
1751                 return super.onKeyMultiple(keyCode, count, event);
1752             default:
1753                 return false;
1754         }
1755     }
1756 
1757     /**
1758      * Scrolls up or down by the number of items currently present on screen.
1759      *
1760      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1761      * @return whether selection was moved
1762      */
pageScroll(int direction)1763     boolean pageScroll(int direction) {
1764         int nextPage = -1;
1765 
1766         if (direction == FOCUS_UP) {
1767             nextPage = Math.max(0, mSelectedPosition - getChildCount());
1768         } else if (direction == FOCUS_DOWN) {
1769             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
1770         }
1771 
1772         if (nextPage >= 0) {
1773             setSelectionInt(nextPage);
1774             invokeOnItemScrollListener();
1775             awakenScrollBars();
1776             return true;
1777         }
1778 
1779         return false;
1780     }
1781 
1782     /**
1783      * Go to the last or first item if possible.
1784      *
1785      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1786      *
1787      * @return Whether selection was moved.
1788      */
fullScroll(int direction)1789     boolean fullScroll(int direction) {
1790         boolean moved = false;
1791         if (direction == FOCUS_UP) {
1792             mLayoutMode = LAYOUT_SET_SELECTION;
1793             setSelectionInt(0);
1794             invokeOnItemScrollListener();
1795             moved = true;
1796         } else if (direction == FOCUS_DOWN) {
1797             mLayoutMode = LAYOUT_SET_SELECTION;
1798             setSelectionInt(mItemCount - 1);
1799             invokeOnItemScrollListener();
1800             moved = true;
1801         }
1802 
1803         if (moved) {
1804             awakenScrollBars();
1805         }
1806 
1807         return moved;
1808     }
1809 
1810     /**
1811      * Scrolls to the next or previous item, horizontally or vertically.
1812      *
1813      * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1814      *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1815      *
1816      * @return whether selection was moved
1817      */
arrowScroll(int direction)1818     boolean arrowScroll(int direction) {
1819         final int selectedPosition = mSelectedPosition;
1820         final int numColumns = mNumColumns;
1821 
1822         int startOfRowPos;
1823         int endOfRowPos;
1824 
1825         boolean moved = false;
1826 
1827         if (!mStackFromBottom) {
1828             startOfRowPos = (selectedPosition / numColumns) * numColumns;
1829             endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1830         } else {
1831             final int invertedSelection = mItemCount - 1 - selectedPosition;
1832             endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1833             startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1834         }
1835 
1836         switch (direction) {
1837             case FOCUS_UP:
1838                 if (startOfRowPos > 0) {
1839                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1840                     setSelectionInt(Math.max(0, selectedPosition - numColumns));
1841                     moved = true;
1842                 }
1843                 break;
1844             case FOCUS_DOWN:
1845                 if (endOfRowPos < mItemCount - 1) {
1846                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1847                     setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1848                     moved = true;
1849                 }
1850                 break;
1851         }
1852 
1853         final boolean isLayoutRtl = isLayoutRtl();
1854         if (selectedPosition > startOfRowPos && ((direction == FOCUS_LEFT && !isLayoutRtl) ||
1855                 (direction == FOCUS_RIGHT && isLayoutRtl))) {
1856             mLayoutMode = LAYOUT_MOVE_SELECTION;
1857             setSelectionInt(Math.max(0, selectedPosition - 1));
1858             moved = true;
1859         } else if (selectedPosition < endOfRowPos && ((direction == FOCUS_LEFT && isLayoutRtl) ||
1860                 (direction == FOCUS_RIGHT && !isLayoutRtl))) {
1861             mLayoutMode = LAYOUT_MOVE_SELECTION;
1862             setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
1863             moved = true;
1864         }
1865 
1866         if (moved) {
1867             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1868             invokeOnItemScrollListener();
1869         }
1870 
1871         if (moved) {
1872             awakenScrollBars();
1873         }
1874 
1875         return moved;
1876     }
1877 
1878     /**
1879      * Goes to the next or previous item according to the order set by the
1880      * adapter.
1881      */
sequenceScroll(int direction)1882     boolean sequenceScroll(int direction) {
1883         int selectedPosition = mSelectedPosition;
1884         int numColumns = mNumColumns;
1885         int count = mItemCount;
1886 
1887         int startOfRow;
1888         int endOfRow;
1889         if (!mStackFromBottom) {
1890             startOfRow = (selectedPosition / numColumns) * numColumns;
1891             endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1892         } else {
1893             int invertedSelection = count - 1 - selectedPosition;
1894             endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1895             startOfRow = Math.max(0, endOfRow - numColumns + 1);
1896         }
1897 
1898         boolean moved = false;
1899         boolean showScroll = false;
1900         switch (direction) {
1901             case FOCUS_FORWARD:
1902                 if (selectedPosition < count - 1) {
1903                     // Move to the next item.
1904                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1905                     setSelectionInt(selectedPosition + 1);
1906                     moved = true;
1907                     // Show the scrollbar only if changing rows.
1908                     showScroll = selectedPosition == endOfRow;
1909                 }
1910                 break;
1911 
1912             case FOCUS_BACKWARD:
1913                 if (selectedPosition > 0) {
1914                     // Move to the previous item.
1915                     mLayoutMode = LAYOUT_MOVE_SELECTION;
1916                     setSelectionInt(selectedPosition - 1);
1917                     moved = true;
1918                     // Show the scrollbar only if changing rows.
1919                     showScroll = selectedPosition == startOfRow;
1920                 }
1921                 break;
1922         }
1923 
1924         if (moved) {
1925             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1926             invokeOnItemScrollListener();
1927         }
1928 
1929         if (showScroll) {
1930             awakenScrollBars();
1931         }
1932 
1933         return moved;
1934     }
1935 
1936     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)1937     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1938         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1939 
1940         int closestChildIndex = -1;
1941         if (gainFocus && previouslyFocusedRect != null) {
1942             previouslyFocusedRect.offset(mScrollX, mScrollY);
1943 
1944             // figure out which item should be selected based on previously
1945             // focused rect
1946             Rect otherRect = mTempRect;
1947             int minDistance = Integer.MAX_VALUE;
1948             final int childCount = getChildCount();
1949             for (int i = 0; i < childCount; i++) {
1950                 // only consider view's on appropriate edge of grid
1951                 if (!isCandidateSelection(i, direction)) {
1952                     continue;
1953                 }
1954 
1955                 final View other = getChildAt(i);
1956                 other.getDrawingRect(otherRect);
1957                 offsetDescendantRectToMyCoords(other, otherRect);
1958                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1959 
1960                 if (distance < minDistance) {
1961                     minDistance = distance;
1962                     closestChildIndex = i;
1963                 }
1964             }
1965         }
1966 
1967         if (closestChildIndex >= 0) {
1968             setSelection(closestChildIndex + mFirstPosition);
1969         } else {
1970             requestLayout();
1971         }
1972     }
1973 
1974     /**
1975      * Is childIndex a candidate for next focus given the direction the focus
1976      * change is coming from?
1977      * @param childIndex The index to check.
1978      * @param direction The direction, one of
1979      *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
1980      * @return Whether childIndex is a candidate.
1981      */
isCandidateSelection(int childIndex, int direction)1982     private boolean isCandidateSelection(int childIndex, int direction) {
1983         final int count = getChildCount();
1984         final int invertedIndex = count - 1 - childIndex;
1985 
1986         int rowStart;
1987         int rowEnd;
1988 
1989         if (!mStackFromBottom) {
1990             rowStart = childIndex - (childIndex % mNumColumns);
1991             rowEnd = Math.min(rowStart + mNumColumns - 1, count);
1992         } else {
1993             rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1994             rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1995         }
1996 
1997         switch (direction) {
1998             case View.FOCUS_RIGHT:
1999                 // coming from left, selection is only valid if it is on left
2000                 // edge
2001                 return childIndex == rowStart;
2002             case View.FOCUS_DOWN:
2003                 // coming from top; only valid if in top row
2004                 return rowStart == 0;
2005             case View.FOCUS_LEFT:
2006                 // coming from right, must be on right edge
2007                 return childIndex == rowEnd;
2008             case View.FOCUS_UP:
2009                 // coming from bottom, need to be in last row
2010                 return rowEnd == count - 1;
2011             case View.FOCUS_FORWARD:
2012                 // coming from top-left, need to be first in top row
2013                 return childIndex == rowStart && rowStart == 0;
2014             case View.FOCUS_BACKWARD:
2015                 // coming from bottom-right, need to be last in bottom row
2016                 return childIndex == rowEnd && rowEnd == count - 1;
2017             default:
2018                 throw new IllegalArgumentException("direction must be one of "
2019                         + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
2020                         + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
2021         }
2022     }
2023 
2024     /**
2025      * Set the gravity for this grid. Gravity describes how the child views
2026      * are horizontally aligned. Defaults to Gravity.LEFT
2027      *
2028      * @param gravity the gravity to apply to this grid's children
2029      *
2030      * @attr ref android.R.styleable#GridView_gravity
2031      */
setGravity(int gravity)2032     public void setGravity(int gravity) {
2033         if (mGravity != gravity) {
2034             mGravity = gravity;
2035             requestLayoutIfNecessary();
2036         }
2037     }
2038 
2039     /**
2040      * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
2041      *
2042      * @return the gravity that will be applied to this grid's children
2043      *
2044      * @attr ref android.R.styleable#GridView_gravity
2045      */
getGravity()2046     public int getGravity() {
2047         return mGravity;
2048     }
2049 
2050     /**
2051      * Set the amount of horizontal (x) spacing to place between each item
2052      * in the grid.
2053      *
2054      * @param horizontalSpacing The amount of horizontal space between items,
2055      * in pixels.
2056      *
2057      * @attr ref android.R.styleable#GridView_horizontalSpacing
2058      */
setHorizontalSpacing(int horizontalSpacing)2059     public void setHorizontalSpacing(int horizontalSpacing) {
2060         if (horizontalSpacing != mRequestedHorizontalSpacing) {
2061             mRequestedHorizontalSpacing = horizontalSpacing;
2062             requestLayoutIfNecessary();
2063         }
2064     }
2065 
2066     /**
2067      * Returns the amount of horizontal spacing currently used between each item in the grid.
2068      *
2069      * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
2070      * has been called but layout is not yet complete, this method may return a stale value.
2071      * To get the horizontal spacing that was explicitly requested use
2072      * {@link #getRequestedHorizontalSpacing()}.</p>
2073      *
2074      * @return Current horizontal spacing between each item in pixels
2075      *
2076      * @see #setHorizontalSpacing(int)
2077      * @see #getRequestedHorizontalSpacing()
2078      *
2079      * @attr ref android.R.styleable#GridView_horizontalSpacing
2080      */
getHorizontalSpacing()2081     public int getHorizontalSpacing() {
2082         return mHorizontalSpacing;
2083     }
2084 
2085     /**
2086      * Returns the requested amount of horizontal spacing between each item in the grid.
2087      *
2088      * <p>The value returned may have been supplied during inflation as part of a style,
2089      * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
2090      * If layout is not yet complete or if GridView calculated a different horizontal spacing
2091      * from what was requested, this may return a different value from
2092      * {@link #getHorizontalSpacing()}.</p>
2093      *
2094      * @return The currently requested horizontal spacing between items, in pixels
2095      *
2096      * @see #setHorizontalSpacing(int)
2097      * @see #getHorizontalSpacing()
2098      *
2099      * @attr ref android.R.styleable#GridView_horizontalSpacing
2100      */
getRequestedHorizontalSpacing()2101     public int getRequestedHorizontalSpacing() {
2102         return mRequestedHorizontalSpacing;
2103     }
2104 
2105     /**
2106      * Set the amount of vertical (y) spacing to place between each item
2107      * in the grid.
2108      *
2109      * @param verticalSpacing The amount of vertical space between items,
2110      * in pixels.
2111      *
2112      * @see #getVerticalSpacing()
2113      *
2114      * @attr ref android.R.styleable#GridView_verticalSpacing
2115      */
setVerticalSpacing(int verticalSpacing)2116     public void setVerticalSpacing(int verticalSpacing) {
2117         if (verticalSpacing != mVerticalSpacing) {
2118             mVerticalSpacing = verticalSpacing;
2119             requestLayoutIfNecessary();
2120         }
2121     }
2122 
2123     /**
2124      * Returns the amount of vertical spacing between each item in the grid.
2125      *
2126      * @return The vertical spacing between items in pixels
2127      *
2128      * @see #setVerticalSpacing(int)
2129      *
2130      * @attr ref android.R.styleable#GridView_verticalSpacing
2131      */
getVerticalSpacing()2132     public int getVerticalSpacing() {
2133         return mVerticalSpacing;
2134     }
2135 
2136     /**
2137      * Control how items are stretched to fill their space.
2138      *
2139      * @param stretchMode Either {@link #NO_STRETCH},
2140      * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2141      *
2142      * @attr ref android.R.styleable#GridView_stretchMode
2143      */
setStretchMode(@tretchMode int stretchMode)2144     public void setStretchMode(@StretchMode int stretchMode) {
2145         if (stretchMode != mStretchMode) {
2146             mStretchMode = stretchMode;
2147             requestLayoutIfNecessary();
2148         }
2149     }
2150 
2151     @StretchMode
getStretchMode()2152     public int getStretchMode() {
2153         return mStretchMode;
2154     }
2155 
2156     /**
2157      * Set the width of columns in the grid.
2158      *
2159      * @param columnWidth The column width, in pixels.
2160      *
2161      * @attr ref android.R.styleable#GridView_columnWidth
2162      */
setColumnWidth(int columnWidth)2163     public void setColumnWidth(int columnWidth) {
2164         if (columnWidth != mRequestedColumnWidth) {
2165             mRequestedColumnWidth = columnWidth;
2166             requestLayoutIfNecessary();
2167         }
2168     }
2169 
2170     /**
2171      * Return the width of a column in the grid.
2172      *
2173      * <p>This may not be valid yet if a layout is pending.</p>
2174      *
2175      * @return The column width in pixels
2176      *
2177      * @see #setColumnWidth(int)
2178      * @see #getRequestedColumnWidth()
2179      *
2180      * @attr ref android.R.styleable#GridView_columnWidth
2181      */
getColumnWidth()2182     public int getColumnWidth() {
2183         return mColumnWidth;
2184     }
2185 
2186     /**
2187      * Return the requested width of a column in the grid.
2188      *
2189      * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2190      * to retrieve the current real width of a column.</p>
2191      *
2192      * @return The requested column width in pixels
2193      *
2194      * @see #setColumnWidth(int)
2195      * @see #getColumnWidth()
2196      *
2197      * @attr ref android.R.styleable#GridView_columnWidth
2198      */
getRequestedColumnWidth()2199     public int getRequestedColumnWidth() {
2200         return mRequestedColumnWidth;
2201     }
2202 
2203     /**
2204      * Set the number of columns in the grid
2205      *
2206      * @param numColumns The desired number of columns.
2207      *
2208      * @attr ref android.R.styleable#GridView_numColumns
2209      */
setNumColumns(int numColumns)2210     public void setNumColumns(int numColumns) {
2211         if (numColumns != mRequestedNumColumns) {
2212             mRequestedNumColumns = numColumns;
2213             requestLayoutIfNecessary();
2214         }
2215     }
2216 
2217     /**
2218      * Get the number of columns in the grid.
2219      * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2220      *
2221      * @attr ref android.R.styleable#GridView_numColumns
2222      *
2223      * @see #setNumColumns(int)
2224      */
2225     @ViewDebug.ExportedProperty
getNumColumns()2226     public int getNumColumns() {
2227         return mNumColumns;
2228     }
2229 
2230     /**
2231      * Make sure views are touching the top or bottom edge, as appropriate for
2232      * our gravity
2233      */
adjustViewsUpOrDown()2234     private void adjustViewsUpOrDown() {
2235         final int childCount = getChildCount();
2236 
2237         if (childCount > 0) {
2238             int delta;
2239             View child;
2240 
2241             if (!mStackFromBottom) {
2242                 // Uh-oh -- we came up short. Slide all views up to make them
2243                 // align with the top
2244                 child = getChildAt(0);
2245                 delta = child.getTop() - mListPadding.top;
2246                 if (mFirstPosition != 0) {
2247                     // It's OK to have some space above the first item if it is
2248                     // part of the vertical spacing
2249                     delta -= mVerticalSpacing;
2250                 }
2251                 if (delta < 0) {
2252                     // We only are looking to see if we are too low, not too high
2253                     delta = 0;
2254                 }
2255             } else {
2256                 // we are too high, slide all views down to align with bottom
2257                 child = getChildAt(childCount - 1);
2258                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2259 
2260                 if (mFirstPosition + childCount < mItemCount) {
2261                     // It's OK to have some space below the last item if it is
2262                     // part of the vertical spacing
2263                     delta += mVerticalSpacing;
2264                 }
2265 
2266                 if (delta > 0) {
2267                     // We only are looking to see if we are too high, not too low
2268                     delta = 0;
2269                 }
2270             }
2271 
2272             if (delta != 0) {
2273                 offsetChildrenTopAndBottom(-delta);
2274             }
2275         }
2276     }
2277 
2278     @Override
computeVerticalScrollExtent()2279     protected int computeVerticalScrollExtent() {
2280         final int count = getChildCount();
2281         if (count > 0) {
2282             final int numColumns = mNumColumns;
2283             final int rowCount = (count + numColumns - 1) / numColumns;
2284 
2285             int extent = rowCount * 100;
2286 
2287             View view = getChildAt(0);
2288             final int top = view.getTop();
2289             int height = view.getHeight();
2290             if (height > 0) {
2291                 extent += (top * 100) / height;
2292             }
2293 
2294             view = getChildAt(count - 1);
2295             final int bottom = view.getBottom();
2296             height = view.getHeight();
2297             if (height > 0) {
2298                 extent -= ((bottom - getHeight()) * 100) / height;
2299             }
2300 
2301             return extent;
2302         }
2303         return 0;
2304     }
2305 
2306     @Override
computeVerticalScrollOffset()2307     protected int computeVerticalScrollOffset() {
2308         if (mFirstPosition >= 0 && getChildCount() > 0) {
2309             final View view = getChildAt(0);
2310             final int top = view.getTop();
2311             int height = view.getHeight();
2312             if (height > 0) {
2313                 final int numColumns = mNumColumns;
2314                 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2315                 // In case of stackFromBottom the calculation of whichRow needs
2316                 // to take into account that counting from the top the first row
2317                 // might not be entirely filled.
2318                 final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
2319                         mItemCount) : 0;
2320                 final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
2321                 return Math.max(whichRow * 100 - (top * 100) / height +
2322                         (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
2323             }
2324         }
2325         return 0;
2326     }
2327 
2328     @Override
computeVerticalScrollRange()2329     protected int computeVerticalScrollRange() {
2330         // TODO: Account for vertical spacing too
2331         final int numColumns = mNumColumns;
2332         final int rowCount = (mItemCount + numColumns - 1) / numColumns;
2333         int result = Math.max(rowCount * 100, 0);
2334         if (mScrollY != 0) {
2335             // Compensate for overscroll
2336             result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2337         }
2338         return result;
2339     }
2340 
2341     @Override
getAccessibilityClassName()2342     public CharSequence getAccessibilityClassName() {
2343         return GridView.class.getName();
2344     }
2345 
2346     /** @hide */
2347     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)2348     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
2349         super.onInitializeAccessibilityNodeInfoInternal(info);
2350 
2351         final int columnsCount = getNumColumns();
2352         final int rowsCount = getCount() / columnsCount;
2353         final int selectionMode = getSelectionModeForAccessibility();
2354         final CollectionInfo collectionInfo = CollectionInfo.obtain(
2355                 rowsCount, columnsCount, false, selectionMode);
2356         info.setCollectionInfo(collectionInfo);
2357 
2358         if (columnsCount > 0 || rowsCount > 0) {
2359             info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
2360         }
2361     }
2362 
2363     /** @hide */
2364     @Override
performAccessibilityActionInternal(int action, Bundle arguments)2365     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
2366         if (super.performAccessibilityActionInternal(action, arguments)) {
2367             return true;
2368         }
2369 
2370         switch (action) {
2371             case R.id.accessibilityActionScrollToPosition: {
2372                 // GridView only supports scrolling in one direction, so we can
2373                 // ignore the column argument.
2374                 final int numColumns = getNumColumns();
2375                 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
2376                 final int position = Math.min(row * numColumns, getCount() - 1);
2377                 if (row >= 0) {
2378                     // The accessibility service gets data asynchronously, so
2379                     // we'll be a little lenient by clamping the last position.
2380                     smoothScrollToPosition(position);
2381                     return true;
2382                 }
2383             } break;
2384         }
2385 
2386         return false;
2387     }
2388 
2389     @Override
onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info)2390     public void onInitializeAccessibilityNodeInfoForItem(
2391             View view, int position, AccessibilityNodeInfo info) {
2392         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
2393 
2394         final int count = getCount();
2395         final int columnsCount = getNumColumns();
2396         final int rowsCount = count / columnsCount;
2397 
2398         final int row;
2399         final int column;
2400         if (!mStackFromBottom) {
2401             column = position % columnsCount;
2402             row = position / columnsCount;
2403         } else {
2404             final int invertedIndex = count - 1 - position;
2405 
2406             column = columnsCount - 1 - (invertedIndex % columnsCount);
2407             row = rowsCount - 1 - invertedIndex / columnsCount;
2408         }
2409 
2410         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
2411         final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
2412         final boolean isSelected = isItemChecked(position);
2413         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
2414                 row, 1, column, 1, isHeading, isSelected);
2415         info.setCollectionItemInfo(itemInfo);
2416     }
2417 
2418     /** @hide */
2419     @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)2420     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
2421         super.encodeProperties(encoder);
2422         encoder.addProperty("numColumns", getNumColumns());
2423     }
2424 }
2425