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