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