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