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