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