blob: 15daf83c6003a2ed34e593600d4bcb75c7244191 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
22import android.graphics.Rect;
Romain Guy5fade8c2013-07-10 16:36:18 -070023import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.util.AttributeSet;
25import android.view.Gravity;
26import android.view.KeyEvent;
Winson Chung499cb9f2010-07-16 11:18:17 -070027import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.View;
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -070029import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080031import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette5b2081d2013-08-28 10:43:07 -070033import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
34import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.view.animation.GridLayoutAnimationController;
Alan Viverette5b2081d2013-08-28 10:43:07 -070036import android.widget.AbsListView.LayoutParams;
Winson Chung499cb9f2010-07-16 11:18:17 -070037import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
39
40/**
41 * A view that shows items in two-dimensional scrolling grid. The items in the
42 * grid come from the {@link ListAdapter} associated with this view.
Scott Main41ec6532010-08-19 16:57:07 -070043 *
Scott Main4c359b72012-07-24 15:51:27 -070044 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
45 * View</a> guide.</p>
Romain Guy84c6b952011-02-22 11:15:42 -080046 *
47 * @attr ref android.R.styleable#GridView_horizontalSpacing
48 * @attr ref android.R.styleable#GridView_verticalSpacing
49 * @attr ref android.R.styleable#GridView_stretchMode
50 * @attr ref android.R.styleable#GridView_columnWidth
51 * @attr ref android.R.styleable#GridView_numColumns
52 * @attr ref android.R.styleable#GridView_gravity
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 */
Winson Chung499cb9f2010-07-16 11:18:17 -070054@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055public class GridView extends AbsListView {
Romain Guy84c6b952011-02-22 11:15:42 -080056 /**
57 * Disables stretching.
58 *
59 * @see #setStretchMode(int)
60 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 public static final int NO_STRETCH = 0;
Romain Guy84c6b952011-02-22 11:15:42 -080062 /**
63 * Stretches the spacing between columns.
64 *
65 * @see #setStretchMode(int)
66 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 public static final int STRETCH_SPACING = 1;
Romain Guy84c6b952011-02-22 11:15:42 -080068 /**
69 * Stretches columns.
70 *
71 * @see #setStretchMode(int)
72 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 public static final int STRETCH_COLUMN_WIDTH = 2;
Romain Guy84c6b952011-02-22 11:15:42 -080074 /**
75 * Stretches the spacing between columns. The spacing is uniform.
76 *
77 * @see #setStretchMode(int)
78 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 public static final int STRETCH_SPACING_UNIFORM = 3;
Romain Guy84c6b952011-02-22 11:15:42 -080080
81 /**
82 * Creates as many columns as can fit on screen.
83 *
84 * @see #setNumColumns(int)
85 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 public static final int AUTO_FIT = -1;
87
88 private int mNumColumns = AUTO_FIT;
89
90 private int mHorizontalSpacing = 0;
91 private int mRequestedHorizontalSpacing;
92 private int mVerticalSpacing = 0;
93 private int mStretchMode = STRETCH_COLUMN_WIDTH;
94 private int mColumnWidth;
95 private int mRequestedColumnWidth;
96 private int mRequestedNumColumns;
97
98 private View mReferenceView = null;
99 private View mReferenceViewInSelectedRow = null;
100
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700101 private int mGravity = Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102
103 private final Rect mTempRect = new Rect();
104
105 public GridView(Context context) {
Adam Powell48774532012-03-12 13:41:37 -0700106 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 }
108
109 public GridView(Context context, AttributeSet attrs) {
110 this(context, attrs, com.android.internal.R.attr.gridViewStyle);
111 }
112
113 public GridView(Context context, AttributeSet attrs, int defStyle) {
114 super(context, attrs, defStyle);
115
116 TypedArray a = context.obtainStyledAttributes(attrs,
117 com.android.internal.R.styleable.GridView, defStyle, 0);
118
119 int hSpacing = a.getDimensionPixelOffset(
120 com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
121 setHorizontalSpacing(hSpacing);
122
123 int vSpacing = a.getDimensionPixelOffset(
124 com.android.internal.R.styleable.GridView_verticalSpacing, 0);
125 setVerticalSpacing(vSpacing);
126
127 int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
128 if (index >= 0) {
129 setStretchMode(index);
130 }
131
132 int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
133 if (columnWidth > 0) {
134 setColumnWidth(columnWidth);
135 }
136
137 int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
138 setNumColumns(numColumns);
139
140 index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
141 if (index >= 0) {
142 setGravity(index);
143 }
144
145 a.recycle();
146 }
147
148 @Override
149 public ListAdapter getAdapter() {
150 return mAdapter;
151 }
152
153 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700154 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
155 * through the specified intent.
156 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
157 */
158 @android.view.RemotableViewMethod
159 public void setRemoteViewsAdapter(Intent intent) {
160 super.setRemoteViewsAdapter(intent);
161 }
162
163 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 * Sets the data behind this GridView.
165 *
166 * @param adapter the adapter providing the grid's data
167 */
168 @Override
169 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700170 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 mAdapter.unregisterDataSetObserver(mDataSetObserver);
172 }
173
174 resetList();
175 mRecycler.clear();
176 mAdapter = adapter;
177
178 mOldSelectedPosition = INVALID_POSITION;
179 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700180
181 // AbsListView#setAdapter will update choice mode states.
182 super.setAdapter(adapter);
183
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 if (mAdapter != null) {
185 mOldItemCount = mItemCount;
186 mItemCount = mAdapter.getCount();
187 mDataChanged = true;
188 checkFocus();
189
190 mDataSetObserver = new AdapterDataSetObserver();
191 mAdapter.registerDataSetObserver(mDataSetObserver);
192
193 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
194
195 int position;
196 if (mStackFromBottom) {
197 position = lookForSelectablePosition(mItemCount - 1, false);
198 } else {
199 position = lookForSelectablePosition(0, true);
200 }
201 setSelectedPositionInt(position);
202 setNextSelectedPositionInt(position);
203 checkSelectionChanged();
204 } else {
205 checkFocus();
206 // Nothing selected
207 checkSelectionChanged();
208 }
209
210 requestLayout();
211 }
212
213 @Override
214 int lookForSelectablePosition(int position, boolean lookDown) {
215 final ListAdapter adapter = mAdapter;
216 if (adapter == null || isInTouchMode()) {
217 return INVALID_POSITION;
218 }
219
220 if (position < 0 || position >= mItemCount) {
221 return INVALID_POSITION;
222 }
223 return position;
224 }
225
226 /**
227 * {@inheritDoc}
228 */
229 @Override
230 void fillGap(boolean down) {
231 final int numColumns = mNumColumns;
232 final int verticalSpacing = mVerticalSpacing;
233
234 final int count = getChildCount();
235
236 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800237 int paddingTop = 0;
238 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
239 paddingTop = getListPaddingTop();
240 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 final int startOffset = count > 0 ?
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800242 getChildAt(count - 1).getBottom() + verticalSpacing : paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 int position = mFirstPosition + count;
244 if (mStackFromBottom) {
245 position += numColumns - 1;
246 }
247 fillDown(position, startOffset);
248 correctTooHigh(numColumns, verticalSpacing, getChildCount());
249 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800250 int paddingBottom = 0;
251 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
252 paddingBottom = getListPaddingBottom();
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(0).getTop() - verticalSpacing : getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 int position = mFirstPosition;
257 if (!mStackFromBottom) {
258 position -= numColumns;
259 } else {
260 position--;
261 }
262 fillUp(position, startOffset);
263 correctTooLow(numColumns, verticalSpacing, getChildCount());
264 }
265 }
266
267 /**
268 * Fills the list from pos down to the end of the list view.
269 *
270 * @param pos The first position to put in the list
271 *
272 * @param nextTop The location where the top of the item associated with pos
273 * should be drawn
274 *
275 * @return The view that is currently selected, if it happens to be in the
276 * range that we draw.
277 */
278 private View fillDown(int pos, int nextTop) {
279 View selectedView = null;
280
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800281 int end = (mBottom - mTop);
282 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
283 end -= mListPadding.bottom;
284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285
286 while (nextTop < end && pos < mItemCount) {
287 View temp = makeRow(pos, nextTop, true);
288 if (temp != null) {
289 selectedView = temp;
290 }
291
Romain Guy8bcdc072009-09-29 15:17:47 -0700292 // mReferenceView will change with each call to makeRow()
293 // do not cache in a local variable outside of this loop
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 nextTop = mReferenceView.getBottom() + mVerticalSpacing;
295
296 pos += mNumColumns;
297 }
298
Adam Cohenb9673922012-01-05 13:58:47 -0800299 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 return selectedView;
301 }
302
303 private View makeRow(int startPos, int y, boolean flow) {
304 final int columnWidth = mColumnWidth;
305 final int horizontalSpacing = mHorizontalSpacing;
306
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700307 final boolean isLayoutRtl = isLayoutRtl();
308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 int last;
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700310 int nextLeft;
311
312 if (isLayoutRtl) {
313 nextLeft = getWidth() - mListPadding.right - columnWidth -
314 ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
315 } else {
316 nextLeft = mListPadding.left +
317 ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319
320 if (!mStackFromBottom) {
321 last = Math.min(startPos + mNumColumns, mItemCount);
322 } else {
323 last = startPos + 1;
324 startPos = Math.max(0, startPos - mNumColumns + 1);
325
326 if (last - startPos < mNumColumns) {
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700327 final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
328 nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
330 }
331
332 View selectedView = null;
333
334 final boolean hasFocus = shouldShowSelector();
335 final boolean inClick = touchModeDrawsInPressedState();
336 final int selectedPosition = mSelectedPosition;
337
Romain Guy8bcdc072009-09-29 15:17:47 -0700338 View child = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 for (int pos = startPos; pos < last; pos++) {
340 // is this the selected item?
341 boolean selected = pos == selectedPosition;
342 // does the list view have focus or contain focus
343
344 final int where = flow ? -1 : pos - startPos;
Romain Guy8bcdc072009-09-29 15:17:47 -0700345 child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346
Fabrice Di Meglioa5987202012-06-08 15:22:20 -0700347 nextLeft += (isLayoutRtl ? -1 : +1) * columnWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 if (pos < last - 1) {
349 nextLeft += horizontalSpacing;
350 }
351
352 if (selected && (hasFocus || inClick)) {
353 selectedView = child;
354 }
355 }
356
Romain Guy8bcdc072009-09-29 15:17:47 -0700357 mReferenceView = child;
358
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 if (selectedView != null) {
360 mReferenceViewInSelectedRow = mReferenceView;
361 }
362
363 return selectedView;
364 }
365
366 /**
367 * Fills the list from pos up to the top of the list view.
368 *
369 * @param pos The first position to put in the list
370 *
371 * @param nextBottom The location where the bottom of the item associated
372 * with pos should be drawn
373 *
374 * @return The view that is currently selected
375 */
376 private View fillUp(int pos, int nextBottom) {
377 View selectedView = null;
378
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800379 int end = 0;
380 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
381 end = mListPadding.top;
382 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383
384 while (nextBottom > end && pos >= 0) {
385
386 View temp = makeRow(pos, nextBottom, false);
387 if (temp != null) {
388 selectedView = temp;
389 }
390
391 nextBottom = mReferenceView.getTop() - mVerticalSpacing;
392
393 mFirstPosition = pos;
394
395 pos -= mNumColumns;
396 }
397
398 if (mStackFromBottom) {
399 mFirstPosition = Math.max(0, pos + 1);
400 }
401
Adam Cohenb9673922012-01-05 13:58:47 -0800402 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800403 return selectedView;
404 }
405
406 /**
407 * Fills the list from top to bottom, starting with mFirstPosition
408 *
409 * @param nextTop The location where the top of the first item should be
410 * drawn
411 *
412 * @return The view that is currently selected
413 */
414 private View fillFromTop(int nextTop) {
415 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
416 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
417 if (mFirstPosition < 0) {
418 mFirstPosition = 0;
419 }
420 mFirstPosition -= mFirstPosition % mNumColumns;
421 return fillDown(mFirstPosition, nextTop);
422 }
423
424 private View fillFromBottom(int lastPosition, int nextBottom) {
425 lastPosition = Math.max(lastPosition, mSelectedPosition);
426 lastPosition = Math.min(lastPosition, mItemCount - 1);
427
428 final int invertedPosition = mItemCount - 1 - lastPosition;
429 lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
430
431 return fillUp(lastPosition, nextBottom);
432 }
433
434 private View fillSelection(int childrenTop, int childrenBottom) {
435 final int selectedPosition = reconcileSelectedPosition();
436 final int numColumns = mNumColumns;
437 final int verticalSpacing = mVerticalSpacing;
438
439 int rowStart;
440 int rowEnd = -1;
441
442 if (!mStackFromBottom) {
443 rowStart = selectedPosition - (selectedPosition % numColumns);
444 } else {
445 final int invertedSelection = mItemCount - 1 - selectedPosition;
446
447 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
448 rowStart = Math.max(0, rowEnd - numColumns + 1);
449 }
450
451 final int fadingEdgeLength = getVerticalFadingEdgeLength();
452 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
453
454 final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
455 mFirstPosition = rowStart;
456
457 final View referenceView = mReferenceView;
458
459 if (!mStackFromBottom) {
460 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
461 pinToBottom(childrenBottom);
462 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
463 adjustViewsUpOrDown();
464 } else {
465 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
466 fadingEdgeLength, numColumns, rowStart);
467 final int offset = bottomSelectionPixel - referenceView.getBottom();
468 offsetChildrenTopAndBottom(offset);
469 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
470 pinToTop(childrenTop);
471 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
472 adjustViewsUpOrDown();
473 }
474
475 return sel;
476 }
477
478 private void pinToTop(int childrenTop) {
479 if (mFirstPosition == 0) {
480 final int top = getChildAt(0).getTop();
481 final int offset = childrenTop - top;
482 if (offset < 0) {
483 offsetChildrenTopAndBottom(offset);
484 }
485 }
486 }
487
488 private void pinToBottom(int childrenBottom) {
489 final int count = getChildCount();
490 if (mFirstPosition + count == mItemCount) {
491 final int bottom = getChildAt(count - 1).getBottom();
492 final int offset = childrenBottom - bottom;
493 if (offset > 0) {
494 offsetChildrenTopAndBottom(offset);
495 }
496 }
497 }
498
499 @Override
500 int findMotionRow(int y) {
501 final int childCount = getChildCount();
502 if (childCount > 0) {
503
504 final int numColumns = mNumColumns;
505 if (!mStackFromBottom) {
506 for (int i = 0; i < childCount; i += numColumns) {
507 if (y <= getChildAt(i).getBottom()) {
508 return mFirstPosition + i;
509 }
510 }
511 } else {
512 for (int i = childCount - 1; i >= 0; i -= numColumns) {
513 if (y >= getChildAt(i).getTop()) {
514 return mFirstPosition + i;
515 }
516 }
517 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 }
519 return INVALID_POSITION;
520 }
521
522 /**
523 * Layout during a scroll that results from tracking motion events. Places
524 * the mMotionPosition view at the offset specified by mMotionViewTop, and
525 * then build surrounding views from there.
526 *
527 * @param position the position at which to start filling
528 * @param top the top of the view at that position
529 * @return The selected view, or null if the selected view is outside the
530 * visible area.
531 */
532 private View fillSpecific(int position, int top) {
533 final int numColumns = mNumColumns;
534
535 int motionRowStart;
536 int motionRowEnd = -1;
537
538 if (!mStackFromBottom) {
539 motionRowStart = position - (position % numColumns);
540 } else {
541 final int invertedSelection = mItemCount - 1 - position;
542
543 motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
544 motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
545 }
546
547 final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
548
549 // Possibly changed again in fillUp if we add rows above this one.
550 mFirstPosition = motionRowStart;
551
552 final View referenceView = mReferenceView;
Romain Guy8bcdc072009-09-29 15:17:47 -0700553 // We didn't have anything to layout, bail out
554 if (referenceView == null) {
555 return null;
556 }
557
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 final int verticalSpacing = mVerticalSpacing;
559
560 View above;
561 View below;
562
563 if (!mStackFromBottom) {
564 above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
565 adjustViewsUpOrDown();
566 below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
567 // Check if we have dragged the bottom of the grid too high
568 final int childCount = getChildCount();
569 if (childCount > 0) {
570 correctTooHigh(numColumns, verticalSpacing, childCount);
571 }
572 } else {
573 below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
574 adjustViewsUpOrDown();
575 above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
576 // Check if we have dragged the bottom of the grid too high
577 final int childCount = getChildCount();
578 if (childCount > 0) {
579 correctTooLow(numColumns, verticalSpacing, childCount);
580 }
581 }
582
583 if (temp != null) {
584 return temp;
585 } else if (above != null) {
586 return above;
587 } else {
588 return below;
589 }
590 }
591
592 private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
593 // First see if the last item is visible
594 final int lastPosition = mFirstPosition + childCount - 1;
595 if (lastPosition == mItemCount - 1 && childCount > 0) {
596 // Get the last child ...
597 final View lastChild = getChildAt(childCount - 1);
598
599 // ... and its bottom edge
600 final int lastBottom = lastChild.getBottom();
601 // This is bottom of our drawable area
602 final int end = (mBottom - mTop) - mListPadding.bottom;
603
604 // This is how far the bottom edge of the last view is from the bottom of the
605 // drawable area
606 int bottomOffset = end - lastBottom;
607
608 final View firstChild = getChildAt(0);
609 final int firstTop = firstChild.getTop();
610
611 // Make sure we are 1) Too high, and 2) Either there are more rows above the
612 // first row or the first row is scrolled off the top of the drawable area
613 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
614 if (mFirstPosition == 0) {
615 // Don't pull the top too far down
616 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
617 }
618
619 // Move everything down
620 offsetChildrenTopAndBottom(bottomOffset);
621 if (mFirstPosition > 0) {
622 // Fill the gap that was opened above mFirstPosition with more rows, if
623 // possible
624 fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
625 firstChild.getTop() - verticalSpacing);
626 // Close up the remaining gap
627 adjustViewsUpOrDown();
628 }
629 }
630 }
631 }
632
633 private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
634 if (mFirstPosition == 0 && childCount > 0) {
635 // Get the first child ...
636 final View firstChild = getChildAt(0);
637
638 // ... and its top edge
639 final int firstTop = firstChild.getTop();
640
641 // This is top of our drawable area
642 final int start = mListPadding.top;
643
644 // This is bottom of our drawable area
645 final int end = (mBottom - mTop) - mListPadding.bottom;
646
647 // This is how far the top edge of the first view is from the top of the
648 // drawable area
649 int topOffset = firstTop - start;
650 final View lastChild = getChildAt(childCount - 1);
651 final int lastBottom = lastChild.getBottom();
652 final int lastPosition = mFirstPosition + childCount - 1;
653
654 // Make sure we are 1) Too low, and 2) Either there are more rows below the
655 // last row or the last row is scrolled off the bottom of the drawable area
656 if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end)) {
657 if (lastPosition == mItemCount - 1 ) {
658 // Don't pull the bottom too far up
659 topOffset = Math.min(topOffset, lastBottom - end);
660 }
661
662 // Move everything up
663 offsetChildrenTopAndBottom(-topOffset);
664 if (lastPosition < mItemCount - 1) {
665 // Fill the gap that was opened below the last position with more rows, if
666 // possible
667 fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
668 lastChild.getBottom() + verticalSpacing);
669 // Close up the remaining gap
670 adjustViewsUpOrDown();
671 }
672 }
673 }
674 }
675
676 /**
677 * Fills the grid based on positioning the new selection at a specific
678 * location. The selection may be moved so that it does not intersect the
679 * faded edges. The grid is then filled upwards and downwards from there.
680 *
681 * @param selectedTop Where the selected item should be
682 * @param childrenTop Where to start drawing children
683 * @param childrenBottom Last pixel where children can be drawn
684 * @return The view that currently has selection
685 */
686 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
687 final int fadingEdgeLength = getVerticalFadingEdgeLength();
688 final int selectedPosition = mSelectedPosition;
689 final int numColumns = mNumColumns;
690 final int verticalSpacing = mVerticalSpacing;
691
692 int rowStart;
693 int rowEnd = -1;
694
695 if (!mStackFromBottom) {
696 rowStart = selectedPosition - (selectedPosition % numColumns);
697 } else {
698 int invertedSelection = mItemCount - 1 - selectedPosition;
699
700 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
701 rowStart = Math.max(0, rowEnd - numColumns + 1);
702 }
703
704 View sel;
705 View referenceView;
706
707 int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
708 int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
709 numColumns, rowStart);
710
711 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
712 // Possibly changed again in fillUp if we add rows above this one.
713 mFirstPosition = rowStart;
714
715 referenceView = mReferenceView;
716 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
717 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
718
719 if (!mStackFromBottom) {
720 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
721 adjustViewsUpOrDown();
722 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
723 } else {
724 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
725 adjustViewsUpOrDown();
726 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
727 }
728
729
730 return sel;
731 }
732
733 /**
734 * Calculate the bottom-most pixel we can draw the selection into
735 *
736 * @param childrenBottom Bottom pixel were children can be drawn
737 * @param fadingEdgeLength Length of the fading edge in pixels, if present
738 * @param numColumns Number of columns in the grid
739 * @param rowStart The start of the row that will contain the selection
740 * @return The bottom-most pixel we can draw the selection into
741 */
742 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
743 int numColumns, int rowStart) {
744 // Last pixel we can draw the selection into
745 int bottomSelectionPixel = childrenBottom;
746 if (rowStart + numColumns - 1 < mItemCount - 1) {
747 bottomSelectionPixel -= fadingEdgeLength;
748 }
749 return bottomSelectionPixel;
750 }
751
752 /**
753 * Calculate the top-most pixel we can draw the selection into
754 *
755 * @param childrenTop Top pixel were children can be drawn
756 * @param fadingEdgeLength Length of the fading edge in pixels, if present
757 * @param rowStart The start of the row that will contain the selection
758 * @return The top-most pixel we can draw the selection into
759 */
760 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
761 // first pixel we can draw the selection into
762 int topSelectionPixel = childrenTop;
763 if (rowStart > 0) {
764 topSelectionPixel += fadingEdgeLength;
765 }
766 return topSelectionPixel;
767 }
768
769 /**
770 * Move all views upwards so the selected row does not interesect the bottom
771 * fading edge (if necessary).
772 *
773 * @param childInSelectedRow A child in the row that contains the selection
774 * @param topSelectionPixel The topmost pixel we can draw the selection into
775 * @param bottomSelectionPixel The bottommost pixel we can draw the
776 * selection into
777 */
778 private void adjustForBottomFadingEdge(View childInSelectedRow,
779 int topSelectionPixel, int bottomSelectionPixel) {
780 // Some of the newly selected item extends below the bottom of the
781 // list
782 if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
783
784 // Find space available above the selection into which we can
785 // scroll upwards
786 int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
787
788 // Find space required to bring the bottom of the selected item
789 // fully into view
790 int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
791 int offset = Math.min(spaceAbove, spaceBelow);
792
793 // Now offset the selected item to get it into view
794 offsetChildrenTopAndBottom(-offset);
795 }
796 }
797
798 /**
799 * Move all views upwards so the selected row does not interesect the top
800 * fading edge (if necessary).
801 *
802 * @param childInSelectedRow A child in the row that contains the selection
803 * @param topSelectionPixel The topmost pixel we can draw the selection into
804 * @param bottomSelectionPixel The bottommost pixel we can draw the
805 * selection into
806 */
807 private void adjustForTopFadingEdge(View childInSelectedRow,
808 int topSelectionPixel, int bottomSelectionPixel) {
809 // Some of the newly selected item extends above the top of the list
810 if (childInSelectedRow.getTop() < topSelectionPixel) {
811 // Find space required to bring the top of the selected item
812 // fully into view
813 int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
814
815 // Find space available below the selection into which we can
816 // scroll downwards
817 int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
818 int offset = Math.min(spaceAbove, spaceBelow);
819
820 // Now offset the selected item to get it into view
821 offsetChildrenTopAndBottom(offset);
822 }
823 }
824
825 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700826 * Smoothly scroll to the specified adapter position. The view will
827 * scroll such that the indicated position is displayed.
828 * @param position Scroll to this adapter position.
829 */
830 @android.view.RemotableViewMethod
831 public void smoothScrollToPosition(int position) {
832 super.smoothScrollToPosition(position);
833 }
834
835 /**
836 * Smoothly scroll to the specified adapter position offset. The view will
837 * scroll such that the indicated position is displayed.
838 * @param offset The amount to offset from the adapter position to scroll to.
839 */
840 @android.view.RemotableViewMethod
841 public void smoothScrollByOffset(int offset) {
842 super.smoothScrollByOffset(offset);
843 }
844
845 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 * Fills the grid based on positioning the new selection relative to the old
847 * selection. The new selection will be placed at, above, or below the
848 * location of the new selection depending on how the selection is moving.
849 * The selection will then be pinned to the visible part of the screen,
850 * excluding the edges that are faded. The grid is then filled upwards and
851 * downwards from there.
852 *
853 * @param delta Which way we are moving
854 * @param childrenTop Where to start drawing children
855 * @param childrenBottom Last pixel where children can be drawn
856 * @return The view that currently has selection
857 */
858 private View moveSelection(int delta, int childrenTop, int childrenBottom) {
859 final int fadingEdgeLength = getVerticalFadingEdgeLength();
860 final int selectedPosition = mSelectedPosition;
861 final int numColumns = mNumColumns;
862 final int verticalSpacing = mVerticalSpacing;
863
864 int oldRowStart;
865 int rowStart;
866 int rowEnd = -1;
867
868 if (!mStackFromBottom) {
869 oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
870
871 rowStart = selectedPosition - (selectedPosition % numColumns);
872 } else {
873 int invertedSelection = mItemCount - 1 - selectedPosition;
874
875 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
876 rowStart = Math.max(0, rowEnd - numColumns + 1);
877
878 invertedSelection = mItemCount - 1 - (selectedPosition - delta);
879 oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
880 oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
881 }
882
883 final int rowDelta = rowStart - oldRowStart;
884
885 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
886 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
887 numColumns, rowStart);
888
889 // Possibly changed again in fillUp if we add rows above this one.
890 mFirstPosition = rowStart;
891
892 View sel;
893 View referenceView;
894
895 if (rowDelta > 0) {
896 /*
897 * Case 1: Scrolling down.
898 */
899
900 final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
901 mReferenceViewInSelectedRow.getBottom();
902
903 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
904 referenceView = mReferenceView;
905
906 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
907 } else if (rowDelta < 0) {
908 /*
909 * Case 2: Scrolling up.
910 */
911 final int oldTop = mReferenceViewInSelectedRow == null ?
912 0 : mReferenceViewInSelectedRow .getTop();
913
914 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
915 referenceView = mReferenceView;
916
917 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
918 } else {
919 /*
920 * Keep selection where it was
921 */
922 final int oldTop = mReferenceViewInSelectedRow == null ?
923 0 : mReferenceViewInSelectedRow .getTop();
924
925 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
926 referenceView = mReferenceView;
927 }
928
929 if (!mStackFromBottom) {
930 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
931 adjustViewsUpOrDown();
932 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
933 } else {
934 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
935 adjustViewsUpOrDown();
936 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
937 }
938
939 return sel;
940 }
941
Adam Lesinskiedd95082010-12-08 12:09:06 -0800942 private boolean determineColumns(int availableSpace) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
944 final int stretchMode = mStretchMode;
945 final int requestedColumnWidth = mRequestedColumnWidth;
Adam Lesinskiedd95082010-12-08 12:09:06 -0800946 boolean didNotInitiallyFit = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947
948 if (mRequestedNumColumns == AUTO_FIT) {
949 if (requestedColumnWidth > 0) {
950 // Client told us to pick the number of columns
951 mNumColumns = (availableSpace + requestedHorizontalSpacing) /
952 (requestedColumnWidth + requestedHorizontalSpacing);
953 } else {
954 // Just make up a number if we don't have enough info
955 mNumColumns = 2;
956 }
957 } else {
958 // We picked the columns
959 mNumColumns = mRequestedNumColumns;
960 }
961
962 if (mNumColumns <= 0) {
963 mNumColumns = 1;
964 }
965
966 switch (stretchMode) {
967 case NO_STRETCH:
968 // Nobody stretches
969 mColumnWidth = requestedColumnWidth;
970 mHorizontalSpacing = requestedHorizontalSpacing;
971 break;
972
973 default:
974 int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
975 ((mNumColumns - 1) * requestedHorizontalSpacing);
Adam Lesinskiedd95082010-12-08 12:09:06 -0800976
977 if (spaceLeftOver < 0) {
978 didNotInitiallyFit = true;
979 }
980
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800981 switch (stretchMode) {
982 case STRETCH_COLUMN_WIDTH:
983 // Stretch the columns
984 mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
985 mHorizontalSpacing = requestedHorizontalSpacing;
986 break;
987
988 case STRETCH_SPACING:
989 // Stretch the spacing between columns
990 mColumnWidth = requestedColumnWidth;
991 if (mNumColumns > 1) {
992 mHorizontalSpacing = requestedHorizontalSpacing +
993 spaceLeftOver / (mNumColumns - 1);
994 } else {
995 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
996 }
997 break;
998
999 case STRETCH_SPACING_UNIFORM:
1000 // Stretch the spacing between columns
1001 mColumnWidth = requestedColumnWidth;
1002 if (mNumColumns > 1) {
1003 mHorizontalSpacing = requestedHorizontalSpacing +
1004 spaceLeftOver / (mNumColumns + 1);
1005 } else {
1006 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
1007 }
1008 break;
1009 }
1010
1011 break;
1012 }
Adam Lesinskiedd95082010-12-08 12:09:06 -08001013 return didNotInitiallyFit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 }
1015
1016 @Override
1017 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1018 // Sets up mListPadding
1019 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1020
1021 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1022 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1023 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1024 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1025
1026 if (widthMode == MeasureSpec.UNSPECIFIED) {
1027 if (mColumnWidth > 0) {
1028 widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
1029 } else {
1030 widthSize = mListPadding.left + mListPadding.right;
1031 }
1032 widthSize += getVerticalScrollbarWidth();
1033 }
1034
1035 int childWidth = widthSize - mListPadding.left - mListPadding.right;
Adam Lesinskiedd95082010-12-08 12:09:06 -08001036 boolean didNotInitiallyFit = determineColumns(childWidth);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037
1038 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001039 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001040
1041 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1042 final int count = mItemCount;
1043 if (count > 0) {
Romain Guy21875052010-01-06 18:48:08 -08001044 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045
Adam Powellaebd28f2012-02-22 10:31:16 -08001046 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001048 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001049 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 }
1051 p.viewType = mAdapter.getItemViewType(0);
Romain Guy0bf88592010-03-02 13:38:44 -08001052 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053
1054 int childHeightSpec = getChildMeasureSpec(
1055 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1056 int childWidthSpec = getChildMeasureSpec(
1057 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1058 child.measure(childWidthSpec, childHeightSpec);
1059
1060 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001061 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062
1063 if (mRecycler.shouldRecycleViewType(p.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001064 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 }
1066 }
1067
1068 if (heightMode == MeasureSpec.UNSPECIFIED) {
1069 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1070 getVerticalFadingEdgeLength() * 2;
1071 }
1072
1073 if (heightMode == MeasureSpec.AT_MOST) {
1074 int ourSize = mListPadding.top + mListPadding.bottom;
1075
1076 final int numColumns = mNumColumns;
1077 for (int i = 0; i < count; i += numColumns) {
1078 ourSize += childHeight;
1079 if (i + numColumns < count) {
1080 ourSize += mVerticalSpacing;
1081 }
1082 if (ourSize >= heightSize) {
1083 ourSize = heightSize;
1084 break;
1085 }
1086 }
1087 heightSize = ourSize;
1088 }
1089
Dianne Hackborn189ee182010-12-02 21:48:53 -08001090 if (widthMode == MeasureSpec.AT_MOST && mRequestedNumColumns != AUTO_FIT) {
1091 int ourSize = (mRequestedNumColumns*mColumnWidth)
1092 + ((mRequestedNumColumns-1)*mHorizontalSpacing)
1093 + mListPadding.left + mListPadding.right;
Adam Lesinskiedd95082010-12-08 12:09:06 -08001094 if (ourSize > widthSize || didNotInitiallyFit) {
Dianne Hackborn189ee182010-12-02 21:48:53 -08001095 widthSize |= MEASURED_STATE_TOO_SMALL;
1096 }
1097 }
1098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 setMeasuredDimension(widthSize, heightSize);
1100 mWidthMeasureSpec = widthMeasureSpec;
1101 }
1102
1103 @Override
1104 protected void attachLayoutAnimationParameters(View child,
1105 ViewGroup.LayoutParams params, int index, int count) {
1106
1107 GridLayoutAnimationController.AnimationParameters animationParams =
1108 (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
1109
1110 if (animationParams == null) {
1111 animationParams = new GridLayoutAnimationController.AnimationParameters();
1112 params.layoutAnimationParameters = animationParams;
1113 }
1114
1115 animationParams.count = count;
1116 animationParams.index = index;
1117 animationParams.columnsCount = mNumColumns;
1118 animationParams.rowsCount = count / mNumColumns;
1119
1120 if (!mStackFromBottom) {
1121 animationParams.column = index % mNumColumns;
1122 animationParams.row = index / mNumColumns;
1123 } else {
1124 final int invertedIndex = count - 1 - index;
1125
1126 animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
1127 animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
1128 }
1129 }
1130
1131 @Override
1132 protected void layoutChildren() {
1133 final boolean blockLayoutRequests = mBlockLayoutRequests;
1134 if (!blockLayoutRequests) {
1135 mBlockLayoutRequests = true;
1136 }
1137
1138 try {
1139 super.layoutChildren();
1140
1141 invalidate();
1142
1143 if (mAdapter == null) {
1144 resetList();
1145 invokeOnItemScrollListener();
1146 return;
1147 }
1148
1149 final int childrenTop = mListPadding.top;
1150 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1151
1152 int childCount = getChildCount();
1153 int index;
1154 int delta = 0;
1155
1156 View sel;
1157 View oldSel = null;
1158 View oldFirst = null;
1159 View newSel = null;
1160
1161 // Remember stuff we will need down below
1162 switch (mLayoutMode) {
1163 case LAYOUT_SET_SELECTION:
1164 index = mNextSelectedPosition - mFirstPosition;
1165 if (index >= 0 && index < childCount) {
1166 newSel = getChildAt(index);
1167 }
1168 break;
1169 case LAYOUT_FORCE_TOP:
1170 case LAYOUT_FORCE_BOTTOM:
1171 case LAYOUT_SPECIFIC:
1172 case LAYOUT_SYNC:
1173 break;
1174 case LAYOUT_MOVE_SELECTION:
1175 if (mNextSelectedPosition >= 0) {
1176 delta = mNextSelectedPosition - mSelectedPosition;
1177 }
1178 break;
1179 default:
1180 // Remember the previously selected view
1181 index = mSelectedPosition - mFirstPosition;
1182 if (index >= 0 && index < childCount) {
1183 oldSel = getChildAt(index);
1184 }
1185
1186 // Remember the previous first child
1187 oldFirst = getChildAt(0);
1188 }
1189
1190 boolean dataChanged = mDataChanged;
1191 if (dataChanged) {
1192 handleDataChanged();
1193 }
1194
1195 // Handle the empty set by removing all views that are visible
1196 // and calling it a day
1197 if (mItemCount == 0) {
1198 resetList();
1199 invokeOnItemScrollListener();
1200 return;
1201 }
1202
1203 setSelectedPositionInt(mNextSelectedPosition);
1204
1205 // Pull all children into the RecycleBin.
1206 // These views will be reused if possible
1207 final int firstPosition = mFirstPosition;
1208 final RecycleBin recycleBin = mRecycler;
1209
1210 if (dataChanged) {
1211 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001212 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
1214 } else {
1215 recycleBin.fillActiveViews(childCount, firstPosition);
1216 }
1217
1218 // Clear out old views
1219 //removeAllViewsInLayout();
1220 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001221 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222
1223 switch (mLayoutMode) {
1224 case LAYOUT_SET_SELECTION:
1225 if (newSel != null) {
1226 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1227 } else {
1228 sel = fillSelection(childrenTop, childrenBottom);
1229 }
1230 break;
1231 case LAYOUT_FORCE_TOP:
1232 mFirstPosition = 0;
1233 sel = fillFromTop(childrenTop);
1234 adjustViewsUpOrDown();
1235 break;
1236 case LAYOUT_FORCE_BOTTOM:
1237 sel = fillUp(mItemCount - 1, childrenBottom);
1238 adjustViewsUpOrDown();
1239 break;
1240 case LAYOUT_SPECIFIC:
1241 sel = fillSpecific(mSelectedPosition, mSpecificTop);
1242 break;
1243 case LAYOUT_SYNC:
1244 sel = fillSpecific(mSyncPosition, mSpecificTop);
1245 break;
1246 case LAYOUT_MOVE_SELECTION:
1247 // Move the selection relative to its old position
1248 sel = moveSelection(delta, childrenTop, childrenBottom);
1249 break;
1250 default:
1251 if (childCount == 0) {
1252 if (!mStackFromBottom) {
Romain Guy91c86132010-03-26 17:29:45 -07001253 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1254 INVALID_POSITION : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 sel = fillFromTop(childrenTop);
1256 } else {
1257 final int last = mItemCount - 1;
Romain Guy91c86132010-03-26 17:29:45 -07001258 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1259 INVALID_POSITION : last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 sel = fillFromBottom(last, childrenBottom);
1261 }
1262 } else {
1263 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1264 sel = fillSpecific(mSelectedPosition, oldSel == null ?
1265 childrenTop : oldSel.getTop());
1266 } else if (mFirstPosition < mItemCount) {
1267 sel = fillSpecific(mFirstPosition, oldFirst == null ?
1268 childrenTop : oldFirst.getTop());
1269 } else {
1270 sel = fillSpecific(0, childrenTop);
1271 }
1272 }
1273 break;
1274 }
1275
1276 // Flush any cached views that did not get reused above
1277 recycleBin.scrapActiveViews();
1278
1279 if (sel != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001280 positionSelector(INVALID_POSITION, sel);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 mSelectedTop = sel.getTop();
Romain Guy3616a412009-09-15 13:50:37 -07001282 } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1283 View child = getChildAt(mMotionPosition - mFirstPosition);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001284 if (child != null) positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 } else {
Romain Guy3616a412009-09-15 13:50:37 -07001286 mSelectedTop = 0;
1287 mSelectorRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 }
1289
1290 mLayoutMode = LAYOUT_NORMAL;
1291 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001292 if (mPositionScrollAfterLayout != null) {
1293 post(mPositionScrollAfterLayout);
1294 mPositionScrollAfterLayout = null;
1295 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001296 mNeedSync = false;
1297 setNextSelectedPositionInt(mSelectedPosition);
1298
1299 updateScrollIndicators();
1300
1301 if (mItemCount > 0) {
1302 checkSelectionChanged();
1303 }
1304
1305 invokeOnItemScrollListener();
1306 } finally {
1307 if (!blockLayoutRequests) {
1308 mBlockLayoutRequests = false;
1309 }
1310 }
1311 }
1312
1313
1314 /**
1315 * Obtain the view and add it to our list of children. The view can be made
1316 * fresh, converted from an unused view, or used as is if it was in the
1317 * recycle bin.
1318 *
1319 * @param position Logical position in the list
1320 * @param y Top or bottom edge of the view to add
1321 * @param flow if true, align top edge to y. If false, align bottom edge to
1322 * y.
1323 * @param childrenLeft Left edge where children should be positioned
1324 * @param selected Is this position selected?
1325 * @param where to add new item in the list
1326 * @return View that was added
1327 */
1328 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1329 boolean selected, int where) {
1330 View child;
1331
1332 if (!mDataChanged) {
Romain Guy21875052010-01-06 18:48:08 -08001333 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001334 child = mRecycler.getActiveView(position);
1335 if (child != null) {
1336 // Found it -- we're using an existing child
1337 // This just needs to be positioned
1338 setupChild(child, position, y, flow, childrenLeft, selected, true, where);
1339 return child;
1340 }
1341 }
1342
1343 // Make a new view for this position, or convert an unused view if
1344 // possible
Romain Guy21875052010-01-06 18:48:08 -08001345 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346
1347 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001348 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349
1350 return child;
1351 }
1352
1353 /**
1354 * Add a view as a child and make sure it is measured (if necessary) and
1355 * positioned properly.
1356 *
1357 * @param child The view to add
1358 * @param position The position of the view
1359 * @param y The y position relative to which this view will be positioned
1360 * @param flow if true, align top edge to y. If false, align bottom edge
1361 * to y.
1362 * @param childrenLeft Left edge where children should be positioned
1363 * @param selected Is this position selected?
1364 * @param recycled Has this view been pulled from the recycle bin? If so it
1365 * does not need to be remeasured.
1366 * @param where Where to add the item in the list
1367 *
1368 */
1369 private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
1370 boolean selected, boolean recycled, int where) {
Romain Guy5fade8c2013-07-10 16:36:18 -07001371 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupGridItem");
1372
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 boolean isSelected = selected && shouldShowSelector();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001375 final int mode = mTouchMode;
1376 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1377 mMotionPosition == position;
1378 final boolean updateChildPressed = isPressed != child.isPressed();
1379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1381
1382 // Respect layout params that are already in the view. Otherwise make
1383 // some up...
Adam Powellaebd28f2012-02-22 10:31:16 -08001384 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001385 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001386 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001387 }
1388 p.viewType = mAdapter.getItemViewType(position);
1389
Romain Guy0bf88592010-03-02 13:38:44 -08001390 if (recycled && !p.forceAdd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 attachViewToParent(child, where, p);
1392 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001393 p.forceAdd = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001394 addViewInLayout(child, where, p, true);
1395 }
1396
1397 if (updateChildSelected) {
1398 child.setSelected(isSelected);
1399 if (isSelected) {
1400 requestFocus();
1401 }
1402 }
1403
Romain Guy3616a412009-09-15 13:50:37 -07001404 if (updateChildPressed) {
1405 child.setPressed(isPressed);
1406 }
1407
Adam Powellf343e1b2010-08-13 18:27:04 -07001408 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1409 if (child instanceof Checkable) {
1410 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001411 } else if (getContext().getApplicationInfo().targetSdkVersion
1412 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1413 child.setActivated(mCheckStates.get(position));
Adam Powellf343e1b2010-08-13 18:27:04 -07001414 }
1415 }
1416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417 if (needToMeasure) {
1418 int childHeightSpec = ViewGroup.getChildMeasureSpec(
1419 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1420
1421 int childWidthSpec = ViewGroup.getChildMeasureSpec(
1422 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1423 child.measure(childWidthSpec, childHeightSpec);
1424 } else {
1425 cleanupLayoutState(child);
1426 }
1427
1428 final int w = child.getMeasuredWidth();
1429 final int h = child.getMeasuredHeight();
1430
1431 int childLeft;
1432 final int childTop = flow ? y : y - h;
1433
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001434 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07001435 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07001436 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
1437 case Gravity.LEFT:
1438 childLeft = childrenLeft;
1439 break;
1440 case Gravity.CENTER_HORIZONTAL:
1441 childLeft = childrenLeft + ((mColumnWidth - w) / 2);
1442 break;
1443 case Gravity.RIGHT:
1444 childLeft = childrenLeft + mColumnWidth - w;
1445 break;
1446 default:
1447 childLeft = childrenLeft;
1448 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 }
1450
1451 if (needToMeasure) {
1452 final int childRight = childLeft + w;
1453 final int childBottom = childTop + h;
1454 child.layout(childLeft, childTop, childRight, childBottom);
1455 } else {
1456 child.offsetLeftAndRight(childLeft - child.getLeft());
1457 child.offsetTopAndBottom(childTop - child.getTop());
1458 }
1459
1460 if (mCachingStarted) {
1461 child.setDrawingCacheEnabled(true);
1462 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001463
1464 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1465 != position) {
1466 child.jumpDrawablesToCurrentState();
1467 }
Romain Guy5fade8c2013-07-10 16:36:18 -07001468
1469 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 }
1471
1472 /**
1473 * Sets the currently selected item
1474 *
1475 * @param position Index (starting at 0) of the data item to be selected.
1476 *
1477 * If in touch mode, the item will not be selected but it will still be positioned
1478 * appropriately.
1479 */
1480 @Override
1481 public void setSelection(int position) {
1482 if (!isInTouchMode()) {
1483 setNextSelectedPositionInt(position);
1484 } else {
1485 mResurrectToPosition = position;
1486 }
1487 mLayoutMode = LAYOUT_SET_SELECTION;
Adam Powell1fa179ef2012-04-12 15:01:40 -07001488 if (mPositionScroller != null) {
1489 mPositionScroller.stop();
1490 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 requestLayout();
1492 }
1493
1494 /**
1495 * Makes the item at the supplied position selected.
1496 *
1497 * @param position the position of the new selection
1498 */
1499 @Override
1500 void setSelectionInt(int position) {
Mike Cleronf116bf82009-09-27 19:14:12 -07001501 int previousSelectedPosition = mNextSelectedPosition;
1502
Adam Powell1fa179ef2012-04-12 15:01:40 -07001503 if (mPositionScroller != null) {
1504 mPositionScroller.stop();
1505 }
1506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001507 setNextSelectedPositionInt(position);
1508 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07001509
1510 final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
1511 mNextSelectedPosition;
1512 final int previous = mStackFromBottom ? mItemCount - 1
1513 - previousSelectedPosition : previousSelectedPosition;
1514
1515 final int nextRow = next / mNumColumns;
1516 final int previousRow = previous / mNumColumns;
1517
1518 if (nextRow != previousRow) {
1519 awakenScrollBars();
1520 }
1521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522 }
1523
1524 @Override
1525 public boolean onKeyDown(int keyCode, KeyEvent event) {
1526 return commonKey(keyCode, 1, event);
1527 }
1528
1529 @Override
1530 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1531 return commonKey(keyCode, repeatCount, event);
1532 }
1533
1534 @Override
1535 public boolean onKeyUp(int keyCode, KeyEvent event) {
1536 return commonKey(keyCode, 1, event);
1537 }
1538
1539 private boolean commonKey(int keyCode, int count, KeyEvent event) {
1540 if (mAdapter == null) {
1541 return false;
1542 }
1543
1544 if (mDataChanged) {
1545 layoutChildren();
1546 }
1547
1548 boolean handled = false;
1549 int action = event.getAction();
1550
1551 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001552 switch (keyCode) {
1553 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001554 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001555 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001557 break;
1558
1559 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001560 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001561 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001562 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001563 break;
1564
1565 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001566 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001567 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
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_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001570 }
1571 break;
1572
1573 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08001574 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001575 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001576 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001577 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001578 }
1579 break;
1580
1581 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001582 case KeyEvent.KEYCODE_ENTER:
1583 if (event.hasNoModifiers()) {
1584 handled = resurrectSelectionIfNeeded();
1585 if (!handled
1586 && event.getRepeatCount() == 0 && getChildCount() > 0) {
1587 keyPressed();
1588 handled = true;
1589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001590 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001591 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001592
1593 case KeyEvent.KEYCODE_SPACE:
1594 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08001595 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001596 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001597 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001598 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001599 }
1600 }
1601 break;
1602
1603 case KeyEvent.KEYCODE_PAGE_UP:
1604 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001605 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
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_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001608 }
1609 break;
1610
1611 case KeyEvent.KEYCODE_PAGE_DOWN:
1612 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001613 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001614 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001615 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001616 }
1617 break;
1618
1619 case KeyEvent.KEYCODE_MOVE_HOME:
1620 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001621 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001622 }
1623 break;
1624
1625 case KeyEvent.KEYCODE_MOVE_END:
1626 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001627 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001628 }
1629 break;
1630
1631 case KeyEvent.KEYCODE_TAB:
1632 // XXX Sometimes it is useful to be able to TAB through the items in
1633 // a GridView sequentially. Unfortunately this can create an
1634 // asymmetry in TAB navigation order unless the list selection
1635 // always reverts to the top or bottom when receiving TAB focus from
1636 // another widget. Leaving this behavior disabled for now but
1637 // perhaps it should be configurable (and more comprehensive).
1638 if (false) {
1639 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001640 handled = resurrectSelectionIfNeeded()
1641 || sequenceScroll(FOCUS_FORWARD);
Jeff Brown4e6319b2010-12-13 10:36:51 -08001642 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001643 handled = resurrectSelectionIfNeeded()
1644 || sequenceScroll(FOCUS_BACKWARD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001645 }
1646 }
1647 break;
1648 }
1649 }
1650
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001651 if (handled) {
1652 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08001653 }
1654
1655 if (sendToTextFilter(keyCode, count, event)) {
1656 return true;
1657 }
1658
1659 switch (action) {
1660 case KeyEvent.ACTION_DOWN:
1661 return super.onKeyDown(keyCode, event);
1662 case KeyEvent.ACTION_UP:
1663 return super.onKeyUp(keyCode, event);
1664 case KeyEvent.ACTION_MULTIPLE:
1665 return super.onKeyMultiple(keyCode, count, event);
1666 default:
1667 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001668 }
1669 }
1670
1671 /**
1672 * Scrolls up or down by the number of items currently present on screen.
1673 *
1674 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1675 * @return whether selection was moved
1676 */
1677 boolean pageScroll(int direction) {
1678 int nextPage = -1;
1679
1680 if (direction == FOCUS_UP) {
Romain Guy64d50a62010-08-20 10:50:49 -07001681 nextPage = Math.max(0, mSelectedPosition - getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001682 } else if (direction == FOCUS_DOWN) {
Romain Guy64d50a62010-08-20 10:50:49 -07001683 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001684 }
1685
1686 if (nextPage >= 0) {
1687 setSelectionInt(nextPage);
1688 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07001689 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690 return true;
1691 }
1692
1693 return false;
1694 }
1695
1696 /**
1697 * Go to the last or first item if possible.
1698 *
1699 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1700 *
1701 * @return Whether selection was moved.
1702 */
1703 boolean fullScroll(int direction) {
1704 boolean moved = false;
1705 if (direction == FOCUS_UP) {
1706 mLayoutMode = LAYOUT_SET_SELECTION;
1707 setSelectionInt(0);
1708 invokeOnItemScrollListener();
1709 moved = true;
1710 } else if (direction == FOCUS_DOWN) {
1711 mLayoutMode = LAYOUT_SET_SELECTION;
1712 setSelectionInt(mItemCount - 1);
1713 invokeOnItemScrollListener();
1714 moved = true;
1715 }
Mike Cleronf116bf82009-09-27 19:14:12 -07001716
1717 if (moved) {
1718 awakenScrollBars();
1719 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001720
1721 return moved;
1722 }
1723
1724 /**
1725 * Scrolls to the next or previous item, horizontally or vertically.
1726 *
1727 * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1728 * {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1729 *
1730 * @return whether selection was moved
1731 */
1732 boolean arrowScroll(int direction) {
1733 final int selectedPosition = mSelectedPosition;
1734 final int numColumns = mNumColumns;
1735
1736 int startOfRowPos;
1737 int endOfRowPos;
1738
1739 boolean moved = false;
1740
1741 if (!mStackFromBottom) {
1742 startOfRowPos = (selectedPosition / numColumns) * numColumns;
1743 endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1744 } else {
1745 final int invertedSelection = mItemCount - 1 - selectedPosition;
1746 endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1747 startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1748 }
1749
1750 switch (direction) {
1751 case FOCUS_UP:
1752 if (startOfRowPos > 0) {
1753 mLayoutMode = LAYOUT_MOVE_SELECTION;
1754 setSelectionInt(Math.max(0, selectedPosition - numColumns));
1755 moved = true;
1756 }
1757 break;
1758 case FOCUS_DOWN:
1759 if (endOfRowPos < mItemCount - 1) {
1760 mLayoutMode = LAYOUT_MOVE_SELECTION;
1761 setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1762 moved = true;
1763 }
1764 break;
1765 case FOCUS_LEFT:
1766 if (selectedPosition > startOfRowPos) {
1767 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001768 setSelectionInt(Math.max(0, selectedPosition - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001769 moved = true;
1770 }
1771 break;
1772 case FOCUS_RIGHT:
1773 if (selectedPosition < endOfRowPos) {
1774 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001775 setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776 moved = true;
1777 }
1778 break;
1779 }
1780
1781 if (moved) {
1782 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1783 invokeOnItemScrollListener();
1784 }
1785
Mike Cleronf116bf82009-09-27 19:14:12 -07001786 if (moved) {
1787 awakenScrollBars();
1788 }
1789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001790 return moved;
1791 }
1792
Jeff Brown4e6319b2010-12-13 10:36:51 -08001793 /**
1794 * Goes to the next or previous item according to the order set by the
1795 * adapter.
1796 */
1797 boolean sequenceScroll(int direction) {
1798 int selectedPosition = mSelectedPosition;
1799 int numColumns = mNumColumns;
1800 int count = mItemCount;
1801
1802 int startOfRow;
1803 int endOfRow;
1804 if (!mStackFromBottom) {
1805 startOfRow = (selectedPosition / numColumns) * numColumns;
1806 endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
1807 } else {
1808 int invertedSelection = count - 1 - selectedPosition;
1809 endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
1810 startOfRow = Math.max(0, endOfRow - numColumns + 1);
1811 }
1812
1813 boolean moved = false;
1814 boolean showScroll = false;
1815 switch (direction) {
1816 case FOCUS_FORWARD:
1817 if (selectedPosition < count - 1) {
1818 // Move to the next item.
1819 mLayoutMode = LAYOUT_MOVE_SELECTION;
1820 setSelectionInt(selectedPosition + 1);
1821 moved = true;
1822 // Show the scrollbar only if changing rows.
1823 showScroll = selectedPosition == endOfRow;
1824 }
1825 break;
1826
1827 case FOCUS_BACKWARD:
1828 if (selectedPosition > 0) {
1829 // Move to the previous item.
1830 mLayoutMode = LAYOUT_MOVE_SELECTION;
1831 setSelectionInt(selectedPosition - 1);
1832 moved = true;
1833 // Show the scrollbar only if changing rows.
1834 showScroll = selectedPosition == startOfRow;
1835 }
1836 break;
1837 }
1838
1839 if (moved) {
1840 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1841 invokeOnItemScrollListener();
1842 }
1843
1844 if (showScroll) {
1845 awakenScrollBars();
1846 }
1847
1848 return moved;
1849 }
1850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001851 @Override
1852 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1853 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1854
1855 int closestChildIndex = -1;
1856 if (gainFocus && previouslyFocusedRect != null) {
1857 previouslyFocusedRect.offset(mScrollX, mScrollY);
1858
1859 // figure out which item should be selected based on previously
1860 // focused rect
1861 Rect otherRect = mTempRect;
1862 int minDistance = Integer.MAX_VALUE;
1863 final int childCount = getChildCount();
1864 for (int i = 0; i < childCount; i++) {
1865 // only consider view's on appropriate edge of grid
1866 if (!isCandidateSelection(i, direction)) {
1867 continue;
1868 }
1869
1870 final View other = getChildAt(i);
1871 other.getDrawingRect(otherRect);
1872 offsetDescendantRectToMyCoords(other, otherRect);
1873 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1874
1875 if (distance < minDistance) {
1876 minDistance = distance;
1877 closestChildIndex = i;
1878 }
1879 }
1880 }
1881
1882 if (closestChildIndex >= 0) {
1883 setSelection(closestChildIndex + mFirstPosition);
1884 } else {
1885 requestLayout();
1886 }
1887 }
1888
1889 /**
1890 * Is childIndex a candidate for next focus given the direction the focus
1891 * change is coming from?
1892 * @param childIndex The index to check.
1893 * @param direction The direction, one of
Jeff Brown4e6319b2010-12-13 10:36:51 -08001894 * {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001895 * @return Whether childIndex is a candidate.
1896 */
1897 private boolean isCandidateSelection(int childIndex, int direction) {
1898 final int count = getChildCount();
1899 final int invertedIndex = count - 1 - childIndex;
1900
1901 int rowStart;
1902 int rowEnd;
1903
1904 if (!mStackFromBottom) {
1905 rowStart = childIndex - (childIndex % mNumColumns);
1906 rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1907 } else {
1908 rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1909 rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1910 }
1911
1912 switch (direction) {
1913 case View.FOCUS_RIGHT:
1914 // coming from left, selection is only valid if it is on left
1915 // edge
1916 return childIndex == rowStart;
1917 case View.FOCUS_DOWN:
1918 // coming from top; only valid if in top row
1919 return rowStart == 0;
1920 case View.FOCUS_LEFT:
1921 // coming from right, must be on right edge
1922 return childIndex == rowEnd;
1923 case View.FOCUS_UP:
1924 // coming from bottom, need to be in last row
1925 return rowEnd == count - 1;
Jeff Brown4e6319b2010-12-13 10:36:51 -08001926 case View.FOCUS_FORWARD:
1927 // coming from top-left, need to be first in top row
1928 return childIndex == rowStart && rowStart == 0;
1929 case View.FOCUS_BACKWARD:
1930 // coming from bottom-right, need to be last in bottom row
1931 return childIndex == rowEnd && rowEnd == count - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001932 default:
1933 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08001934 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
1935 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001936 }
1937 }
1938
1939 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07001940 * Set the gravity for this grid. Gravity describes how the child views
1941 * are horizontally aligned. Defaults to Gravity.LEFT
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001942 *
1943 * @param gravity the gravity to apply to this grid's children
1944 *
1945 * @attr ref android.R.styleable#GridView_gravity
1946 */
1947 public void setGravity(int gravity) {
1948 if (mGravity != gravity) {
1949 mGravity = gravity;
1950 requestLayoutIfNecessary();
1951 }
1952 }
1953
1954 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07001955 * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
1956 *
1957 * @return the gravity that will be applied to this grid's children
1958 *
1959 * @attr ref android.R.styleable#GridView_gravity
1960 */
1961 public int getGravity() {
1962 return mGravity;
1963 }
1964
1965 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001966 * Set the amount of horizontal (x) spacing to place between each item
1967 * in the grid.
1968 *
1969 * @param horizontalSpacing The amount of horizontal space between items,
1970 * in pixels.
1971 *
1972 * @attr ref android.R.styleable#GridView_horizontalSpacing
1973 */
1974 public void setHorizontalSpacing(int horizontalSpacing) {
1975 if (horizontalSpacing != mRequestedHorizontalSpacing) {
1976 mRequestedHorizontalSpacing = horizontalSpacing;
1977 requestLayoutIfNecessary();
1978 }
1979 }
1980
Adam Powell0b7413d2012-03-21 14:51:41 -07001981 /**
1982 * Returns the amount of horizontal spacing currently used between each item in the grid.
1983 *
1984 * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
1985 * has been called but layout is not yet complete, this method may return a stale value.
1986 * To get the horizontal spacing that was explicitly requested use
1987 * {@link #getRequestedHorizontalSpacing()}.</p>
1988 *
1989 * @return Current horizontal spacing between each item in pixels
1990 *
1991 * @see #setHorizontalSpacing(int)
1992 * @see #getRequestedHorizontalSpacing()
1993 *
1994 * @attr ref android.R.styleable#GridView_horizontalSpacing
1995 */
1996 public int getHorizontalSpacing() {
1997 return mHorizontalSpacing;
1998 }
1999
2000 /**
2001 * Returns the requested amount of horizontal spacing between each item in the grid.
2002 *
2003 * <p>The value returned may have been supplied during inflation as part of a style,
2004 * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
2005 * If layout is not yet complete or if GridView calculated a different horizontal spacing
2006 * from what was requested, this may return a different value from
2007 * {@link #getHorizontalSpacing()}.</p>
2008 *
2009 * @return The currently requested horizontal spacing between items, in pixels
2010 *
2011 * @see #setHorizontalSpacing(int)
2012 * @see #getHorizontalSpacing()
2013 *
2014 * @attr ref android.R.styleable#GridView_horizontalSpacing
2015 */
2016 public int getRequestedHorizontalSpacing() {
2017 return mRequestedHorizontalSpacing;
2018 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002019
2020 /**
2021 * Set the amount of vertical (y) spacing to place between each item
2022 * in the grid.
2023 *
2024 * @param verticalSpacing The amount of vertical space between items,
2025 * in pixels.
2026 *
Adam Powell0b7413d2012-03-21 14:51:41 -07002027 * @see #getVerticalSpacing()
2028 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002029 * @attr ref android.R.styleable#GridView_verticalSpacing
2030 */
2031 public void setVerticalSpacing(int verticalSpacing) {
2032 if (verticalSpacing != mVerticalSpacing) {
2033 mVerticalSpacing = verticalSpacing;
2034 requestLayoutIfNecessary();
2035 }
2036 }
2037
2038 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07002039 * Returns the amount of vertical spacing between each item in the grid.
2040 *
2041 * @return The vertical spacing between items in pixels
2042 *
2043 * @see #setVerticalSpacing(int)
2044 *
2045 * @attr ref android.R.styleable#GridView_verticalSpacing
2046 */
2047 public int getVerticalSpacing() {
2048 return mVerticalSpacing;
2049 }
2050
2051 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002052 * Control how items are stretched to fill their space.
2053 *
2054 * @param stretchMode Either {@link #NO_STRETCH},
2055 * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
2056 *
2057 * @attr ref android.R.styleable#GridView_stretchMode
2058 */
2059 public void setStretchMode(int stretchMode) {
2060 if (stretchMode != mStretchMode) {
2061 mStretchMode = stretchMode;
2062 requestLayoutIfNecessary();
2063 }
2064 }
2065
2066 public int getStretchMode() {
2067 return mStretchMode;
2068 }
2069
2070 /**
2071 * Set the width of columns in the grid.
2072 *
2073 * @param columnWidth The column width, in pixels.
2074 *
2075 * @attr ref android.R.styleable#GridView_columnWidth
2076 */
2077 public void setColumnWidth(int columnWidth) {
2078 if (columnWidth != mRequestedColumnWidth) {
2079 mRequestedColumnWidth = columnWidth;
2080 requestLayoutIfNecessary();
2081 }
2082 }
2083
2084 /**
Adam Powell0b7413d2012-03-21 14:51:41 -07002085 * Return the width of a column in the grid.
2086 *
2087 * <p>This may not be valid yet if a layout is pending.</p>
2088 *
2089 * @return The column width in pixels
2090 *
2091 * @see #setColumnWidth(int)
2092 * @see #getRequestedColumnWidth()
2093 *
2094 * @attr ref android.R.styleable#GridView_columnWidth
2095 */
2096 public int getColumnWidth() {
2097 return mColumnWidth;
2098 }
2099
2100 /**
2101 * Return the requested width of a column in the grid.
2102 *
2103 * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
2104 * to retrieve the current real width of a column.</p>
2105 *
2106 * @return The requested column width in pixels
2107 *
2108 * @see #setColumnWidth(int)
2109 * @see #getColumnWidth()
2110 *
2111 * @attr ref android.R.styleable#GridView_columnWidth
2112 */
2113 public int getRequestedColumnWidth() {
2114 return mRequestedColumnWidth;
2115 }
2116
2117 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002118 * Set the number of columns in the grid
2119 *
2120 * @param numColumns The desired number of columns.
2121 *
2122 * @attr ref android.R.styleable#GridView_numColumns
2123 */
2124 public void setNumColumns(int numColumns) {
2125 if (numColumns != mRequestedNumColumns) {
2126 mRequestedNumColumns = numColumns;
2127 requestLayoutIfNecessary();
2128 }
2129 }
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -07002130
2131 /**
2132 * Get the number of columns in the grid.
2133 * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
2134 *
2135 * @attr ref android.R.styleable#GridView_numColumns
2136 *
2137 * @see #setNumColumns(int)
2138 */
2139 @ViewDebug.ExportedProperty
2140 public int getNumColumns() {
2141 return mNumColumns;
2142 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002143
2144 /**
2145 * Make sure views are touching the top or bottom edge, as appropriate for
2146 * our gravity
2147 */
2148 private void adjustViewsUpOrDown() {
2149 final int childCount = getChildCount();
2150
2151 if (childCount > 0) {
2152 int delta;
2153 View child;
2154
2155 if (!mStackFromBottom) {
2156 // Uh-oh -- we came up short. Slide all views up to make them
2157 // align with the top
2158 child = getChildAt(0);
2159 delta = child.getTop() - mListPadding.top;
2160 if (mFirstPosition != 0) {
2161 // It's OK to have some space above the first item if it is
2162 // part of the vertical spacing
2163 delta -= mVerticalSpacing;
2164 }
2165 if (delta < 0) {
2166 // We only are looking to see if we are too low, not too high
2167 delta = 0;
2168 }
2169 } else {
2170 // we are too high, slide all views down to align with bottom
2171 child = getChildAt(childCount - 1);
2172 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
2173
2174 if (mFirstPosition + childCount < mItemCount) {
2175 // It's OK to have some space below the last item if it is
2176 // part of the vertical spacing
2177 delta += mVerticalSpacing;
2178 }
2179
2180 if (delta > 0) {
2181 // We only are looking to see if we are too high, not too low
2182 delta = 0;
2183 }
2184 }
2185
2186 if (delta != 0) {
2187 offsetChildrenTopAndBottom(-delta);
2188 }
2189 }
2190 }
2191
2192 @Override
2193 protected int computeVerticalScrollExtent() {
2194 final int count = getChildCount();
2195 if (count > 0) {
2196 final int numColumns = mNumColumns;
2197 final int rowCount = (count + numColumns - 1) / numColumns;
2198
2199 int extent = rowCount * 100;
2200
2201 View view = getChildAt(0);
2202 final int top = view.getTop();
2203 int height = view.getHeight();
2204 if (height > 0) {
2205 extent += (top * 100) / height;
2206 }
2207
2208 view = getChildAt(count - 1);
2209 final int bottom = view.getBottom();
2210 height = view.getHeight();
2211 if (height > 0) {
2212 extent -= ((bottom - getHeight()) * 100) / height;
2213 }
2214
2215 return extent;
2216 }
2217 return 0;
2218 }
2219
2220 @Override
2221 protected int computeVerticalScrollOffset() {
2222 if (mFirstPosition >= 0 && getChildCount() > 0) {
2223 final View view = getChildAt(0);
2224 final int top = view.getTop();
2225 int height = view.getHeight();
2226 if (height > 0) {
Adam Powellf2a204e2010-02-12 15:25:33 -08002227 final int numColumns = mNumColumns;
Adam Powellf2a204e2010-02-12 15:25:33 -08002228 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
Pieter-Jan Vandormaelbbf7b4c2012-06-16 17:11:54 +02002229 // In case of stackFromBottom the calculation of whichRow needs
2230 // to take into account that counting from the top the first row
2231 // might not be entirely filled.
2232 final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
2233 mItemCount) : 0;
2234 final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
Adam Powellf2a204e2010-02-12 15:25:33 -08002235 return Math.max(whichRow * 100 - (top * 100) / height +
2236 (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002237 }
2238 }
2239 return 0;
2240 }
2241
2242 @Override
2243 protected int computeVerticalScrollRange() {
2244 // TODO: Account for vertical spacing too
2245 final int numColumns = mNumColumns;
2246 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
Adam Powell637d3372010-08-25 14:37:03 -07002247 int result = Math.max(rowCount * 100, 0);
2248 if (mScrollY != 0) {
2249 // Compensate for overscroll
2250 result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
2251 }
2252 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002253 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002254
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08002255 @Override
2256 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2257 super.onInitializeAccessibilityEvent(event);
2258 event.setClassName(GridView.class.getName());
2259 }
2260
2261 @Override
2262 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2263 super.onInitializeAccessibilityNodeInfo(info);
2264 info.setClassName(GridView.class.getName());
Alan Viverette5b2081d2013-08-28 10:43:07 -07002265
2266 final int columnsCount = getNumColumns();
2267 final int rowsCount = getCount() / columnsCount;
2268 final CollectionInfo collectionInfo = CollectionInfo.obtain(columnsCount, rowsCount, false);
2269 info.setCollectionInfo(collectionInfo);
2270 }
2271
2272 @Override
2273 public void onInitializeAccessibilityNodeInfoForItem(
2274 View view, int position, AccessibilityNodeInfo info) {
2275 super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
2276
2277 final int count = getCount();
2278 final int columnsCount = getNumColumns();
2279 final int rowsCount = count / columnsCount;
2280
2281 final int row;
2282 final int column;
2283 if (!mStackFromBottom) {
2284 column = position % columnsCount;
2285 row = position / columnsCount;
2286 } else {
2287 final int invertedIndex = count - 1 - position;
2288
2289 column = columnsCount - 1 - (invertedIndex % columnsCount);
2290 row = rowsCount - 1 - invertedIndex / columnsCount;
2291 }
2292
2293 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
2294 final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
2295 final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(column, 1, row, 1, isHeading);
2296 info.setCollectionItemInfo(itemInfo);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08002297 }
2298}