blob: a7d546af071cbb2cfcac17d586f2641fc8a6572c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.widget;
18
19import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
22import android.graphics.Rect;
Romain Guy5fade8c2013-07-10 16:36:18 -070023import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.util.AttributeSet;
25import android.view.Gravity;
26import android.view.KeyEvent;
Winson Chung499cb9f2010-07-16 11:18:17 -070027import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.View;
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -070029import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080031import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.view.animation.GridLayoutAnimationController;
Winson Chung499cb9f2010-07-16 11:18:17 -070034import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
36
37/**
38 * A view that shows items in two-dimensional scrolling grid. The items in the
39 * grid come from the {@link ListAdapter} associated with this view.
Scott Main41ec6532010-08-19 16:57:07 -070040 *
Scott Main4c359b72012-07-24 15:51:27 -070041 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
42 * View</a> guide.</p>
Romain Guy84c6b952011-02-22 11:15:42 -080043 *
44 * @attr ref android.R.styleable#GridView_horizontalSpacing
45 * @attr ref android.R.styleable#GridView_verticalSpacing
46 * @attr ref android.R.styleable#GridView_stretchMode
47 * @attr ref android.R.styleable#GridView_columnWidth
48 * @attr ref android.R.styleable#GridView_numColumns
49 * @attr ref android.R.styleable#GridView_gravity
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
Winson Chung499cb9f2010-07-16 11:18:17 -070051@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052public class GridView extends AbsListView {
Romain Guy84c6b952011-02-22 11:15:42 -080053 /**
54 * Disables stretching.
55 *
56 * @see #setStretchMode(int)
57 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 public static final int NO_STRETCH = 0;
Romain Guy84c6b952011-02-22 11:15:42 -080059 /**
60 * Stretches the spacing between columns.
61 *
62 * @see #setStretchMode(int)
63 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 public static final int STRETCH_SPACING = 1;
Romain Guy84c6b952011-02-22 11:15:42 -080065 /**
66 * Stretches columns.
67 *
68 * @see #setStretchMode(int)
69 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 public static final int STRETCH_COLUMN_WIDTH = 2;
Romain Guy84c6b952011-02-22 11:15:42 -080071 /**
72 * Stretches the spacing between columns. The spacing is uniform.
73 *
74 * @see #setStretchMode(int)
75 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 public static final int STRETCH_SPACING_UNIFORM = 3;
Romain Guy84c6b952011-02-22 11:15:42 -080077
78 /**
79 * Creates as many columns as can fit on screen.
80 *
81 * @see #setNumColumns(int)
82 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 public static final int AUTO_FIT = -1;
84
85 private int mNumColumns = AUTO_FIT;
86
87 private int mHorizontalSpacing = 0;
88 private int mRequestedHorizontalSpacing;
89 private int mVerticalSpacing = 0;
90 private int mStretchMode = STRETCH_COLUMN_WIDTH;
91 private int mColumnWidth;
92 private int mRequestedColumnWidth;
93 private int mRequestedNumColumns;
94
95 private View mReferenceView = null;
96 private View mReferenceViewInSelectedRow = null;
97
Fabrice Di Meglioa5987202012-06-08 15:22:20 -070098 private int mGravity = Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099
100 private final Rect mTempRect = new Rect();
101
102 public GridView(Context context) {
Adam Powell48774532012-03-12 13:41:37 -0700103 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 }
105
106 public GridView(Context context, AttributeSet attrs) {
107 this(context, attrs, com.android.internal.R.attr.gridViewStyle);
108 }
109
110 public GridView(Context context, AttributeSet attrs, int defStyle) {
111 super(context, attrs, defStyle);
112
113 TypedArray a = context.obtainStyledAttributes(attrs,
114 com.android.internal.R.styleable.GridView, defStyle, 0);
115
116 int hSpacing = a.getDimensionPixelOffset(
117 com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
118 setHorizontalSpacing(hSpacing);
119
120 int vSpacing = a.getDimensionPixelOffset(
121 com.android.internal.R.styleable.GridView_verticalSpacing, 0);
122 setVerticalSpacing(vSpacing);
123
124 int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
125 if (index >= 0) {
126 setStretchMode(index);
127 }
128
129 int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
130 if (columnWidth > 0) {
131 setColumnWidth(columnWidth);
132 }
133
134 int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
135 setNumColumns(numColumns);
136
137 index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
138 if (index >= 0) {
139 setGravity(index);
140 }
141
142 a.recycle();
143 }
144
145 @Override
146 public ListAdapter getAdapter() {
147 return mAdapter;
148 }
149
150 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700151 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
152 * through the specified intent.
153 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
154 */
155 @android.view.RemotableViewMethod
156 public void setRemoteViewsAdapter(Intent intent) {
157 super.setRemoteViewsAdapter(intent);
158 }
159
160 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 * Sets the data behind this GridView.
162 *
163 * @param adapter the adapter providing the grid's data
164 */
165 @Override
166 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700167 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 mAdapter.unregisterDataSetObserver(mDataSetObserver);
169 }
170
171 resetList();
172 mRecycler.clear();
173 mAdapter = adapter;
174
175 mOldSelectedPosition = INVALID_POSITION;
176 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700177
178 // AbsListView#setAdapter will update choice mode states.
179 super.setAdapter(adapter);
180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 if (mAdapter != null) {
182 mOldItemCount = mItemCount;
183 mItemCount = mAdapter.getCount();
184 mDataChanged = true;
185 checkFocus();
186
187 mDataSetObserver = new AdapterDataSetObserver();
188 mAdapter.registerDataSetObserver(mDataSetObserver);
189
190 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
191
192 int position;
193 if (mStackFromBottom) {
194 position = lookForSelectablePosition(mItemCount - 1, false);
195 } else {
196 position = lookForSelectablePosition(0, true);
197 }
198 setSelectedPositionInt(position);
199 setNextSelectedPositionInt(position);
200 checkSelectionChanged();
201 } else {
202 checkFocus();
203 // Nothing selected
204 checkSelectionChanged();
205 }
206
207 requestLayout();
208 }
209
210 @Override
211 int lookForSelectablePosition(int position, boolean lookDown) {
212 final ListAdapter adapter = mAdapter;
213 if (adapter == null || isInTouchMode()) {
214 return INVALID_POSITION;
215 }
216
217 if (position < 0 || position >= mItemCount) {
218 return INVALID_POSITION;
219 }
220 return position;
221 }
222
223 /**
224 * {@inheritDoc}
225 */
226 @Override
227 void fillGap(boolean down) {
228 final int numColumns = mNumColumns;
229 final int verticalSpacing = mVerticalSpacing;
230
231 final int count = getChildCount();
232
233 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800234 int paddingTop = 0;
235 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
236 paddingTop = getListPaddingTop();
237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 final int startOffset = count > 0 ?
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800239 getChildAt(count - 1).getBottom() + verticalSpacing : paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 int position = mFirstPosition + count;
241 if (mStackFromBottom) {
242 position += numColumns - 1;
243 }
244 fillDown(position, startOffset);
245 correctTooHigh(numColumns, verticalSpacing, getChildCount());
246 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800247 int paddingBottom = 0;
248 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
249 paddingBottom = getListPaddingBottom();
250 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 final int startOffset = count > 0 ?
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800252 getChildAt(0).getTop() - verticalSpacing : getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 int position = mFirstPosition;
254 if (!mStackFromBottom) {
255 position -= numColumns;
256 } else {
257 position--;
258 }
259 fillUp(position, startOffset);
260 correctTooLow(numColumns, verticalSpacing, getChildCount());
261 }
262 }
263
264 /**
265 * Fills the list from pos down to the end of the list view.
266 *
267 * @param pos The first position to put in the list
268 *
269 * @param nextTop The location where the top of the item associated with pos
270 * should be drawn
271 *
272 * @return The view that is currently selected, if it happens to be in the
273 * range that we draw.
274 */
275 private View fillDown(int pos, int nextTop) {
276 View selectedView = null;
277
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800278 int end = (mBottom - mTop);
279 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
280 end -= mListPadding.bottom;
281 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282
283 while (nextTop < end && pos < mItemCount) {
284 View temp = makeRow(pos, nextTop, true);
285 if (temp != null) {
286 selectedView = temp;
287 }
288
Romain Guy8bcdc072009-09-29 15:17:47 -0700289 // mReferenceView will change with each call to makeRow()
290 // do not cache in a local variable outside of this loop
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 nextTop = mReferenceView.getBottom() + mVerticalSpacing;
292
293 pos += mNumColumns;
294 }
295
Adam Cohenb9673922012-01-05 13:58:47 -0800296 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 return selectedView;
298 }
299
300 private View makeRow(int startPos, int y, boolean flow) {
301 final int columnWidth = mColumnWidth;
302 final int horizontalSpacing = mHorizontalSpacing;
303
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700304 final boolean isLayoutRtl = isLayoutRtl();
305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 int last;
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700307 int nextLeft;
308
309 if (isLayoutRtl) {
310 nextLeft = getWidth() - mListPadding.right - columnWidth -
311 ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
312 } else {
313 nextLeft = mListPadding.left +
314 ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
315 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316
317 if (!mStackFromBottom) {
318 last = Math.min(startPos + mNumColumns, mItemCount);
319 } else {
320 last = startPos + 1;
321 startPos = Math.max(0, startPos - mNumColumns + 1);
322
323 if (last - startPos < mNumColumns) {
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700324 final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
325 nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 }
327 }
328
329 View selectedView = null;
330
331 final boolean hasFocus = shouldShowSelector();
332 final boolean inClick = touchModeDrawsInPressedState();
333 final int selectedPosition = mSelectedPosition;
334
Romain Guy8bcdc072009-09-29 15:17:47 -0700335 View child = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 for (int pos = startPos; pos < last; pos++) {
337 // is this the selected item?
338 boolean selected = pos == selectedPosition;
339 // does the list view have focus or contain focus
340
341 final int where = flow ? -1 : pos - startPos;
Romain Guy8bcdc072009-09-29 15:17:47 -0700342 child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700344 nextLeft += (isLayoutRtl ? -1 : +1) * columnWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 if (pos < last - 1) {
346 nextLeft += horizontalSpacing;
347 }
348
349 if (selected && (hasFocus || inClick)) {
350 selectedView = child;
351 }
352 }
353
Romain Guy8bcdc072009-09-29 15:17:47 -0700354 mReferenceView = child;
355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 if (selectedView != null) {
357 mReferenceViewInSelectedRow = mReferenceView;
358 }
359
360 return selectedView;
361 }
362
363 /**
364 * Fills the list from pos up to the top of the list view.
365 *
366 * @param pos The first position to put in the list
367 *
368 * @param nextBottom The location where the bottom of the item associated
369 * with pos should be drawn
370 *
371 * @return The view that is currently selected
372 */
373 private View fillUp(int pos, int nextBottom) {
374 View selectedView = null;
375
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800376 int end = 0;
377 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
378 end = mListPadding.top;
379 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380
381 while (nextBottom > end && pos >= 0) {
382
383 View temp = makeRow(pos, nextBottom, false);
384 if (temp != null) {
385 selectedView = temp;
386 }
387
388 nextBottom = mReferenceView.getTop() - mVerticalSpacing;
389
390 mFirstPosition = pos;
391
392 pos -= mNumColumns;
393 }
394
395 if (mStackFromBottom) {
396 mFirstPosition = Math.max(0, pos + 1);
397 }
398
Adam Cohenb9673922012-01-05 13:58:47 -0800399 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 return selectedView;
401 }
402
403 /**
404 * Fills the list from top to bottom, starting with mFirstPosition
405 *
406 * @param nextTop The location where the top of the first item should be
407 * drawn
408 *
409 * @return The view that is currently selected
410 */
411 private View fillFromTop(int nextTop) {
412 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
413 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
414 if (mFirstPosition < 0) {
415 mFirstPosition = 0;
416 }
417 mFirstPosition -= mFirstPosition % mNumColumns;
418 return fillDown(mFirstPosition, nextTop);
419 }
420
421 private View fillFromBottom(int lastPosition, int nextBottom) {
422 lastPosition = Math.max(lastPosition, mSelectedPosition);
423 lastPosition = Math.min(lastPosition, mItemCount - 1);
424
425 final int invertedPosition = mItemCount - 1 - lastPosition;
426 lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
427
428 return fillUp(lastPosition, nextBottom);
429 }
430
431 private View fillSelection(int childrenTop, int childrenBottom) {
432 final int selectedPosition = reconcileSelectedPosition();
433 final int numColumns = mNumColumns;
434 final int verticalSpacing = mVerticalSpacing;
435
436 int rowStart;
437 int rowEnd = -1;
438
439 if (!mStackFromBottom) {
440 rowStart = selectedPosition - (selectedPosition % numColumns);
441 } else {
442 final int invertedSelection = mItemCount - 1 - selectedPosition;
443
444 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
445 rowStart = Math.max(0, rowEnd - numColumns + 1);
446 }
447
448 final int fadingEdgeLength = getVerticalFadingEdgeLength();
449 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
450
451 final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
452 mFirstPosition = rowStart;
453
454 final View referenceView = mReferenceView;
455
456 if (!mStackFromBottom) {
457 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
458 pinToBottom(childrenBottom);
459 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
460 adjustViewsUpOrDown();
461 } else {
462 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
463 fadingEdgeLength, numColumns, rowStart);
464 final int offset = bottomSelectionPixel - referenceView.getBottom();
465 offsetChildrenTopAndBottom(offset);
466 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
467 pinToTop(childrenTop);
468 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
469 adjustViewsUpOrDown();
470 }
471
472 return sel;
473 }
474
475 private void pinToTop(int childrenTop) {
476 if (mFirstPosition == 0) {
477 final int top = getChildAt(0).getTop();
478 final int offset = childrenTop - top;
479 if (offset < 0) {
480 offsetChildrenTopAndBottom(offset);
481 }
482 }
483 }
484
485 private void pinToBottom(int childrenBottom) {
486 final int count = getChildCount();
487 if (mFirstPosition + count == mItemCount) {
488 final int bottom = getChildAt(count - 1).getBottom();
489 final int offset = childrenBottom - bottom;
490 if (offset > 0) {
491 offsetChildrenTopAndBottom(offset);
492 }
493 }
494 }
495
496 @Override
497 int findMotionRow(int y) {
498 final int childCount = getChildCount();
499 if (childCount > 0) {
500
501 final int numColumns = mNumColumns;
502 if (!mStackFromBottom) {
503 for (int i = 0; i < childCount; i += numColumns) {
504 if (y <= getChildAt(i).getBottom()) {
505 return mFirstPosition + i;
506 }
507 }
508 } else {
509 for (int i = childCount - 1; i >= 0; i -= numColumns) {
510 if (y >= getChildAt(i).getTop()) {
511 return mFirstPosition + i;
512 }
513 }
514 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 }
516 return INVALID_POSITION;
517 }
518
519 /**
520 * Layout during a scroll that results from tracking motion events. Places
521 * the mMotionPosition view at the offset specified by mMotionViewTop, and
522 * then build surrounding views from there.
523 *
524 * @param position the position at which to start filling
525 * @param top the top of the view at that position
526 * @return The selected view, or null if the selected view is outside the
527 * visible area.
528 */
529 private View fillSpecific(int position, int top) {
530 final int numColumns = mNumColumns;
531
532 int motionRowStart;
533 int motionRowEnd = -1;
534
535 if (!mStackFromBottom) {
536 motionRowStart = position - (position % numColumns);
537 } else {
538 final int invertedSelection = mItemCount - 1 - position;
539
540 motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
541 motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
542 }
543
544 final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
545
546 // Possibly changed again in fillUp if we add rows above this one.
547 mFirstPosition = motionRowStart;
548
549 final View referenceView = mReferenceView;
Romain Guy8bcdc072009-09-29 15:17:47 -0700550 // We didn't have anything to layout, bail out
551 if (referenceView == null) {
552 return null;
553 }
554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 final int verticalSpacing = mVerticalSpacing;
556
557 View above;
558 View below;
559
560 if (!mStackFromBottom) {
561 above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
562 adjustViewsUpOrDown();
563 below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
564 // Check if we have dragged the bottom of the grid too high
565 final int childCount = getChildCount();
566 if (childCount > 0) {
567 correctTooHigh(numColumns, verticalSpacing, childCount);
568 }
569 } else {
570 below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
571 adjustViewsUpOrDown();
572 above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
573 // Check if we have dragged the bottom of the grid too high
574 final int childCount = getChildCount();
575 if (childCount > 0) {
576 correctTooLow(numColumns, verticalSpacing, childCount);
577 }
578 }
579
580 if (temp != null) {
581 return temp;
582 } else if (above != null) {
583 return above;
584 } else {
585 return below;
586 }
587 }
588
589 private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
590 // First see if the last item is visible
591 final int lastPosition = mFirstPosition + childCount - 1;
592 if (lastPosition == mItemCount - 1 && childCount > 0) {
593 // Get the last child ...
594 final View lastChild = getChildAt(childCount - 1);
595
596 // ... and its bottom edge
597 final int lastBottom = lastChild.getBottom();
598 // This is bottom of our drawable area
599 final int end = (mBottom - mTop) - mListPadding.bottom;
600
601 // This is how far the bottom edge of the last view is from the bottom of the
602 // drawable area
603 int bottomOffset = end - lastBottom;
604
605 final View firstChild = getChildAt(0);
606 final int firstTop = firstChild.getTop();
607
608 // Make sure we are 1) Too high, and 2) Either there are more rows above the
609 // first row or the first row is scrolled off the top of the drawable area
610 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
611 if (mFirstPosition == 0) {
612 // Don't pull the top too far down
613 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
614 }
615
616 // Move everything down
617 offsetChildrenTopAndBottom(bottomOffset);
618 if (mFirstPosition > 0) {
619 // Fill the gap that was opened above mFirstPosition with more rows, if
620 // possible
621 fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
622 firstChild.getTop() - verticalSpacing);
623 // Close up the remaining gap
624 adjustViewsUpOrDown();
625 }
626 }
627 }
628 }
629
630 private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
631 if (mFirstPosition == 0 && childCount > 0) {
632 // Get the first child ...
633 final View firstChild = getChildAt(0);
634
635 // ... and its top edge
636 final int firstTop = firstChild.getTop();
637
638 // This is top of our drawable area
639 final int start = mListPadding.top;
640
641 // This is bottom of our drawable area
642 final int end = (mBottom - mTop) - mListPadding.bottom;
643
644 // This is how far the top edge of the first view is from the top of the
645 // drawable area
646 int topOffset = firstTop - start;
647 final View lastChild = getChildAt(childCount - 1);
648 final int lastBottom = lastChild.getBottom();
649 final int lastPosition = mFirstPosition + childCount - 1;
650
651 // Make sure we are 1) Too low, and 2) Either there are more rows below the
652 // last row or the last row is scrolled off the bottom of the drawable area
653 if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end)) {
654 if (lastPosition == mItemCount - 1 ) {
655 // Don't pull the bottom too far up
656 topOffset = Math.min(topOffset, lastBottom - end);
657 }
658
659 // Move everything up
660 offsetChildrenTopAndBottom(-topOffset);
661 if (lastPosition < mItemCount - 1) {
662 // Fill the gap that was opened below the last position with more rows, if
663 // possible
664 fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
665 lastChild.getBottom() + verticalSpacing);
666 // Close up the remaining gap
667 adjustViewsUpOrDown();
668 }
669 }
670 }
671 }
672
673 /**
674 * Fills the grid based on positioning the new selection at a specific
675 * location. The selection may be moved so that it does not intersect the
676 * faded edges. The grid is then filled upwards and downwards from there.
677 *
678 * @param selectedTop Where the selected item should be
679 * @param childrenTop Where to start drawing children
680 * @param childrenBottom Last pixel where children can be drawn
681 * @return The view that currently has selection
682 */
683 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
684 final int fadingEdgeLength = getVerticalFadingEdgeLength();
685 final int selectedPosition = mSelectedPosition;
686 final int numColumns = mNumColumns;
687 final int verticalSpacing = mVerticalSpacing;
688
689 int rowStart;
690 int rowEnd = -1;
691
692 if (!mStackFromBottom) {
693 rowStart = selectedPosition - (selectedPosition % numColumns);
694 } else {
695 int invertedSelection = mItemCount - 1 - selectedPosition;
696
697 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
698 rowStart = Math.max(0, rowEnd - numColumns + 1);
699 }
700
701 View sel;
702 View referenceView;
703
704 int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
705 int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
706 numColumns, rowStart);
707
708 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
709 // Possibly changed again in fillUp if we add rows above this one.
710 mFirstPosition = rowStart;
711
712 referenceView = mReferenceView;
713 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
714 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
715
716 if (!mStackFromBottom) {
717 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
718 adjustViewsUpOrDown();
719 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
720 } else {
721 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
722 adjustViewsUpOrDown();
723 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
724 }
725
726
727 return sel;
728 }
729
730 /**
731 * Calculate the bottom-most pixel we can draw the selection into
732 *
733 * @param childrenBottom Bottom pixel were children can be drawn
734 * @param fadingEdgeLength Length of the fading edge in pixels, if present
735 * @param numColumns Number of columns in the grid
736 * @param rowStart The start of the row that will contain the selection
737 * @return The bottom-most pixel we can draw the selection into
738 */
739 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
740 int numColumns, int rowStart) {
741 // Last pixel we can draw the selection into
742 int bottomSelectionPixel = childrenBottom;
743 if (rowStart + numColumns - 1 < mItemCount - 1) {
744 bottomSelectionPixel -= fadingEdgeLength;
745 }
746 return bottomSelectionPixel;
747 }
748
749 /**
750 * Calculate the top-most pixel we can draw the selection into
751 *
752 * @param childrenTop Top pixel were children can be drawn
753 * @param fadingEdgeLength Length of the fading edge in pixels, if present
754 * @param rowStart The start of the row that will contain the selection
755 * @return The top-most pixel we can draw the selection into
756 */
757 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
758 // first pixel we can draw the selection into
759 int topSelectionPixel = childrenTop;
760 if (rowStart > 0) {
761 topSelectionPixel += fadingEdgeLength;
762 }
763 return topSelectionPixel;
764 }
765
766 /**
767 * Move all views upwards so the selected row does not interesect the bottom
768 * fading edge (if necessary).
769 *
770 * @param childInSelectedRow A child in the row that contains the selection
771 * @param topSelectionPixel The topmost pixel we can draw the selection into
772 * @param bottomSelectionPixel The bottommost pixel we can draw the
773 * selection into
774 */
775 private void adjustForBottomFadingEdge(View childInSelectedRow,
776 int topSelectionPixel, int bottomSelectionPixel) {
777 // Some of the newly selected item extends below the bottom of the
778 // list
779 if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
780
781 // Find space available above the selection into which we can
782 // scroll upwards
783 int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
784
785 // Find space required to bring the bottom of the selected item
786 // fully into view
787 int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
788 int offset = Math.min(spaceAbove, spaceBelow);
789
790 // Now offset the selected item to get it into view
791 offsetChildrenTopAndBottom(-offset);
792 }
793 }
794
795 /**
796 * Move all views upwards so the selected row does not interesect the top
797 * fading edge (if necessary).
798 *
799 * @param childInSelectedRow A child in the row that contains the selection
800 * @param topSelectionPixel The topmost pixel we can draw the selection into
801 * @param bottomSelectionPixel The bottommost pixel we can draw the
802 * selection into
803 */
804 private void adjustForTopFadingEdge(View childInSelectedRow,
805 int topSelectionPixel, int bottomSelectionPixel) {
806 // Some of the newly selected item extends above the top of the list
807 if (childInSelectedRow.getTop() < topSelectionPixel) {
808 // Find space required to bring the top of the selected item
809 // fully into view
810 int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
811
812 // Find space available below the selection into which we can
813 // scroll downwards
814 int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
815 int offset = Math.min(spaceAbove, spaceBelow);
816
817 // Now offset the selected item to get it into view
818 offsetChildrenTopAndBottom(offset);
819 }
820 }
821
822 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700823 * Smoothly scroll to the specified adapter position. The view will
824 * scroll such that the indicated position is displayed.
825 * @param position Scroll to this adapter position.
826 */
827 @android.view.RemotableViewMethod
828 public void smoothScrollToPosition(int position) {
829 super.smoothScrollToPosition(position);
830 }
831
832 /**
833 * Smoothly scroll to the specified adapter position offset. The view will
834 * scroll such that the indicated position is displayed.
835 * @param offset The amount to offset from the adapter position to scroll to.
836 */
837 @android.view.RemotableViewMethod
838 public void smoothScrollByOffset(int offset) {
839 super.smoothScrollByOffset(offset);
840 }
841
842 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 * Fills the grid based on positioning the new selection relative to the old
844 * selection. The new selection will be placed at, above, or below the
845 * location of the new selection depending on how the selection is moving.
846 * The selection will then be pinned to the visible part of the screen,
847 * excluding the edges that are faded. The grid is then filled upwards and
848 * downwards from there.
849 *
850 * @param delta Which way we are moving
851 * @param childrenTop Where to start drawing children
852 * @param childrenBottom Last pixel where children can be drawn
853 * @return The view that currently has selection
854 */
855 private View moveSelection(int delta, int childrenTop, int childrenBottom) {
856 final int fadingEdgeLength = getVerticalFadingEdgeLength();
857 final int selectedPosition = mSelectedPosition;
858 final int numColumns = mNumColumns;
859 final int verticalSpacing = mVerticalSpacing;
860
861 int oldRowStart;
862 int rowStart;
863 int rowEnd = -1;
864
865 if (!mStackFromBottom) {
866 oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
867
868 rowStart = selectedPosition - (selectedPosition % numColumns);
869 } else {
870 int invertedSelection = mItemCount - 1 - selectedPosition;
871
872 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
873 rowStart = Math.max(0, rowEnd - numColumns + 1);
874
875 invertedSelection = mItemCount - 1 - (selectedPosition - delta);
876 oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
877 oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
878 }
879
880 final int rowDelta = rowStart - oldRowStart;
881
882 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
883 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
884 numColumns, rowStart);
885
886 // Possibly changed again in fillUp if we add rows above this one.
887 mFirstPosition = rowStart;
888
889 View sel;
890 View referenceView;
891
892 if (rowDelta > 0) {
893 /*
894 * Case 1: Scrolling down.
895 */
896
897 final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
898 mReferenceViewInSelectedRow.getBottom();
899
900 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
901 referenceView = mReferenceView;
902
903 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
904 } else if (rowDelta < 0) {
905 /*
906 * Case 2: Scrolling up.
907 */
908 final int oldTop = mReferenceViewInSelectedRow == null ?
909 0 : mReferenceViewInSelectedRow .getTop();
910
911 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
912 referenceView = mReferenceView;
913
914 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
915 } else {
916 /*
917 * Keep selection where it was
918 */
919 final int oldTop = mReferenceViewInSelectedRow == null ?
920 0 : mReferenceViewInSelectedRow .getTop();
921
922 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
923 referenceView = mReferenceView;
924 }
925
926 if (!mStackFromBottom) {
927 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
928 adjustViewsUpOrDown();
929 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
930 } else {
931 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
932 adjustViewsUpOrDown();
933 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
934 }
935
936 return sel;
937 }
938
Adam Lesinskiedd95082010-12-08 12:09:06 -0800939 private boolean determineColumns(int availableSpace) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800940 final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
941 final int stretchMode = mStretchMode;
942 final int requestedColumnWidth = mRequestedColumnWidth;
Adam Lesinskiedd95082010-12-08 12:09:06 -0800943 boolean didNotInitiallyFit = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944
945 if (mRequestedNumColumns == AUTO_FIT) {
946 if (requestedColumnWidth > 0) {
947 // Client told us to pick the number of columns
948 mNumColumns = (availableSpace + requestedHorizontalSpacing) /
949 (requestedColumnWidth + requestedHorizontalSpacing);
950 } else {
951 // Just make up a number if we don't have enough info
952 mNumColumns = 2;
953 }
954 } else {
955 // We picked the columns
956 mNumColumns = mRequestedNumColumns;
957 }
958
959 if (mNumColumns <= 0) {
960 mNumColumns = 1;
961 }
962
963 switch (stretchMode) {
964 case NO_STRETCH:
965 // Nobody stretches
966 mColumnWidth = requestedColumnWidth;
967 mHorizontalSpacing = requestedHorizontalSpacing;
968 break;
969
970 default:
971 int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
972 ((mNumColumns - 1) * requestedHorizontalSpacing);
Adam Lesinskiedd95082010-12-08 12:09:06 -0800973
974 if (spaceLeftOver < 0) {
975 didNotInitiallyFit = true;
976 }
977
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 switch (stretchMode) {
979 case STRETCH_COLUMN_WIDTH:
980 // Stretch the columns
981 mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
982 mHorizontalSpacing = requestedHorizontalSpacing;
983 break;
984
985 case STRETCH_SPACING:
986 // Stretch the spacing between columns
987 mColumnWidth = requestedColumnWidth;
988 if (mNumColumns > 1) {
989 mHorizontalSpacing = requestedHorizontalSpacing +
990 spaceLeftOver / (mNumColumns - 1);
991 } else {
992 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
993 }
994 break;
995
996 case STRETCH_SPACING_UNIFORM:
997 // Stretch the spacing between columns
998 mColumnWidth = requestedColumnWidth;
999 if (mNumColumns > 1) {
1000 mHorizontalSpacing = requestedHorizontalSpacing +
1001 spaceLeftOver / (mNumColumns + 1);
1002 } else {
1003 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
1004 }
1005 break;
1006 }
1007
1008 break;
1009 }
Adam Lesinskiedd95082010-12-08 12:09:06 -08001010 return didNotInitiallyFit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 }
1012
1013 @Override
1014 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1015 // Sets up mListPadding
1016 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1017
1018 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1019 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1020 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1021 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1022
1023 if (widthMode == MeasureSpec.UNSPECIFIED) {
1024 if (mColumnWidth > 0) {
1025 widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
1026 } else {
1027 widthSize = mListPadding.left + mListPadding.right;
1028 }
1029 widthSize += getVerticalScrollbarWidth();
1030 }
1031
1032 int childWidth = widthSize - mListPadding.left - mListPadding.right;
Adam Lesinskiedd95082010-12-08 12:09:06 -08001033 boolean didNotInitiallyFit = determineColumns(childWidth);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034
1035 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001036 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037
1038 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1039 final int count = mItemCount;
1040 if (count > 0) {
Romain Guy21875052010-01-06 18:48:08 -08001041 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042
Adam Powellaebd28f2012-02-22 10:31:16 -08001043 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001044 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001045 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001046 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 }
1048 p.viewType = mAdapter.getItemViewType(0);
Romain Guy0bf88592010-03-02 13:38:44 -08001049 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050
1051 int childHeightSpec = getChildMeasureSpec(
1052 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1053 int childWidthSpec = getChildMeasureSpec(
1054 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1055 child.measure(childWidthSpec, childHeightSpec);
1056
1057 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001058 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059
1060 if (mRecycler.shouldRecycleViewType(p.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001061 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062 }
1063 }
1064
1065 if (heightMode == MeasureSpec.UNSPECIFIED) {
1066 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1067 getVerticalFadingEdgeLength() * 2;
1068 }
1069
1070 if (heightMode == MeasureSpec.AT_MOST) {
1071 int ourSize = mListPadding.top + mListPadding.bottom;
1072
1073 final int numColumns = mNumColumns;
1074 for (int i = 0; i < count; i += numColumns) {
1075 ourSize += childHeight;
1076 if (i + numColumns < count) {
1077 ourSize += mVerticalSpacing;
1078 }
1079 if (ourSize >= heightSize) {
1080 ourSize = heightSize;
1081 break;
1082 }
1083 }
1084 heightSize = ourSize;
1085 }
1086
Dianne Hackborn189ee182010-12-02 21:48:53 -08001087 if (widthMode == MeasureSpec.AT_MOST && mRequestedNumColumns != AUTO_FIT) {
1088 int ourSize = (mRequestedNumColumns*mColumnWidth)
1089 + ((mRequestedNumColumns-1)*mHorizontalSpacing)
1090 + mListPadding.left + mListPadding.right;
Adam Lesinskiedd95082010-12-08 12:09:06 -08001091 if (ourSize > widthSize || didNotInitiallyFit) {
Dianne Hackborn189ee182010-12-02 21:48:53 -08001092 widthSize |= MEASURED_STATE_TOO_SMALL;
1093 }
1094 }
1095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 setMeasuredDimension(widthSize, heightSize);
1097 mWidthMeasureSpec = widthMeasureSpec;
1098 }
1099
1100 @Override
1101 protected void attachLayoutAnimationParameters(View child,
1102 ViewGroup.LayoutParams params, int index, int count) {
1103
1104 GridLayoutAnimationController.AnimationParameters animationParams =
1105 (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
1106
1107 if (animationParams == null) {
1108 animationParams = new GridLayoutAnimationController.AnimationParameters();
1109 params.layoutAnimationParameters = animationParams;
1110 }
1111
1112 animationParams.count = count;
1113 animationParams.index = index;
1114 animationParams.columnsCount = mNumColumns;
1115 animationParams.rowsCount = count / mNumColumns;
1116
1117 if (!mStackFromBottom) {
1118 animationParams.column = index % mNumColumns;
1119 animationParams.row = index / mNumColumns;
1120 } else {
1121 final int invertedIndex = count - 1 - index;
1122
1123 animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
1124 animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
1125 }
1126 }
1127
1128 @Override
1129 protected void layoutChildren() {
1130 final boolean blockLayoutRequests = mBlockLayoutRequests;
1131 if (!blockLayoutRequests) {
1132 mBlockLayoutRequests = true;
1133 }
1134
1135 try {
1136 super.layoutChildren();
1137
1138 invalidate();
1139
1140 if (mAdapter == null) {
1141 resetList();
1142 invokeOnItemScrollListener();
1143 return;
1144 }
1145
1146 final int childrenTop = mListPadding.top;
1147 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1148
1149 int childCount = getChildCount();
1150 int index;
1151 int delta = 0;
1152
1153 View sel;
1154 View oldSel = null;
1155 View oldFirst = null;
1156 View newSel = null;
1157
1158 // Remember stuff we will need down below
1159 switch (mLayoutMode) {
1160 case LAYOUT_SET_SELECTION:
1161 index = mNextSelectedPosition - mFirstPosition;
1162 if (index >= 0 && index < childCount) {
1163 newSel = getChildAt(index);
1164 }
1165 break;
1166 case LAYOUT_FORCE_TOP:
1167 case LAYOUT_FORCE_BOTTOM:
1168 case LAYOUT_SPECIFIC:
1169 case LAYOUT_SYNC:
1170 break;
1171 case LAYOUT_MOVE_SELECTION:
1172 if (mNextSelectedPosition >= 0) {
1173 delta = mNextSelectedPosition - mSelectedPosition;
1174 }
1175 break;
1176 default:
1177 // Remember the previously selected view
1178 index = mSelectedPosition - mFirstPosition;
1179 if (index >= 0 && index < childCount) {
1180 oldSel = getChildAt(index);
1181 }
1182
1183 // Remember the previous first child
1184 oldFirst = getChildAt(0);
1185 }
1186
1187 boolean dataChanged = mDataChanged;
1188 if (dataChanged) {
1189 handleDataChanged();
1190 }
1191
1192 // Handle the empty set by removing all views that are visible
1193 // and calling it a day
1194 if (mItemCount == 0) {
1195 resetList();
1196 invokeOnItemScrollListener();
1197 return;
1198 }
1199
1200 setSelectedPositionInt(mNextSelectedPosition);
1201
1202 // Pull all children into the RecycleBin.
1203 // These views will be reused if possible
1204 final int firstPosition = mFirstPosition;
1205 final RecycleBin recycleBin = mRecycler;
1206
1207 if (dataChanged) {
1208 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001209 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210 }
1211 } else {
1212 recycleBin.fillActiveViews(childCount, firstPosition);
1213 }
1214
1215 // Clear out old views
1216 //removeAllViewsInLayout();
1217 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001218 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219
1220 switch (mLayoutMode) {
1221 case LAYOUT_SET_SELECTION:
1222 if (newSel != null) {
1223 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1224 } else {
1225 sel = fillSelection(childrenTop, childrenBottom);
1226 }
1227 break;
1228 case LAYOUT_FORCE_TOP:
1229 mFirstPosition = 0;
1230 sel = fillFromTop(childrenTop);
1231 adjustViewsUpOrDown();
1232 break;
1233 case LAYOUT_FORCE_BOTTOM:
1234 sel = fillUp(mItemCount - 1, childrenBottom);
1235 adjustViewsUpOrDown();
1236 break;
1237 case LAYOUT_SPECIFIC:
1238 sel = fillSpecific(mSelectedPosition, mSpecificTop);
1239 break;
1240 case LAYOUT_SYNC:
1241 sel = fillSpecific(mSyncPosition, mSpecificTop);
1242 break;
1243 case LAYOUT_MOVE_SELECTION:
1244 // Move the selection relative to its old position
1245 sel = moveSelection(delta, childrenTop, childrenBottom);
1246 break;
1247 default:
1248 if (childCount == 0) {
1249 if (!mStackFromBottom) {
Romain Guy91c86132010-03-26 17:29:45 -07001250 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1251 INVALID_POSITION : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 sel = fillFromTop(childrenTop);
1253 } else {
1254 final int last = mItemCount - 1;
Romain Guy91c86132010-03-26 17:29:45 -07001255 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1256 INVALID_POSITION : last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 sel = fillFromBottom(last, childrenBottom);
1258 }
1259 } else {
1260 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1261 sel = fillSpecific(mSelectedPosition, oldSel == null ?
1262 childrenTop : oldSel.getTop());
1263 } else if (mFirstPosition < mItemCount) {
1264 sel = fillSpecific(mFirstPosition, oldFirst == null ?
1265 childrenTop : oldFirst.getTop());
1266 } else {
1267 sel = fillSpecific(0, childrenTop);
1268 }
1269 }
1270 break;
1271 }
1272
1273 // Flush any cached views that did not get reused above
1274 recycleBin.scrapActiveViews();
1275
1276 if (sel != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001277 positionSelector(INVALID_POSITION, sel);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 mSelectedTop = sel.getTop();
Romain Guy3616a412009-09-15 13:50:37 -07001279 } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1280 View child = getChildAt(mMotionPosition - mFirstPosition);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001281 if (child != null) positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 } else {
Romain Guy3616a412009-09-15 13:50:37 -07001283 mSelectedTop = 0;
1284 mSelectorRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 }
1286
1287 mLayoutMode = LAYOUT_NORMAL;
1288 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001289 if (mPositionScrollAfterLayout != null) {
1290 post(mPositionScrollAfterLayout);
1291 mPositionScrollAfterLayout = null;
1292 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001293 mNeedSync = false;
1294 setNextSelectedPositionInt(mSelectedPosition);
1295
1296 updateScrollIndicators();
1297
1298 if (mItemCount > 0) {
1299 checkSelectionChanged();
1300 }
1301
1302 invokeOnItemScrollListener();
1303 } finally {
1304 if (!blockLayoutRequests) {
1305 mBlockLayoutRequests = false;
1306 }
1307 }
1308 }
1309
1310
1311 /**
1312 * Obtain the view and add it to our list of children. The view can be made
1313 * fresh, converted from an unused view, or used as is if it was in the
1314 * recycle bin.
1315 *
1316 * @param position Logical position in the list
1317 * @param y Top or bottom edge of the view to add
1318 * @param flow if true, align top edge to y. If false, align bottom edge to
1319 * y.
1320 * @param childrenLeft Left edge where children should be positioned
1321 * @param selected Is this position selected?
1322 * @param where to add new item in the list
1323 * @return View that was added
1324 */
1325 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1326 boolean selected, int where) {
1327 View child;
1328
1329 if (!mDataChanged) {
Romain Guy21875052010-01-06 18:48:08 -08001330 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001331 child = mRecycler.getActiveView(position);
1332 if (child != null) {
1333 // Found it -- we're using an existing child
1334 // This just needs to be positioned
1335 setupChild(child, position, y, flow, childrenLeft, selected, true, where);
1336 return child;
1337 }
1338 }
1339
1340 // Make a new view for this position, or convert an unused view if
1341 // possible
Romain Guy21875052010-01-06 18:48:08 -08001342 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343
1344 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001345 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346
1347 return child;
1348 }
1349
1350 /**
1351 * Add a view as a child and make sure it is measured (if necessary) and
1352 * positioned properly.
1353 *
1354 * @param child The view to add
1355 * @param position The position of the view
1356 * @param y The y position relative to which this view will be positioned
1357 * @param flow if true, align top edge to y. If false, align bottom edge
1358 * to y.
1359 * @param childrenLeft Left edge where children should be positioned
1360 * @param selected Is this position selected?
1361 * @param recycled Has this view been pulled from the recycle bin? If so it
1362 * does not need to be remeasured.
1363 * @param where Where to add the item in the list
1364 *
1365 */
1366 private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
1367 boolean selected, boolean recycled, int where) {
Romain Guy5fade8c2013-07-10 16:36:18 -07001368 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupGridItem");
1369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 boolean isSelected = selected && shouldShowSelector();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001371 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001372 final int mode = mTouchMode;
1373 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1374 mMotionPosition == position;
1375 final boolean updateChildPressed = isPressed != child.isPressed();
1376
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001377 boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1378
1379 // Respect layout params that are already in the view. Otherwise make
1380 // some up...
Adam Powellaebd28f2012-02-22 10:31:16 -08001381 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001383 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 }
1385 p.viewType = mAdapter.getItemViewType(position);
1386
Romain Guy0bf88592010-03-02 13:38:44 -08001387 if (recycled && !p.forceAdd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001388 attachViewToParent(child, where, p);
1389 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001390 p.forceAdd = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 addViewInLayout(child, where, p, true);
1392 }
1393
1394 if (updateChildSelected) {
1395 child.setSelected(isSelected);
1396 if (isSelected) {
1397 requestFocus();
1398 }
1399 }
1400
Romain Guy3616a412009-09-15 13:50:37 -07001401 if (updateChildPressed) {
1402 child.setPressed(isPressed);
1403 }
1404
Adam Powellf343e1b2010-08-13 18:27:04 -07001405 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1406 if (child instanceof Checkable) {
1407 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001408 } else if (getContext().getApplicationInfo().targetSdkVersion
1409 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1410 child.setActivated(mCheckStates.get(position));
Adam Powellf343e1b2010-08-13 18:27:04 -07001411 }
1412 }
1413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414 if (needToMeasure) {
1415 int childHeightSpec = ViewGroup.getChildMeasureSpec(
1416 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1417
1418 int childWidthSpec = ViewGroup.getChildMeasureSpec(
1419 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1420 child.measure(childWidthSpec, childHeightSpec);
1421 } else {
1422 cleanupLayoutState(child);
1423 }
1424
1425 final int w = child.getMeasuredWidth();
1426 final int h = child.getMeasuredHeight();
1427
1428 int childLeft;
1429 final int childTop = flow ? y : y - h;
1430
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001431 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07001432 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07001433 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
1434 case Gravity.LEFT:
1435 childLeft = childrenLeft;
1436 break;
1437 case Gravity.CENTER_HORIZONTAL:
1438 childLeft = childrenLeft + ((mColumnWidth - w) / 2);
1439 break;
1440 case Gravity.RIGHT:
1441 childLeft = childrenLeft + mColumnWidth - w;
1442 break;
1443 default:
1444 childLeft = childrenLeft;
1445 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001446 }
1447
1448 if (needToMeasure) {
1449 final int childRight = childLeft + w;
1450 final int childBottom = childTop + h;
1451 child.layout(childLeft, childTop, childRight, childBottom);
1452 } else {
1453 child.offsetLeftAndRight(childLeft - child.getLeft());
1454 child.offsetTopAndBottom(childTop - child.getTop());
1455 }
1456
1457 if (mCachingStarted) {
1458 child.setDrawingCacheEnabled(true);
1459 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001460
1461 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1462 != position) {
1463 child.jumpDrawablesToCurrentState();
1464 }
Romain Guy5fade8c2013-07-10 16:36:18 -07001465
1466 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 }
1468
1469 /**
1470 * Sets the currently selected item
1471 *
1472 * @param position Index (starting at 0) of the data item to be selected.
1473 *
1474 * If in touch mode, the item will not be selected but it will still be positioned
1475 * appropriately.
1476 */
1477 @Override
1478 public void setSelection(int position) {
1479 if (!isInTouchMode()) {
1480 setNextSelectedPositionInt(position);
1481 } else {
1482 mResurrectToPosition = position;
1483 }
1484 mLayoutMode = LAYOUT_SET_SELECTION;
Adam Powell1fa179ef2012-04-12 15:01:40 -07001485 if (mPositionScroller != null) {
1486 mPositionScroller.stop();
1487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488 requestLayout();
1489 }
1490
1491 /**
1492 * Makes the item at the supplied position selected.
1493 *
1494 * @param position the position of the new selection
1495 */
1496 @Override
1497 void setSelectionInt(int position) {
Mike Cleronf116bf82009-09-27 19:14:12 -07001498 int previousSelectedPosition = mNextSelectedPosition;
1499
Adam Powell1fa179ef2012-04-12 15:01:40 -07001500 if (mPositionScroller != null) {
1501 mPositionScroller.stop();
1502 }
1503
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 setNextSelectedPositionInt(position);
1505 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07001506
1507 final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
1508 mNextSelectedPosition;
1509 final int previous = mStackFromBottom ? mItemCount - 1
1510 - previousSelectedPosition : previousSelectedPosition;
1511
1512 final int nextRow = next / mNumColumns;
1513 final int previousRow = previous / mNumColumns;
1514
1515 if (nextRow != previousRow) {
1516 awakenScrollBars();
1517 }
1518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519 }
1520
1521 @Override
1522 public boolean onKeyDown(int keyCode, KeyEvent event) {
1523 return commonKey(keyCode, 1, event);
1524 }
1525
1526 @Override
1527 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1528 return commonKey(keyCode, repeatCount, event);
1529 }
1530
1531 @Override
1532 public boolean onKeyUp(int keyCode, KeyEvent event) {
1533 return commonKey(keyCode, 1, event);
1534 }
1535
1536 private boolean commonKey(int keyCode, int count, KeyEvent event) {
1537 if (mAdapter == null) {
1538 return false;
1539 }
1540
1541 if (mDataChanged) {
1542 layoutChildren();
1543 }
1544
1545 boolean handled = false;
1546 int action = event.getAction();
1547
1548 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001549 switch (keyCode) {
1550 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001551 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001552 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001554 break;
1555
1556 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001557 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001558 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001559 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001560 break;
1561
1562 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001563 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001564 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001565 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001566 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 }
1568 break;
1569
1570 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001571 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001572 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001573 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001574 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001575 }
1576 break;
1577
1578 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001579 case KeyEvent.KEYCODE_ENTER:
1580 if (event.hasNoModifiers()) {
1581 handled = resurrectSelectionIfNeeded();
1582 if (!handled
1583 && event.getRepeatCount() == 0 && getChildCount() > 0) {
1584 keyPressed();
1585 handled = true;
1586 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001587 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001588 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001589
1590 case KeyEvent.KEYCODE_SPACE:
1591 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08001592 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001593 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001594 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001595 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001596 }
1597 }
1598 break;
1599
1600 case KeyEvent.KEYCODE_PAGE_UP:
1601 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001602 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001603 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001604 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001605 }
1606 break;
1607
1608 case KeyEvent.KEYCODE_PAGE_DOWN:
1609 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001610 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001611 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001612 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001613 }
1614 break;
1615
1616 case KeyEvent.KEYCODE_MOVE_HOME:
1617 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001618 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001619 }
1620 break;
1621
1622 case KeyEvent.KEYCODE_MOVE_END:
1623 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001624 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001625 }
1626 break;
1627
1628 case KeyEvent.KEYCODE_TAB:
1629 // XXX Sometimes it is useful to be able to TAB through the items in
1630 // a GridView sequentially. Unfortunately this can create an
1631 // asymmetry in TAB navigation order unless the list selection
1632 // always reverts to the top or bottom when receiving TAB focus from
1633 // another widget. Leaving this behavior disabled for now but
1634 // perhaps it should be configurable (and more comprehensive).
1635 if (false) {
1636 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001637 handled = resurrectSelectionIfNeeded()
1638 || sequenceScroll(FOCUS_FORWARD);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001639 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001640 handled = resurrectSelectionIfNeeded()
1641 || sequenceScroll(FOCUS_BACKWARD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001642 }
1643 }
1644 break;
1645 }
1646 }
1647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001648 if (handled) {
1649 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001650 }
1651
1652 if (sendToTextFilter(keyCode, count, event)) {
1653 return true;
1654 }
1655
1656 switch (action) {
1657 case KeyEvent.ACTION_DOWN:
1658 return super.onKeyDown(keyCode, event);
1659 case KeyEvent.ACTION_UP:
1660 return super.onKeyUp(keyCode, event);
1661 case KeyEvent.ACTION_MULTIPLE:
1662 return super.onKeyMultiple(keyCode, count, event);
1663 default:
1664 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001665 }
1666 }
1667
1668 /**
1669 * Scrolls up or down by the number of items currently present on screen.
1670 *
1671 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1672 * @return whether selection was moved
1673 */
1674 boolean pageScroll(int direction) {
1675 int nextPage = -1;
1676
1677 if (direction == FOCUS_UP) {
Romain Guy64d50a62010-08-20 10:50:49 -07001678 nextPage = Math.max(0, mSelectedPosition - getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001679 } else if (direction == FOCUS_DOWN) {
Romain Guy64d50a62010-08-20 10:50:49 -07001680 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001681 }
1682
1683 if (nextPage >= 0) {
1684 setSelectionInt(nextPage);
1685 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07001686 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001687 return true;
1688 }
1689
1690 return false;
1691 }
1692
1693 /**
1694 * Go to the last or first item if possible.
1695 *
1696 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1697 *
1698 * @return Whether selection was moved.
1699 */
1700 boolean fullScroll(int direction) {
1701 boolean moved = false;
1702 if (direction == FOCUS_UP) {
1703 mLayoutMode = LAYOUT_SET_SELECTION;
1704 setSelectionInt(0);
1705 invokeOnItemScrollListener();
1706 moved = true;
1707 } else if (direction == FOCUS_DOWN) {
1708 mLayoutMode = LAYOUT_SET_SELECTION;
1709 setSelectionInt(mItemCount - 1);
1710 invokeOnItemScrollListener();
1711 moved = true;
1712 }
Mike Cleronf116bf82009-09-27 19:14:12 -07001713
1714 if (moved) {
1715 awakenScrollBars();
1716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001717
1718 return moved;
1719 }
1720
1721 /**
1722 * Scrolls to the next or previous item, horizontally or vertically.
1723 *
1724 * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1725 * {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1726 *
1727 * @return whether selection was moved
1728 */
1729 boolean arrowScroll(int direction) {
1730 final int selectedPosition = mSelectedPosition;
1731 final int numColumns = mNumColumns;
1732
1733 int startOfRowPos;
1734 int endOfRowPos;
1735
1736 boolean moved = false;
1737
1738 if (!mStackFromBottom) {
1739 startOfRowPos = (selectedPosition / numColumns) * numColumns;
1740 endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1741 } else {
1742 final int invertedSelection = mItemCount - 1 - selectedPosition;
1743 endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1744 startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1745 }
1746
1747 switch (direction) {
1748 case FOCUS_UP:
1749 if (startOfRowPos > 0) {
1750 mLayoutMode = LAYOUT_MOVE_SELECTION;
1751 setSelectionInt(Math.max(0, selectedPosition - numColumns));
1752 moved = true;
1753 }
1754 break;
1755 case FOCUS_DOWN:
1756 if (endOfRowPos < mItemCount - 1) {
1757 mLayoutMode = LAYOUT_MOVE_SELECTION;
1758 setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1759 moved = true;
1760 }
1761 break;
1762 case FOCUS_LEFT:
1763 if (selectedPosition > startOfRowPos) {
1764 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001765 setSelectionInt(Math.max(0, selectedPosition - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001766 moved = true;
1767 }
1768 break;
1769 case FOCUS_RIGHT:
1770 if (selectedPosition < endOfRowPos) {
1771 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001772 setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 moved = true;
1774 }
1775 break;
1776 }
1777
1778 if (moved) {
1779 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1780 invokeOnItemScrollListener();
1781 }
1782
Mike Cleronf116bf82009-09-27 19:14:12 -07001783 if (moved) {
1784 awakenScrollBars();
1785 }
1786
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 return moved;
1788 }
1789
Jeff Brown4e6319b2010-12-13 10:36:51 -08001790 /**
1791 * Goes to the next or previous item according to the order set by the
1792 * adapter.
1793 */
1794 boolean sequenceScroll(int direction) {
1795 int selectedPosition = mSelectedPosition;
1796 int numColumns = mNumColumns;
1797 int count = mItemCount;
1798
1799 int startOfRow;
1800 int endOfRow;
1801 if (!mStackFromBottom) {
1802 startOfRow = (selectedPosition / numColumns) * numColumns;
1803 endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1804 } else {
1805 int invertedSelection = count - 1 - selectedPosition;
1806 endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1807 startOfRow = Math.max(0, endOfRow - numColumns + 1);
1808 }
1809
1810 boolean moved = false;
1811 boolean showScroll = false;
1812 switch (direction) {
1813 case FOCUS_FORWARD:
1814 if (selectedPosition < count - 1) {
1815 // Move to the next item.
1816 mLayoutMode = LAYOUT_MOVE_SELECTION;
1817 setSelectionInt(selectedPosition + 1);
1818 moved = true;
1819 // Show the scrollbar only if changing rows.
1820 showScroll = selectedPosition == endOfRow;
1821 }
1822 break;
1823
1824 case FOCUS_BACKWARD:
1825 if (selectedPosition > 0) {
1826 // Move to the previous item.
1827 mLayoutMode = LAYOUT_MOVE_SELECTION;
1828 setSelectionInt(selectedPosition - 1);
1829 moved = true;
1830 // Show the scrollbar only if changing rows.
1831 showScroll = selectedPosition == startOfRow;
1832 }
1833 break;
1834 }
1835
1836 if (moved) {
1837 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1838 invokeOnItemScrollListener();
1839 }
1840
1841 if (showScroll) {
1842 awakenScrollBars();
1843 }
1844
1845 return moved;
1846 }
1847
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001848 @Override
1849 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1850 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1851
1852 int closestChildIndex = -1;
1853 if (gainFocus && previouslyFocusedRect != null) {
1854 previouslyFocusedRect.offset(mScrollX, mScrollY);
1855
1856 // figure out which item should be selected based on previously
1857 // focused rect
1858 Rect otherRect = mTempRect;
1859 int minDistance = Integer.MAX_VALUE;
1860 final int childCount = getChildCount();
1861 for (int i = 0; i < childCount; i++) {
1862 // only consider view's on appropriate edge of grid
1863 if (!isCandidateSelection(i, direction)) {
1864 continue;
1865 }
1866
1867 final View other = getChildAt(i);
1868 other.getDrawingRect(otherRect);
1869 offsetDescendantRectToMyCoords(other, otherRect);
1870 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1871
1872 if (distance < minDistance) {
1873 minDistance = distance;
1874 closestChildIndex = i;
1875 }
1876 }
1877 }
1878
1879 if (closestChildIndex >= 0) {
1880 setSelection(closestChildIndex + mFirstPosition);
1881 } else {
1882 requestLayout();
1883 }
1884 }
1885
1886 /**
1887 * Is childIndex a candidate for next focus given the direction the focus
1888 * change is coming from?
1889 * @param childIndex The index to check.
1890 * @param direction The direction, one of
Jeff Brown4e6319b2010-12-13 10:36:51 -08001891 * {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001892 * @return Whether childIndex is a candidate.
1893 */
1894 private boolean isCandidateSelection(int childIndex, int direction) {
1895 final int count = getChildCount();
1896 final int invertedIndex = count - 1 - childIndex;
1897
1898 int rowStart;
1899 int rowEnd;
1900
1901 if (!mStackFromBottom) {
1902 rowStart = childIndex - (childIndex % mNumColumns);
1903 rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1904 } else {
1905 rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1906 rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1907 }
1908
1909 switch (direction) {
1910 case View.FOCUS_RIGHT:
1911 // coming from left, selection is only valid if it is on left
1912 // edge
1913 return childIndex == rowStart;
1914 case View.FOCUS_DOWN:
1915 // coming from top; only valid if in top row
1916 return rowStart == 0;
1917 case View.FOCUS_LEFT:
1918 // coming from right, must be on right edge
1919 return childIndex == rowEnd;
1920 case View.FOCUS_UP:
1921 // coming from bottom, need to be in last row
1922 return rowEnd == count - 1;
Jeff Brown4e6319b2010-12-13 10:36:51 -08001923 case View.FOCUS_FORWARD:
1924 // coming from top-left, need to be first in top row
1925 return childIndex == rowStart && rowStart == 0;
1926 case View.FOCUS_BACKWARD:
1927 // coming from bottom-right, need to be last in bottom row
1928 return childIndex == rowEnd && rowEnd == count - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001929 default:
1930 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08001931 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
1932 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001933 }
1934 }
1935
1936 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07001937 * Set the gravity for this grid. Gravity describes how the child views
1938 * are horizontally aligned. Defaults to Gravity.LEFT
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001939 *
1940 * @param gravity the gravity to apply to this grid's children
1941 *
1942 * @attr ref android.R.styleable#GridView_gravity
1943 */
1944 public void setGravity(int gravity) {
1945 if (mGravity != gravity) {
1946 mGravity = gravity;
1947 requestLayoutIfNecessary();
1948 }
1949 }
1950
1951 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07001952 * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
1953 *
1954 * @return the gravity that will be applied to this grid's children
1955 *
1956 * @attr ref android.R.styleable#GridView_gravity
1957 */
1958 public int getGravity() {
1959 return mGravity;
1960 }
1961
1962 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001963 * Set the amount of horizontal (x) spacing to place between each item
1964 * in the grid.
1965 *
1966 * @param horizontalSpacing The amount of horizontal space between items,
1967 * in pixels.
1968 *
1969 * @attr ref android.R.styleable#GridView_horizontalSpacing
1970 */
1971 public void setHorizontalSpacing(int horizontalSpacing) {
1972 if (horizontalSpacing != mRequestedHorizontalSpacing) {
1973 mRequestedHorizontalSpacing = horizontalSpacing;
1974 requestLayoutIfNecessary();
1975 }
1976 }
1977
Adam Powell0b7413d2012-03-21 14:51:41 -07001978 /**
1979 * Returns the amount of horizontal spacing currently used between each item in the grid.
1980 *
1981 * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
1982 * has been called but layout is not yet complete, this method may return a stale value.
1983 * To get the horizontal spacing that was explicitly requested use
1984 * {@link #getRequestedHorizontalSpacing()}.</p>
1985 *
1986 * @return Current horizontal spacing between each item in pixels
1987 *
1988 * @see #setHorizontalSpacing(int)
1989 * @see #getRequestedHorizontalSpacing()
1990 *
1991 * @attr ref android.R.styleable#GridView_horizontalSpacing
1992 */
1993 public int getHorizontalSpacing() {
1994 return mHorizontalSpacing;
1995 }
1996
1997 /**
1998 * Returns the requested amount of horizontal spacing between each item in the grid.
1999 *
2000 * <p>The value returned may have been supplied during inflation as part of a style,
2001 * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
2002 * If layout is not yet complete or if GridView calculated a different horizontal spacing
2003 * from what was requested, this may return a different value from
2004 * {@link #getHorizontalSpacing()}.</p>
2005 *
2006 * @return The currently requested horizontal spacing between items, in pixels
2007 *
2008 * @see #setHorizontalSpacing(int)
2009 * @see #getHorizontalSpacing()
2010 *
2011 * @attr ref android.R.styleable#GridView_horizontalSpacing
2012 */
2013 public int getRequestedHorizontalSpacing() {
2014 return mRequestedHorizontalSpacing;
2015 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002016
2017 /**
2018 * Set the amount of vertical (y) spacing to place between each item
2019 * in the grid.
2020 *
2021 * @param verticalSpacing The amount of vertical space between items,
2022 * in pixels.
2023 *
Adam Powell0b7413d2012-03-21 14:51:41 -07002024 * @see #getVerticalSpacing()
2025 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002026 * @attr ref android.R.styleable#GridView_verticalSpacing
2027 */
2028 public void setVerticalSpacing(int verticalSpacing) {
2029 if (verticalSpacing != mVerticalSpacing) {
2030 mVerticalSpacing = verticalSpacing;
2031 requestLayoutIfNecessary();
2032 }
2033 }
2034
2035 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07002036 * Returns the amount of vertical spacing between each item in the grid.
2037 *
2038 * @return The vertical spacing between items in pixels
2039 *
2040 * @see #setVerticalSpacing(int)
2041 *
2042 * @attr ref android.R.styleable#GridView_verticalSpacing
2043 */
2044 public int getVerticalSpacing() {
2045 return mVerticalSpacing;
2046 }
2047
2048 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002049 * Control how items are stretched to fill their space.
2050 *
2051 * @param stretchMode Either {@link #NO_STRETCH},
2052 * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2053 *
2054 * @attr ref android.R.styleable#GridView_stretchMode
2055 */
2056 public void setStretchMode(int stretchMode) {
2057 if (stretchMode != mStretchMode) {
2058 mStretchMode = stretchMode;
2059 requestLayoutIfNecessary();
2060 }
2061 }
2062
2063 public int getStretchMode() {
2064 return mStretchMode;
2065 }
2066
2067 /**
2068 * Set the width of columns in the grid.
2069 *
2070 * @param columnWidth The column width, in pixels.
2071 *
2072 * @attr ref android.R.styleable#GridView_columnWidth
2073 */
2074 public void setColumnWidth(int columnWidth) {
2075 if (columnWidth != mRequestedColumnWidth) {
2076 mRequestedColumnWidth = columnWidth;
2077 requestLayoutIfNecessary();
2078 }
2079 }
2080
2081 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07002082 * Return the width of a column in the grid.
2083 *
2084 * <p>This may not be valid yet if a layout is pending.</p>
2085 *
2086 * @return The column width in pixels
2087 *
2088 * @see #setColumnWidth(int)
2089 * @see #getRequestedColumnWidth()
2090 *
2091 * @attr ref android.R.styleable#GridView_columnWidth
2092 */
2093 public int getColumnWidth() {
2094 return mColumnWidth;
2095 }
2096
2097 /**
2098 * Return the requested width of a column in the grid.
2099 *
2100 * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2101 * to retrieve the current real width of a column.</p>
2102 *
2103 * @return The requested column width in pixels
2104 *
2105 * @see #setColumnWidth(int)
2106 * @see #getColumnWidth()
2107 *
2108 * @attr ref android.R.styleable#GridView_columnWidth
2109 */
2110 public int getRequestedColumnWidth() {
2111 return mRequestedColumnWidth;
2112 }
2113
2114 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002115 * Set the number of columns in the grid
2116 *
2117 * @param numColumns The desired number of columns.
2118 *
2119 * @attr ref android.R.styleable#GridView_numColumns
2120 */
2121 public void setNumColumns(int numColumns) {
2122 if (numColumns != mRequestedNumColumns) {
2123 mRequestedNumColumns = numColumns;
2124 requestLayoutIfNecessary();
2125 }
2126 }
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -07002127
2128 /**
2129 * Get the number of columns in the grid.
2130 * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2131 *
2132 * @attr ref android.R.styleable#GridView_numColumns
2133 *
2134 * @see #setNumColumns(int)
2135 */
2136 @ViewDebug.ExportedProperty
2137 public int getNumColumns() {
2138 return mNumColumns;
2139 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002140
2141 /**
2142 * Make sure views are touching the top or bottom edge, as appropriate for
2143 * our gravity
2144 */
2145 private void adjustViewsUpOrDown() {
2146 final int childCount = getChildCount();
2147
2148 if (childCount > 0) {
2149 int delta;
2150 View child;
2151
2152 if (!mStackFromBottom) {
2153 // Uh-oh -- we came up short. Slide all views up to make them
2154 // align with the top
2155 child = getChildAt(0);
2156 delta = child.getTop() - mListPadding.top;
2157 if (mFirstPosition != 0) {
2158 // It's OK to have some space above the first item if it is
2159 // part of the vertical spacing
2160 delta -= mVerticalSpacing;
2161 }
2162 if (delta < 0) {
2163 // We only are looking to see if we are too low, not too high
2164 delta = 0;
2165 }
2166 } else {
2167 // we are too high, slide all views down to align with bottom
2168 child = getChildAt(childCount - 1);
2169 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2170
2171 if (mFirstPosition + childCount < mItemCount) {
2172 // It's OK to have some space below the last item if it is
2173 // part of the vertical spacing
2174 delta += mVerticalSpacing;
2175 }
2176
2177 if (delta > 0) {
2178 // We only are looking to see if we are too high, not too low
2179 delta = 0;
2180 }
2181 }
2182
2183 if (delta != 0) {
2184 offsetChildrenTopAndBottom(-delta);
2185 }
2186 }
2187 }
2188
2189 @Override
2190 protected int computeVerticalScrollExtent() {
2191 final int count = getChildCount();
2192 if (count > 0) {
2193 final int numColumns = mNumColumns;
2194 final int rowCount = (count + numColumns - 1) / numColumns;
2195
2196 int extent = rowCount * 100;
2197
2198 View view = getChildAt(0);
2199 final int top = view.getTop();
2200 int height = view.getHeight();
2201 if (height > 0) {
2202 extent += (top * 100) / height;
2203 }
2204
2205 view = getChildAt(count - 1);
2206 final int bottom = view.getBottom();
2207 height = view.getHeight();
2208 if (height > 0) {
2209 extent -= ((bottom - getHeight()) * 100) / height;
2210 }
2211
2212 return extent;
2213 }
2214 return 0;
2215 }
2216
2217 @Override
2218 protected int computeVerticalScrollOffset() {
2219 if (mFirstPosition >= 0 && getChildCount() > 0) {
2220 final View view = getChildAt(0);
2221 final int top = view.getTop();
2222 int height = view.getHeight();
2223 if (height > 0) {
Adam Powellf2a204e2010-02-12 15:25:33 -08002224 final int numColumns = mNumColumns;
Adam Powellf2a204e2010-02-12 15:25:33 -08002225 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
Pieter-Jan Vandormaelbbf7b4c2012-06-16 17:11:54 +02002226 // In case of stackFromBottom the calculation of whichRow needs
2227 // to take into account that counting from the top the first row
2228 // might not be entirely filled.
2229 final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
2230 mItemCount) : 0;
2231 final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
Adam Powellf2a204e2010-02-12 15:25:33 -08002232 return Math.max(whichRow * 100 - (top * 100) / height +
2233 (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002234 }
2235 }
2236 return 0;
2237 }
2238
2239 @Override
2240 protected int computeVerticalScrollRange() {
2241 // TODO: Account for vertical spacing too
2242 final int numColumns = mNumColumns;
2243 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
Adam Powell637d3372010-08-25 14:37:03 -07002244 int result = Math.max(rowCount * 100, 0);
2245 if (mScrollY != 0) {
2246 // Compensate for overscroll
2247 result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2248 }
2249 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002250 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002251
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08002252 @Override
2253 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2254 super.onInitializeAccessibilityEvent(event);
2255 event.setClassName(GridView.class.getName());
2256 }
2257
2258 @Override
2259 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2260 super.onInitializeAccessibilityNodeInfo(info);
2261 info.setClassName(GridView.class.getName());
2262 }
2263}