blob: 414646023a12838cc686d9c631dac9582cf0c285 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
22import android.graphics.Rect;
23import android.util.AttributeSet;
24import android.view.Gravity;
25import android.view.KeyEvent;
Winson Chung499cb9f2010-07-16 11:18:17 -070026import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.view.View;
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -070028import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.ViewGroup;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.view.animation.GridLayoutAnimationController;
Winson Chung499cb9f2010-07-16 11:18:17 -070031import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
33
34/**
35 * A view that shows items in two-dimensional scrolling grid. The items in the
36 * grid come from the {@link ListAdapter} associated with this view.
Scott Main41ec6532010-08-19 16:57:07 -070037 *
38 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Grid
39 * View tutorial</a>.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 */
Winson Chung499cb9f2010-07-16 11:18:17 -070041@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042public class GridView extends AbsListView {
43 public static final int NO_STRETCH = 0;
44 public static final int STRETCH_SPACING = 1;
45 public static final int STRETCH_COLUMN_WIDTH = 2;
46 public static final int STRETCH_SPACING_UNIFORM = 3;
47
48 public static final int AUTO_FIT = -1;
49
50 private int mNumColumns = AUTO_FIT;
51
52 private int mHorizontalSpacing = 0;
53 private int mRequestedHorizontalSpacing;
54 private int mVerticalSpacing = 0;
55 private int mStretchMode = STRETCH_COLUMN_WIDTH;
56 private int mColumnWidth;
57 private int mRequestedColumnWidth;
58 private int mRequestedNumColumns;
59
60 private View mReferenceView = null;
61 private View mReferenceViewInSelectedRow = null;
62
63 private int mGravity = Gravity.LEFT;
64
65 private final Rect mTempRect = new Rect();
66
67 public GridView(Context context) {
68 super(context);
69 }
70
71 public GridView(Context context, AttributeSet attrs) {
72 this(context, attrs, com.android.internal.R.attr.gridViewStyle);
73 }
74
75 public GridView(Context context, AttributeSet attrs, int defStyle) {
76 super(context, attrs, defStyle);
77
78 TypedArray a = context.obtainStyledAttributes(attrs,
79 com.android.internal.R.styleable.GridView, defStyle, 0);
80
81 int hSpacing = a.getDimensionPixelOffset(
82 com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
83 setHorizontalSpacing(hSpacing);
84
85 int vSpacing = a.getDimensionPixelOffset(
86 com.android.internal.R.styleable.GridView_verticalSpacing, 0);
87 setVerticalSpacing(vSpacing);
88
89 int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
90 if (index >= 0) {
91 setStretchMode(index);
92 }
93
94 int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
95 if (columnWidth > 0) {
96 setColumnWidth(columnWidth);
97 }
98
99 int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
100 setNumColumns(numColumns);
101
102 index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
103 if (index >= 0) {
104 setGravity(index);
105 }
106
107 a.recycle();
108 }
109
110 @Override
111 public ListAdapter getAdapter() {
112 return mAdapter;
113 }
114
115 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700116 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
117 * through the specified intent.
118 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
119 */
120 @android.view.RemotableViewMethod
121 public void setRemoteViewsAdapter(Intent intent) {
122 super.setRemoteViewsAdapter(intent);
123 }
124
125 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 * Sets the data behind this GridView.
127 *
128 * @param adapter the adapter providing the grid's data
129 */
130 @Override
131 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700132 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 mAdapter.unregisterDataSetObserver(mDataSetObserver);
134 }
135
136 resetList();
137 mRecycler.clear();
138 mAdapter = adapter;
139
140 mOldSelectedPosition = INVALID_POSITION;
141 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700142
143 // AbsListView#setAdapter will update choice mode states.
144 super.setAdapter(adapter);
145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 if (mAdapter != null) {
147 mOldItemCount = mItemCount;
148 mItemCount = mAdapter.getCount();
149 mDataChanged = true;
150 checkFocus();
151
152 mDataSetObserver = new AdapterDataSetObserver();
153 mAdapter.registerDataSetObserver(mDataSetObserver);
154
155 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
156
157 int position;
158 if (mStackFromBottom) {
159 position = lookForSelectablePosition(mItemCount - 1, false);
160 } else {
161 position = lookForSelectablePosition(0, true);
162 }
163 setSelectedPositionInt(position);
164 setNextSelectedPositionInt(position);
165 checkSelectionChanged();
166 } else {
167 checkFocus();
168 // Nothing selected
169 checkSelectionChanged();
170 }
171
172 requestLayout();
173 }
174
175 @Override
176 int lookForSelectablePosition(int position, boolean lookDown) {
177 final ListAdapter adapter = mAdapter;
178 if (adapter == null || isInTouchMode()) {
179 return INVALID_POSITION;
180 }
181
182 if (position < 0 || position >= mItemCount) {
183 return INVALID_POSITION;
184 }
185 return position;
186 }
187
188 /**
189 * {@inheritDoc}
190 */
191 @Override
192 void fillGap(boolean down) {
193 final int numColumns = mNumColumns;
194 final int verticalSpacing = mVerticalSpacing;
195
196 final int count = getChildCount();
197
198 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800199 int paddingTop = 0;
200 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
201 paddingTop = getListPaddingTop();
202 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 final int startOffset = count > 0 ?
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800204 getChildAt(count - 1).getBottom() + verticalSpacing : paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 int position = mFirstPosition + count;
206 if (mStackFromBottom) {
207 position += numColumns - 1;
208 }
209 fillDown(position, startOffset);
210 correctTooHigh(numColumns, verticalSpacing, getChildCount());
211 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800212 int paddingBottom = 0;
213 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
214 paddingBottom = getListPaddingBottom();
215 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 final int startOffset = count > 0 ?
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800217 getChildAt(0).getTop() - verticalSpacing : getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 int position = mFirstPosition;
219 if (!mStackFromBottom) {
220 position -= numColumns;
221 } else {
222 position--;
223 }
224 fillUp(position, startOffset);
225 correctTooLow(numColumns, verticalSpacing, getChildCount());
226 }
227 }
228
229 /**
230 * Fills the list from pos down to the end of the list view.
231 *
232 * @param pos The first position to put in the list
233 *
234 * @param nextTop The location where the top of the item associated with pos
235 * should be drawn
236 *
237 * @return The view that is currently selected, if it happens to be in the
238 * range that we draw.
239 */
240 private View fillDown(int pos, int nextTop) {
241 View selectedView = null;
242
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800243 int end = (mBottom - mTop);
244 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
245 end -= mListPadding.bottom;
246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247
248 while (nextTop < end && pos < mItemCount) {
249 View temp = makeRow(pos, nextTop, true);
250 if (temp != null) {
251 selectedView = temp;
252 }
253
Romain Guy8bcdc072009-09-29 15:17:47 -0700254 // mReferenceView will change with each call to makeRow()
255 // do not cache in a local variable outside of this loop
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 nextTop = mReferenceView.getBottom() + mVerticalSpacing;
257
258 pos += mNumColumns;
259 }
260
261 return selectedView;
262 }
263
264 private View makeRow(int startPos, int y, boolean flow) {
265 final int columnWidth = mColumnWidth;
266 final int horizontalSpacing = mHorizontalSpacing;
267
268 int last;
Romain Guy8bcdc072009-09-29 15:17:47 -0700269 int nextLeft = mListPadding.left +
270 ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271
272 if (!mStackFromBottom) {
273 last = Math.min(startPos + mNumColumns, mItemCount);
274 } else {
275 last = startPos + 1;
276 startPos = Math.max(0, startPos - mNumColumns + 1);
277
278 if (last - startPos < mNumColumns) {
279 nextLeft += (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
280 }
281 }
282
283 View selectedView = null;
284
285 final boolean hasFocus = shouldShowSelector();
286 final boolean inClick = touchModeDrawsInPressedState();
287 final int selectedPosition = mSelectedPosition;
288
Romain Guy8bcdc072009-09-29 15:17:47 -0700289 View child = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 for (int pos = startPos; pos < last; pos++) {
291 // is this the selected item?
292 boolean selected = pos == selectedPosition;
293 // does the list view have focus or contain focus
294
295 final int where = flow ? -1 : pos - startPos;
Romain Guy8bcdc072009-09-29 15:17:47 -0700296 child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297
298 nextLeft += columnWidth;
299 if (pos < last - 1) {
300 nextLeft += horizontalSpacing;
301 }
302
303 if (selected && (hasFocus || inClick)) {
304 selectedView = child;
305 }
306 }
307
Romain Guy8bcdc072009-09-29 15:17:47 -0700308 mReferenceView = child;
309
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 if (selectedView != null) {
311 mReferenceViewInSelectedRow = mReferenceView;
312 }
313
314 return selectedView;
315 }
316
317 /**
318 * Fills the list from pos up to the top of the list view.
319 *
320 * @param pos The first position to put in the list
321 *
322 * @param nextBottom The location where the bottom of the item associated
323 * with pos should be drawn
324 *
325 * @return The view that is currently selected
326 */
327 private View fillUp(int pos, int nextBottom) {
328 View selectedView = null;
329
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800330 int end = 0;
331 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
332 end = mListPadding.top;
333 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334
335 while (nextBottom > end && pos >= 0) {
336
337 View temp = makeRow(pos, nextBottom, false);
338 if (temp != null) {
339 selectedView = temp;
340 }
341
342 nextBottom = mReferenceView.getTop() - mVerticalSpacing;
343
344 mFirstPosition = pos;
345
346 pos -= mNumColumns;
347 }
348
349 if (mStackFromBottom) {
350 mFirstPosition = Math.max(0, pos + 1);
351 }
352
353 return selectedView;
354 }
355
356 /**
357 * Fills the list from top to bottom, starting with mFirstPosition
358 *
359 * @param nextTop The location where the top of the first item should be
360 * drawn
361 *
362 * @return The view that is currently selected
363 */
364 private View fillFromTop(int nextTop) {
365 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
366 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
367 if (mFirstPosition < 0) {
368 mFirstPosition = 0;
369 }
370 mFirstPosition -= mFirstPosition % mNumColumns;
371 return fillDown(mFirstPosition, nextTop);
372 }
373
374 private View fillFromBottom(int lastPosition, int nextBottom) {
375 lastPosition = Math.max(lastPosition, mSelectedPosition);
376 lastPosition = Math.min(lastPosition, mItemCount - 1);
377
378 final int invertedPosition = mItemCount - 1 - lastPosition;
379 lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
380
381 return fillUp(lastPosition, nextBottom);
382 }
383
384 private View fillSelection(int childrenTop, int childrenBottom) {
385 final int selectedPosition = reconcileSelectedPosition();
386 final int numColumns = mNumColumns;
387 final int verticalSpacing = mVerticalSpacing;
388
389 int rowStart;
390 int rowEnd = -1;
391
392 if (!mStackFromBottom) {
393 rowStart = selectedPosition - (selectedPosition % numColumns);
394 } else {
395 final int invertedSelection = mItemCount - 1 - selectedPosition;
396
397 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
398 rowStart = Math.max(0, rowEnd - numColumns + 1);
399 }
400
401 final int fadingEdgeLength = getVerticalFadingEdgeLength();
402 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
403
404 final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
405 mFirstPosition = rowStart;
406
407 final View referenceView = mReferenceView;
408
409 if (!mStackFromBottom) {
410 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
411 pinToBottom(childrenBottom);
412 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
413 adjustViewsUpOrDown();
414 } else {
415 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
416 fadingEdgeLength, numColumns, rowStart);
417 final int offset = bottomSelectionPixel - referenceView.getBottom();
418 offsetChildrenTopAndBottom(offset);
419 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
420 pinToTop(childrenTop);
421 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
422 adjustViewsUpOrDown();
423 }
424
425 return sel;
426 }
427
428 private void pinToTop(int childrenTop) {
429 if (mFirstPosition == 0) {
430 final int top = getChildAt(0).getTop();
431 final int offset = childrenTop - top;
432 if (offset < 0) {
433 offsetChildrenTopAndBottom(offset);
434 }
435 }
436 }
437
438 private void pinToBottom(int childrenBottom) {
439 final int count = getChildCount();
440 if (mFirstPosition + count == mItemCount) {
441 final int bottom = getChildAt(count - 1).getBottom();
442 final int offset = childrenBottom - bottom;
443 if (offset > 0) {
444 offsetChildrenTopAndBottom(offset);
445 }
446 }
447 }
448
449 @Override
450 int findMotionRow(int y) {
451 final int childCount = getChildCount();
452 if (childCount > 0) {
453
454 final int numColumns = mNumColumns;
455 if (!mStackFromBottom) {
456 for (int i = 0; i < childCount; i += numColumns) {
457 if (y <= getChildAt(i).getBottom()) {
458 return mFirstPosition + i;
459 }
460 }
461 } else {
462 for (int i = childCount - 1; i >= 0; i -= numColumns) {
463 if (y >= getChildAt(i).getTop()) {
464 return mFirstPosition + i;
465 }
466 }
467 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800468 }
469 return INVALID_POSITION;
470 }
471
472 /**
473 * Layout during a scroll that results from tracking motion events. Places
474 * the mMotionPosition view at the offset specified by mMotionViewTop, and
475 * then build surrounding views from there.
476 *
477 * @param position the position at which to start filling
478 * @param top the top of the view at that position
479 * @return The selected view, or null if the selected view is outside the
480 * visible area.
481 */
482 private View fillSpecific(int position, int top) {
483 final int numColumns = mNumColumns;
484
485 int motionRowStart;
486 int motionRowEnd = -1;
487
488 if (!mStackFromBottom) {
489 motionRowStart = position - (position % numColumns);
490 } else {
491 final int invertedSelection = mItemCount - 1 - position;
492
493 motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
494 motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
495 }
496
497 final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
498
499 // Possibly changed again in fillUp if we add rows above this one.
500 mFirstPosition = motionRowStart;
501
502 final View referenceView = mReferenceView;
Romain Guy8bcdc072009-09-29 15:17:47 -0700503 // We didn't have anything to layout, bail out
504 if (referenceView == null) {
505 return null;
506 }
507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 final int verticalSpacing = mVerticalSpacing;
509
510 View above;
511 View below;
512
513 if (!mStackFromBottom) {
514 above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
515 adjustViewsUpOrDown();
516 below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
517 // Check if we have dragged the bottom of the grid too high
518 final int childCount = getChildCount();
519 if (childCount > 0) {
520 correctTooHigh(numColumns, verticalSpacing, childCount);
521 }
522 } else {
523 below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
524 adjustViewsUpOrDown();
525 above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
526 // Check if we have dragged the bottom of the grid too high
527 final int childCount = getChildCount();
528 if (childCount > 0) {
529 correctTooLow(numColumns, verticalSpacing, childCount);
530 }
531 }
532
533 if (temp != null) {
534 return temp;
535 } else if (above != null) {
536 return above;
537 } else {
538 return below;
539 }
540 }
541
542 private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
543 // First see if the last item is visible
544 final int lastPosition = mFirstPosition + childCount - 1;
545 if (lastPosition == mItemCount - 1 && childCount > 0) {
546 // Get the last child ...
547 final View lastChild = getChildAt(childCount - 1);
548
549 // ... and its bottom edge
550 final int lastBottom = lastChild.getBottom();
551 // This is bottom of our drawable area
552 final int end = (mBottom - mTop) - mListPadding.bottom;
553
554 // This is how far the bottom edge of the last view is from the bottom of the
555 // drawable area
556 int bottomOffset = end - lastBottom;
557
558 final View firstChild = getChildAt(0);
559 final int firstTop = firstChild.getTop();
560
561 // Make sure we are 1) Too high, and 2) Either there are more rows above the
562 // first row or the first row is scrolled off the top of the drawable area
563 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
564 if (mFirstPosition == 0) {
565 // Don't pull the top too far down
566 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
567 }
568
569 // Move everything down
570 offsetChildrenTopAndBottom(bottomOffset);
571 if (mFirstPosition > 0) {
572 // Fill the gap that was opened above mFirstPosition with more rows, if
573 // possible
574 fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
575 firstChild.getTop() - verticalSpacing);
576 // Close up the remaining gap
577 adjustViewsUpOrDown();
578 }
579 }
580 }
581 }
582
583 private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
584 if (mFirstPosition == 0 && childCount > 0) {
585 // Get the first child ...
586 final View firstChild = getChildAt(0);
587
588 // ... and its top edge
589 final int firstTop = firstChild.getTop();
590
591 // This is top of our drawable area
592 final int start = mListPadding.top;
593
594 // This is bottom of our drawable area
595 final int end = (mBottom - mTop) - mListPadding.bottom;
596
597 // This is how far the top edge of the first view is from the top of the
598 // drawable area
599 int topOffset = firstTop - start;
600 final View lastChild = getChildAt(childCount - 1);
601 final int lastBottom = lastChild.getBottom();
602 final int lastPosition = mFirstPosition + childCount - 1;
603
604 // Make sure we are 1) Too low, and 2) Either there are more rows below the
605 // last row or the last row is scrolled off the bottom of the drawable area
606 if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end)) {
607 if (lastPosition == mItemCount - 1 ) {
608 // Don't pull the bottom too far up
609 topOffset = Math.min(topOffset, lastBottom - end);
610 }
611
612 // Move everything up
613 offsetChildrenTopAndBottom(-topOffset);
614 if (lastPosition < mItemCount - 1) {
615 // Fill the gap that was opened below the last position with more rows, if
616 // possible
617 fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
618 lastChild.getBottom() + verticalSpacing);
619 // Close up the remaining gap
620 adjustViewsUpOrDown();
621 }
622 }
623 }
624 }
625
626 /**
627 * Fills the grid based on positioning the new selection at a specific
628 * location. The selection may be moved so that it does not intersect the
629 * faded edges. The grid is then filled upwards and downwards from there.
630 *
631 * @param selectedTop Where the selected item should be
632 * @param childrenTop Where to start drawing children
633 * @param childrenBottom Last pixel where children can be drawn
634 * @return The view that currently has selection
635 */
636 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
637 final int fadingEdgeLength = getVerticalFadingEdgeLength();
638 final int selectedPosition = mSelectedPosition;
639 final int numColumns = mNumColumns;
640 final int verticalSpacing = mVerticalSpacing;
641
642 int rowStart;
643 int rowEnd = -1;
644
645 if (!mStackFromBottom) {
646 rowStart = selectedPosition - (selectedPosition % numColumns);
647 } else {
648 int invertedSelection = mItemCount - 1 - selectedPosition;
649
650 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
651 rowStart = Math.max(0, rowEnd - numColumns + 1);
652 }
653
654 View sel;
655 View referenceView;
656
657 int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
658 int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
659 numColumns, rowStart);
660
661 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
662 // Possibly changed again in fillUp if we add rows above this one.
663 mFirstPosition = rowStart;
664
665 referenceView = mReferenceView;
666 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
667 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
668
669 if (!mStackFromBottom) {
670 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
671 adjustViewsUpOrDown();
672 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
673 } else {
674 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
675 adjustViewsUpOrDown();
676 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
677 }
678
679
680 return sel;
681 }
682
683 /**
684 * Calculate the bottom-most pixel we can draw the selection into
685 *
686 * @param childrenBottom Bottom pixel were children can be drawn
687 * @param fadingEdgeLength Length of the fading edge in pixels, if present
688 * @param numColumns Number of columns in the grid
689 * @param rowStart The start of the row that will contain the selection
690 * @return The bottom-most pixel we can draw the selection into
691 */
692 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
693 int numColumns, int rowStart) {
694 // Last pixel we can draw the selection into
695 int bottomSelectionPixel = childrenBottom;
696 if (rowStart + numColumns - 1 < mItemCount - 1) {
697 bottomSelectionPixel -= fadingEdgeLength;
698 }
699 return bottomSelectionPixel;
700 }
701
702 /**
703 * Calculate the top-most pixel we can draw the selection into
704 *
705 * @param childrenTop Top pixel were children can be drawn
706 * @param fadingEdgeLength Length of the fading edge in pixels, if present
707 * @param rowStart The start of the row that will contain the selection
708 * @return The top-most pixel we can draw the selection into
709 */
710 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
711 // first pixel we can draw the selection into
712 int topSelectionPixel = childrenTop;
713 if (rowStart > 0) {
714 topSelectionPixel += fadingEdgeLength;
715 }
716 return topSelectionPixel;
717 }
718
719 /**
720 * Move all views upwards so the selected row does not interesect the bottom
721 * fading edge (if necessary).
722 *
723 * @param childInSelectedRow A child in the row that contains the selection
724 * @param topSelectionPixel The topmost pixel we can draw the selection into
725 * @param bottomSelectionPixel The bottommost pixel we can draw the
726 * selection into
727 */
728 private void adjustForBottomFadingEdge(View childInSelectedRow,
729 int topSelectionPixel, int bottomSelectionPixel) {
730 // Some of the newly selected item extends below the bottom of the
731 // list
732 if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
733
734 // Find space available above the selection into which we can
735 // scroll upwards
736 int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
737
738 // Find space required to bring the bottom of the selected item
739 // fully into view
740 int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
741 int offset = Math.min(spaceAbove, spaceBelow);
742
743 // Now offset the selected item to get it into view
744 offsetChildrenTopAndBottom(-offset);
745 }
746 }
747
748 /**
749 * Move all views upwards so the selected row does not interesect the top
750 * fading edge (if necessary).
751 *
752 * @param childInSelectedRow A child in the row that contains the selection
753 * @param topSelectionPixel The topmost pixel we can draw the selection into
754 * @param bottomSelectionPixel The bottommost pixel we can draw the
755 * selection into
756 */
757 private void adjustForTopFadingEdge(View childInSelectedRow,
758 int topSelectionPixel, int bottomSelectionPixel) {
759 // Some of the newly selected item extends above the top of the list
760 if (childInSelectedRow.getTop() < topSelectionPixel) {
761 // Find space required to bring the top of the selected item
762 // fully into view
763 int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
764
765 // Find space available below the selection into which we can
766 // scroll downwards
767 int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
768 int offset = Math.min(spaceAbove, spaceBelow);
769
770 // Now offset the selected item to get it into view
771 offsetChildrenTopAndBottom(offset);
772 }
773 }
774
775 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700776 * Smoothly scroll to the specified adapter position. The view will
777 * scroll such that the indicated position is displayed.
778 * @param position Scroll to this adapter position.
779 */
780 @android.view.RemotableViewMethod
781 public void smoothScrollToPosition(int position) {
782 super.smoothScrollToPosition(position);
783 }
784
785 /**
786 * Smoothly scroll to the specified adapter position offset. The view will
787 * scroll such that the indicated position is displayed.
788 * @param offset The amount to offset from the adapter position to scroll to.
789 */
790 @android.view.RemotableViewMethod
791 public void smoothScrollByOffset(int offset) {
792 super.smoothScrollByOffset(offset);
793 }
794
795 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 * Fills the grid based on positioning the new selection relative to the old
797 * selection. The new selection will be placed at, above, or below the
798 * location of the new selection depending on how the selection is moving.
799 * The selection will then be pinned to the visible part of the screen,
800 * excluding the edges that are faded. The grid is then filled upwards and
801 * downwards from there.
802 *
803 * @param delta Which way we are moving
804 * @param childrenTop Where to start drawing children
805 * @param childrenBottom Last pixel where children can be drawn
806 * @return The view that currently has selection
807 */
808 private View moveSelection(int delta, int childrenTop, int childrenBottom) {
809 final int fadingEdgeLength = getVerticalFadingEdgeLength();
810 final int selectedPosition = mSelectedPosition;
811 final int numColumns = mNumColumns;
812 final int verticalSpacing = mVerticalSpacing;
813
814 int oldRowStart;
815 int rowStart;
816 int rowEnd = -1;
817
818 if (!mStackFromBottom) {
819 oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
820
821 rowStart = selectedPosition - (selectedPosition % numColumns);
822 } else {
823 int invertedSelection = mItemCount - 1 - selectedPosition;
824
825 rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
826 rowStart = Math.max(0, rowEnd - numColumns + 1);
827
828 invertedSelection = mItemCount - 1 - (selectedPosition - delta);
829 oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
830 oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
831 }
832
833 final int rowDelta = rowStart - oldRowStart;
834
835 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
836 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
837 numColumns, rowStart);
838
839 // Possibly changed again in fillUp if we add rows above this one.
840 mFirstPosition = rowStart;
841
842 View sel;
843 View referenceView;
844
845 if (rowDelta > 0) {
846 /*
847 * Case 1: Scrolling down.
848 */
849
850 final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
851 mReferenceViewInSelectedRow.getBottom();
852
853 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
854 referenceView = mReferenceView;
855
856 adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
857 } else if (rowDelta < 0) {
858 /*
859 * Case 2: Scrolling up.
860 */
861 final int oldTop = mReferenceViewInSelectedRow == null ?
862 0 : mReferenceViewInSelectedRow .getTop();
863
864 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
865 referenceView = mReferenceView;
866
867 adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
868 } else {
869 /*
870 * Keep selection where it was
871 */
872 final int oldTop = mReferenceViewInSelectedRow == null ?
873 0 : mReferenceViewInSelectedRow .getTop();
874
875 sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
876 referenceView = mReferenceView;
877 }
878
879 if (!mStackFromBottom) {
880 fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
881 adjustViewsUpOrDown();
882 fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
883 } else {
884 fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
885 adjustViewsUpOrDown();
886 fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
887 }
888
889 return sel;
890 }
891
892 private void determineColumns(int availableSpace) {
893 final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
894 final int stretchMode = mStretchMode;
895 final int requestedColumnWidth = mRequestedColumnWidth;
896
897 if (mRequestedNumColumns == AUTO_FIT) {
898 if (requestedColumnWidth > 0) {
899 // Client told us to pick the number of columns
900 mNumColumns = (availableSpace + requestedHorizontalSpacing) /
901 (requestedColumnWidth + requestedHorizontalSpacing);
902 } else {
903 // Just make up a number if we don't have enough info
904 mNumColumns = 2;
905 }
906 } else {
907 // We picked the columns
908 mNumColumns = mRequestedNumColumns;
909 }
910
911 if (mNumColumns <= 0) {
912 mNumColumns = 1;
913 }
914
915 switch (stretchMode) {
916 case NO_STRETCH:
917 // Nobody stretches
918 mColumnWidth = requestedColumnWidth;
919 mHorizontalSpacing = requestedHorizontalSpacing;
920 break;
921
922 default:
923 int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
924 ((mNumColumns - 1) * requestedHorizontalSpacing);
925 switch (stretchMode) {
926 case STRETCH_COLUMN_WIDTH:
927 // Stretch the columns
928 mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
929 mHorizontalSpacing = requestedHorizontalSpacing;
930 break;
931
932 case STRETCH_SPACING:
933 // Stretch the spacing between columns
934 mColumnWidth = requestedColumnWidth;
935 if (mNumColumns > 1) {
936 mHorizontalSpacing = requestedHorizontalSpacing +
937 spaceLeftOver / (mNumColumns - 1);
938 } else {
939 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
940 }
941 break;
942
943 case STRETCH_SPACING_UNIFORM:
944 // Stretch the spacing between columns
945 mColumnWidth = requestedColumnWidth;
946 if (mNumColumns > 1) {
947 mHorizontalSpacing = requestedHorizontalSpacing +
948 spaceLeftOver / (mNumColumns + 1);
949 } else {
950 mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
951 }
952 break;
953 }
954
955 break;
956 }
957 }
958
959 @Override
960 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
961 // Sets up mListPadding
962 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
963
964 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
965 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
966 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
967 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
968
969 if (widthMode == MeasureSpec.UNSPECIFIED) {
970 if (mColumnWidth > 0) {
971 widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
972 } else {
973 widthSize = mListPadding.left + mListPadding.right;
974 }
975 widthSize += getVerticalScrollbarWidth();
976 }
977
978 int childWidth = widthSize - mListPadding.left - mListPadding.right;
979 determineColumns(childWidth);
980
981 int childHeight = 0;
982
983 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
984 final int count = mItemCount;
985 if (count > 0) {
Romain Guy21875052010-01-06 18:48:08 -0800986 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987
988 AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
989 if (p == null) {
Romain Guy980a9382010-01-08 15:06:28 -0800990 p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
The Android Open Source Project4df24232009-03-05 14:34:35 -0800992 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993 }
994 p.viewType = mAdapter.getItemViewType(0);
Romain Guy0bf88592010-03-02 13:38:44 -0800995 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996
997 int childHeightSpec = getChildMeasureSpec(
998 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
999 int childWidthSpec = getChildMeasureSpec(
1000 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1001 child.measure(childWidthSpec, childHeightSpec);
1002
1003 childHeight = child.getMeasuredHeight();
1004
1005 if (mRecycler.shouldRecycleViewType(p.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001006 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 }
1008 }
1009
1010 if (heightMode == MeasureSpec.UNSPECIFIED) {
1011 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1012 getVerticalFadingEdgeLength() * 2;
1013 }
1014
1015 if (heightMode == MeasureSpec.AT_MOST) {
1016 int ourSize = mListPadding.top + mListPadding.bottom;
1017
1018 final int numColumns = mNumColumns;
1019 for (int i = 0; i < count; i += numColumns) {
1020 ourSize += childHeight;
1021 if (i + numColumns < count) {
1022 ourSize += mVerticalSpacing;
1023 }
1024 if (ourSize >= heightSize) {
1025 ourSize = heightSize;
1026 break;
1027 }
1028 }
1029 heightSize = ourSize;
1030 }
1031
1032 setMeasuredDimension(widthSize, heightSize);
1033 mWidthMeasureSpec = widthMeasureSpec;
1034 }
1035
1036 @Override
1037 protected void attachLayoutAnimationParameters(View child,
1038 ViewGroup.LayoutParams params, int index, int count) {
1039
1040 GridLayoutAnimationController.AnimationParameters animationParams =
1041 (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
1042
1043 if (animationParams == null) {
1044 animationParams = new GridLayoutAnimationController.AnimationParameters();
1045 params.layoutAnimationParameters = animationParams;
1046 }
1047
1048 animationParams.count = count;
1049 animationParams.index = index;
1050 animationParams.columnsCount = mNumColumns;
1051 animationParams.rowsCount = count / mNumColumns;
1052
1053 if (!mStackFromBottom) {
1054 animationParams.column = index % mNumColumns;
1055 animationParams.row = index / mNumColumns;
1056 } else {
1057 final int invertedIndex = count - 1 - index;
1058
1059 animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
1060 animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
1061 }
1062 }
1063
1064 @Override
1065 protected void layoutChildren() {
1066 final boolean blockLayoutRequests = mBlockLayoutRequests;
1067 if (!blockLayoutRequests) {
1068 mBlockLayoutRequests = true;
1069 }
1070
1071 try {
1072 super.layoutChildren();
1073
1074 invalidate();
1075
1076 if (mAdapter == null) {
1077 resetList();
1078 invokeOnItemScrollListener();
1079 return;
1080 }
1081
1082 final int childrenTop = mListPadding.top;
1083 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1084
1085 int childCount = getChildCount();
1086 int index;
1087 int delta = 0;
1088
1089 View sel;
1090 View oldSel = null;
1091 View oldFirst = null;
1092 View newSel = null;
1093
1094 // Remember stuff we will need down below
1095 switch (mLayoutMode) {
1096 case LAYOUT_SET_SELECTION:
1097 index = mNextSelectedPosition - mFirstPosition;
1098 if (index >= 0 && index < childCount) {
1099 newSel = getChildAt(index);
1100 }
1101 break;
1102 case LAYOUT_FORCE_TOP:
1103 case LAYOUT_FORCE_BOTTOM:
1104 case LAYOUT_SPECIFIC:
1105 case LAYOUT_SYNC:
1106 break;
1107 case LAYOUT_MOVE_SELECTION:
1108 if (mNextSelectedPosition >= 0) {
1109 delta = mNextSelectedPosition - mSelectedPosition;
1110 }
1111 break;
1112 default:
1113 // Remember the previously selected view
1114 index = mSelectedPosition - mFirstPosition;
1115 if (index >= 0 && index < childCount) {
1116 oldSel = getChildAt(index);
1117 }
1118
1119 // Remember the previous first child
1120 oldFirst = getChildAt(0);
1121 }
1122
1123 boolean dataChanged = mDataChanged;
1124 if (dataChanged) {
1125 handleDataChanged();
1126 }
1127
1128 // Handle the empty set by removing all views that are visible
1129 // and calling it a day
1130 if (mItemCount == 0) {
1131 resetList();
1132 invokeOnItemScrollListener();
1133 return;
1134 }
1135
1136 setSelectedPositionInt(mNextSelectedPosition);
1137
1138 // Pull all children into the RecycleBin.
1139 // These views will be reused if possible
1140 final int firstPosition = mFirstPosition;
1141 final RecycleBin recycleBin = mRecycler;
1142
1143 if (dataChanged) {
1144 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001145 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 }
1147 } else {
1148 recycleBin.fillActiveViews(childCount, firstPosition);
1149 }
1150
1151 // Clear out old views
1152 //removeAllViewsInLayout();
1153 detachAllViewsFromParent();
1154
1155 switch (mLayoutMode) {
1156 case LAYOUT_SET_SELECTION:
1157 if (newSel != null) {
1158 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1159 } else {
1160 sel = fillSelection(childrenTop, childrenBottom);
1161 }
1162 break;
1163 case LAYOUT_FORCE_TOP:
1164 mFirstPosition = 0;
1165 sel = fillFromTop(childrenTop);
1166 adjustViewsUpOrDown();
1167 break;
1168 case LAYOUT_FORCE_BOTTOM:
1169 sel = fillUp(mItemCount - 1, childrenBottom);
1170 adjustViewsUpOrDown();
1171 break;
1172 case LAYOUT_SPECIFIC:
1173 sel = fillSpecific(mSelectedPosition, mSpecificTop);
1174 break;
1175 case LAYOUT_SYNC:
1176 sel = fillSpecific(mSyncPosition, mSpecificTop);
1177 break;
1178 case LAYOUT_MOVE_SELECTION:
1179 // Move the selection relative to its old position
1180 sel = moveSelection(delta, childrenTop, childrenBottom);
1181 break;
1182 default:
1183 if (childCount == 0) {
1184 if (!mStackFromBottom) {
Romain Guy91c86132010-03-26 17:29:45 -07001185 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1186 INVALID_POSITION : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187 sel = fillFromTop(childrenTop);
1188 } else {
1189 final int last = mItemCount - 1;
Romain Guy91c86132010-03-26 17:29:45 -07001190 setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
1191 INVALID_POSITION : last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 sel = fillFromBottom(last, childrenBottom);
1193 }
1194 } else {
1195 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1196 sel = fillSpecific(mSelectedPosition, oldSel == null ?
1197 childrenTop : oldSel.getTop());
1198 } else if (mFirstPosition < mItemCount) {
1199 sel = fillSpecific(mFirstPosition, oldFirst == null ?
1200 childrenTop : oldFirst.getTop());
1201 } else {
1202 sel = fillSpecific(0, childrenTop);
1203 }
1204 }
1205 break;
1206 }
1207
1208 // Flush any cached views that did not get reused above
1209 recycleBin.scrapActiveViews();
1210
1211 if (sel != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001212 positionSelector(INVALID_POSITION, sel);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 mSelectedTop = sel.getTop();
Romain Guy3616a412009-09-15 13:50:37 -07001214 } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1215 View child = getChildAt(mMotionPosition - mFirstPosition);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001216 if (child != null) positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 } else {
Romain Guy3616a412009-09-15 13:50:37 -07001218 mSelectedTop = 0;
1219 mSelectorRect.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 }
1221
1222 mLayoutMode = LAYOUT_NORMAL;
1223 mDataChanged = false;
1224 mNeedSync = false;
1225 setNextSelectedPositionInt(mSelectedPosition);
1226
1227 updateScrollIndicators();
1228
1229 if (mItemCount > 0) {
1230 checkSelectionChanged();
1231 }
1232
1233 invokeOnItemScrollListener();
1234 } finally {
1235 if (!blockLayoutRequests) {
1236 mBlockLayoutRequests = false;
1237 }
1238 }
1239 }
1240
1241
1242 /**
1243 * Obtain the view and add it to our list of children. The view can be made
1244 * fresh, converted from an unused view, or used as is if it was in the
1245 * recycle bin.
1246 *
1247 * @param position Logical position in the list
1248 * @param y Top or bottom edge of the view to add
1249 * @param flow if true, align top edge to y. If false, align bottom edge to
1250 * y.
1251 * @param childrenLeft Left edge where children should be positioned
1252 * @param selected Is this position selected?
1253 * @param where to add new item in the list
1254 * @return View that was added
1255 */
1256 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1257 boolean selected, int where) {
1258 View child;
1259
1260 if (!mDataChanged) {
Romain Guy21875052010-01-06 18:48:08 -08001261 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 child = mRecycler.getActiveView(position);
1263 if (child != null) {
1264 // Found it -- we're using an existing child
1265 // This just needs to be positioned
1266 setupChild(child, position, y, flow, childrenLeft, selected, true, where);
1267 return child;
1268 }
1269 }
1270
1271 // Make a new view for this position, or convert an unused view if
1272 // possible
Romain Guy21875052010-01-06 18:48:08 -08001273 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274
1275 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001276 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001277
1278 return child;
1279 }
1280
1281 /**
1282 * Add a view as a child and make sure it is measured (if necessary) and
1283 * positioned properly.
1284 *
1285 * @param child The view to add
1286 * @param position The position of the view
1287 * @param y The y position relative to which this view will be positioned
1288 * @param flow if true, align top edge to y. If false, align bottom edge
1289 * to y.
1290 * @param childrenLeft Left edge where children should be positioned
1291 * @param selected Is this position selected?
1292 * @param recycled Has this view been pulled from the recycle bin? If so it
1293 * does not need to be remeasured.
1294 * @param where Where to add the item in the list
1295 *
1296 */
1297 private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
1298 boolean selected, boolean recycled, int where) {
1299 boolean isSelected = selected && shouldShowSelector();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001301 final int mode = mTouchMode;
1302 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1303 mMotionPosition == position;
1304 final boolean updateChildPressed = isPressed != child.isPressed();
1305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1307
1308 // Respect layout params that are already in the view. Otherwise make
1309 // some up...
1310 AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
1311 if (p == null) {
Romain Guy980a9382010-01-08 15:06:28 -08001312 p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
1314 }
1315 p.viewType = mAdapter.getItemViewType(position);
1316
Romain Guy0bf88592010-03-02 13:38:44 -08001317 if (recycled && !p.forceAdd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 attachViewToParent(child, where, p);
1319 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001320 p.forceAdd = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 addViewInLayout(child, where, p, true);
1322 }
1323
1324 if (updateChildSelected) {
1325 child.setSelected(isSelected);
1326 if (isSelected) {
1327 requestFocus();
1328 }
1329 }
1330
Romain Guy3616a412009-09-15 13:50:37 -07001331 if (updateChildPressed) {
1332 child.setPressed(isPressed);
1333 }
1334
Adam Powellf343e1b2010-08-13 18:27:04 -07001335 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1336 if (child instanceof Checkable) {
1337 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001338 } else if (getContext().getApplicationInfo().targetSdkVersion
1339 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1340 child.setActivated(mCheckStates.get(position));
Adam Powellf343e1b2010-08-13 18:27:04 -07001341 }
1342 }
1343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 if (needToMeasure) {
1345 int childHeightSpec = ViewGroup.getChildMeasureSpec(
1346 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
1347
1348 int childWidthSpec = ViewGroup.getChildMeasureSpec(
1349 MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
1350 child.measure(childWidthSpec, childHeightSpec);
1351 } else {
1352 cleanupLayoutState(child);
1353 }
1354
1355 final int w = child.getMeasuredWidth();
1356 final int h = child.getMeasuredHeight();
1357
1358 int childLeft;
1359 final int childTop = flow ? y : y - h;
1360
1361 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
1362 case Gravity.LEFT:
1363 childLeft = childrenLeft;
1364 break;
1365 case Gravity.CENTER_HORIZONTAL:
1366 childLeft = childrenLeft + ((mColumnWidth - w) / 2);
1367 break;
1368 case Gravity.RIGHT:
1369 childLeft = childrenLeft + mColumnWidth - w;
1370 break;
1371 default:
1372 childLeft = childrenLeft;
1373 break;
1374 }
1375
1376 if (needToMeasure) {
1377 final int childRight = childLeft + w;
1378 final int childBottom = childTop + h;
1379 child.layout(childLeft, childTop, childRight, childBottom);
1380 } else {
1381 child.offsetLeftAndRight(childLeft - child.getLeft());
1382 child.offsetTopAndBottom(childTop - child.getTop());
1383 }
1384
1385 if (mCachingStarted) {
1386 child.setDrawingCacheEnabled(true);
1387 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001388
1389 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1390 != position) {
1391 child.jumpDrawablesToCurrentState();
1392 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393 }
1394
1395 /**
1396 * Sets the currently selected item
1397 *
1398 * @param position Index (starting at 0) of the data item to be selected.
1399 *
1400 * If in touch mode, the item will not be selected but it will still be positioned
1401 * appropriately.
1402 */
1403 @Override
1404 public void setSelection(int position) {
1405 if (!isInTouchMode()) {
1406 setNextSelectedPositionInt(position);
1407 } else {
1408 mResurrectToPosition = position;
1409 }
1410 mLayoutMode = LAYOUT_SET_SELECTION;
1411 requestLayout();
1412 }
1413
1414 /**
1415 * Makes the item at the supplied position selected.
1416 *
1417 * @param position the position of the new selection
1418 */
1419 @Override
1420 void setSelectionInt(int position) {
Mike Cleronf116bf82009-09-27 19:14:12 -07001421 int previousSelectedPosition = mNextSelectedPosition;
1422
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001423 setNextSelectedPositionInt(position);
1424 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07001425
1426 final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
1427 mNextSelectedPosition;
1428 final int previous = mStackFromBottom ? mItemCount - 1
1429 - previousSelectedPosition : previousSelectedPosition;
1430
1431 final int nextRow = next / mNumColumns;
1432 final int previousRow = previous / mNumColumns;
1433
1434 if (nextRow != previousRow) {
1435 awakenScrollBars();
1436 }
1437
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 }
1439
1440 @Override
1441 public boolean onKeyDown(int keyCode, KeyEvent event) {
1442 return commonKey(keyCode, 1, event);
1443 }
1444
1445 @Override
1446 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
1447 return commonKey(keyCode, repeatCount, event);
1448 }
1449
1450 @Override
1451 public boolean onKeyUp(int keyCode, KeyEvent event) {
1452 return commonKey(keyCode, 1, event);
1453 }
1454
1455 private boolean commonKey(int keyCode, int count, KeyEvent event) {
1456 if (mAdapter == null) {
1457 return false;
1458 }
1459
1460 if (mDataChanged) {
1461 layoutChildren();
1462 }
1463
1464 boolean handled = false;
1465 int action = event.getAction();
1466
1467 if (action != KeyEvent.ACTION_UP) {
1468 if (mSelectedPosition < 0) {
1469 switch (keyCode) {
1470 case KeyEvent.KEYCODE_DPAD_UP:
1471 case KeyEvent.KEYCODE_DPAD_DOWN:
1472 case KeyEvent.KEYCODE_DPAD_LEFT:
1473 case KeyEvent.KEYCODE_DPAD_RIGHT:
1474 case KeyEvent.KEYCODE_DPAD_CENTER:
1475 case KeyEvent.KEYCODE_SPACE:
1476 case KeyEvent.KEYCODE_ENTER:
1477 resurrectSelection();
1478 return true;
1479 }
1480 }
1481
1482 switch (keyCode) {
1483 case KeyEvent.KEYCODE_DPAD_LEFT:
1484 handled = arrowScroll(FOCUS_LEFT);
1485 break;
1486
1487 case KeyEvent.KEYCODE_DPAD_RIGHT:
1488 handled = arrowScroll(FOCUS_RIGHT);
1489 break;
1490
1491 case KeyEvent.KEYCODE_DPAD_UP:
1492 if (!event.isAltPressed()) {
1493 handled = arrowScroll(FOCUS_UP);
1494
1495 } else {
1496 handled = fullScroll(FOCUS_UP);
1497 }
1498 break;
1499
1500 case KeyEvent.KEYCODE_DPAD_DOWN:
1501 if (!event.isAltPressed()) {
1502 handled = arrowScroll(FOCUS_DOWN);
1503 } else {
1504 handled = fullScroll(FOCUS_DOWN);
1505 }
1506 break;
1507
1508 case KeyEvent.KEYCODE_DPAD_CENTER:
1509 case KeyEvent.KEYCODE_ENTER: {
1510 if (getChildCount() > 0 && event.getRepeatCount() == 0) {
1511 keyPressed();
1512 }
1513
1514 return true;
1515 }
1516
1517 case KeyEvent.KEYCODE_SPACE:
1518 if (mPopup == null || !mPopup.isShowing()) {
1519 if (!event.isShiftPressed()) {
1520 handled = pageScroll(FOCUS_DOWN);
1521 } else {
1522 handled = pageScroll(FOCUS_UP);
1523 }
1524 }
1525 break;
1526 }
1527 }
1528
1529 if (!handled) {
1530 handled = sendToTextFilter(keyCode, count, event);
1531 }
1532
1533 if (handled) {
1534 return true;
1535 } else {
1536 switch (action) {
1537 case KeyEvent.ACTION_DOWN:
1538 return super.onKeyDown(keyCode, event);
1539 case KeyEvent.ACTION_UP:
1540 return super.onKeyUp(keyCode, event);
1541 case KeyEvent.ACTION_MULTIPLE:
1542 return super.onKeyMultiple(keyCode, count, event);
1543 default:
1544 return false;
1545 }
1546 }
1547 }
1548
1549 /**
1550 * Scrolls up or down by the number of items currently present on screen.
1551 *
1552 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1553 * @return whether selection was moved
1554 */
1555 boolean pageScroll(int direction) {
1556 int nextPage = -1;
1557
1558 if (direction == FOCUS_UP) {
Romain Guy64d50a62010-08-20 10:50:49 -07001559 nextPage = Math.max(0, mSelectedPosition - getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001560 } else if (direction == FOCUS_DOWN) {
Romain Guy64d50a62010-08-20 10:50:49 -07001561 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001562 }
1563
1564 if (nextPage >= 0) {
1565 setSelectionInt(nextPage);
1566 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07001567 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 return true;
1569 }
1570
1571 return false;
1572 }
1573
1574 /**
1575 * Go to the last or first item if possible.
1576 *
1577 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
1578 *
1579 * @return Whether selection was moved.
1580 */
1581 boolean fullScroll(int direction) {
1582 boolean moved = false;
1583 if (direction == FOCUS_UP) {
1584 mLayoutMode = LAYOUT_SET_SELECTION;
1585 setSelectionInt(0);
1586 invokeOnItemScrollListener();
1587 moved = true;
1588 } else if (direction == FOCUS_DOWN) {
1589 mLayoutMode = LAYOUT_SET_SELECTION;
1590 setSelectionInt(mItemCount - 1);
1591 invokeOnItemScrollListener();
1592 moved = true;
1593 }
Mike Cleronf116bf82009-09-27 19:14:12 -07001594
1595 if (moved) {
1596 awakenScrollBars();
1597 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001598
1599 return moved;
1600 }
1601
1602 /**
1603 * Scrolls to the next or previous item, horizontally or vertically.
1604 *
1605 * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1606 * {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
1607 *
1608 * @return whether selection was moved
1609 */
1610 boolean arrowScroll(int direction) {
1611 final int selectedPosition = mSelectedPosition;
1612 final int numColumns = mNumColumns;
1613
1614 int startOfRowPos;
1615 int endOfRowPos;
1616
1617 boolean moved = false;
1618
1619 if (!mStackFromBottom) {
1620 startOfRowPos = (selectedPosition / numColumns) * numColumns;
1621 endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
1622 } else {
1623 final int invertedSelection = mItemCount - 1 - selectedPosition;
1624 endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
1625 startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
1626 }
1627
1628 switch (direction) {
1629 case FOCUS_UP:
1630 if (startOfRowPos > 0) {
1631 mLayoutMode = LAYOUT_MOVE_SELECTION;
1632 setSelectionInt(Math.max(0, selectedPosition - numColumns));
1633 moved = true;
1634 }
1635 break;
1636 case FOCUS_DOWN:
1637 if (endOfRowPos < mItemCount - 1) {
1638 mLayoutMode = LAYOUT_MOVE_SELECTION;
1639 setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
1640 moved = true;
1641 }
1642 break;
1643 case FOCUS_LEFT:
1644 if (selectedPosition > startOfRowPos) {
1645 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001646 setSelectionInt(Math.max(0, selectedPosition - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001647 moved = true;
1648 }
1649 break;
1650 case FOCUS_RIGHT:
1651 if (selectedPosition < endOfRowPos) {
1652 mLayoutMode = LAYOUT_MOVE_SELECTION;
Romain Guy51d154b2009-05-04 16:24:39 -07001653 setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001654 moved = true;
1655 }
1656 break;
1657 }
1658
1659 if (moved) {
1660 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1661 invokeOnItemScrollListener();
1662 }
1663
Mike Cleronf116bf82009-09-27 19:14:12 -07001664 if (moved) {
1665 awakenScrollBars();
1666 }
1667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001668 return moved;
1669 }
1670
1671 @Override
1672 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1673 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1674
1675 int closestChildIndex = -1;
1676 if (gainFocus && previouslyFocusedRect != null) {
1677 previouslyFocusedRect.offset(mScrollX, mScrollY);
1678
1679 // figure out which item should be selected based on previously
1680 // focused rect
1681 Rect otherRect = mTempRect;
1682 int minDistance = Integer.MAX_VALUE;
1683 final int childCount = getChildCount();
1684 for (int i = 0; i < childCount; i++) {
1685 // only consider view's on appropriate edge of grid
1686 if (!isCandidateSelection(i, direction)) {
1687 continue;
1688 }
1689
1690 final View other = getChildAt(i);
1691 other.getDrawingRect(otherRect);
1692 offsetDescendantRectToMyCoords(other, otherRect);
1693 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
1694
1695 if (distance < minDistance) {
1696 minDistance = distance;
1697 closestChildIndex = i;
1698 }
1699 }
1700 }
1701
1702 if (closestChildIndex >= 0) {
1703 setSelection(closestChildIndex + mFirstPosition);
1704 } else {
1705 requestLayout();
1706 }
1707 }
1708
1709 /**
1710 * Is childIndex a candidate for next focus given the direction the focus
1711 * change is coming from?
1712 * @param childIndex The index to check.
1713 * @param direction The direction, one of
1714 * {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}
1715 * @return Whether childIndex is a candidate.
1716 */
1717 private boolean isCandidateSelection(int childIndex, int direction) {
1718 final int count = getChildCount();
1719 final int invertedIndex = count - 1 - childIndex;
1720
1721 int rowStart;
1722 int rowEnd;
1723
1724 if (!mStackFromBottom) {
1725 rowStart = childIndex - (childIndex % mNumColumns);
1726 rowEnd = Math.max(rowStart + mNumColumns - 1, count);
1727 } else {
1728 rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
1729 rowStart = Math.max(0, rowEnd - mNumColumns + 1);
1730 }
1731
1732 switch (direction) {
1733 case View.FOCUS_RIGHT:
1734 // coming from left, selection is only valid if it is on left
1735 // edge
1736 return childIndex == rowStart;
1737 case View.FOCUS_DOWN:
1738 // coming from top; only valid if in top row
1739 return rowStart == 0;
1740 case View.FOCUS_LEFT:
1741 // coming from right, must be on right edge
1742 return childIndex == rowEnd;
1743 case View.FOCUS_UP:
1744 // coming from bottom, need to be in last row
1745 return rowEnd == count - 1;
1746 default:
1747 throw new IllegalArgumentException("direction must be one of "
1748 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
1749 }
1750 }
1751
1752 /**
1753 * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
1754 *
1755 * @param gravity the gravity to apply to this grid's children
1756 *
1757 * @attr ref android.R.styleable#GridView_gravity
1758 */
1759 public void setGravity(int gravity) {
1760 if (mGravity != gravity) {
1761 mGravity = gravity;
1762 requestLayoutIfNecessary();
1763 }
1764 }
1765
1766 /**
1767 * Set the amount of horizontal (x) spacing to place between each item
1768 * in the grid.
1769 *
1770 * @param horizontalSpacing The amount of horizontal space between items,
1771 * in pixels.
1772 *
1773 * @attr ref android.R.styleable#GridView_horizontalSpacing
1774 */
1775 public void setHorizontalSpacing(int horizontalSpacing) {
1776 if (horizontalSpacing != mRequestedHorizontalSpacing) {
1777 mRequestedHorizontalSpacing = horizontalSpacing;
1778 requestLayoutIfNecessary();
1779 }
1780 }
1781
1782
1783 /**
1784 * Set the amount of vertical (y) spacing to place between each item
1785 * in the grid.
1786 *
1787 * @param verticalSpacing The amount of vertical space between items,
1788 * in pixels.
1789 *
1790 * @attr ref android.R.styleable#GridView_verticalSpacing
1791 */
1792 public void setVerticalSpacing(int verticalSpacing) {
1793 if (verticalSpacing != mVerticalSpacing) {
1794 mVerticalSpacing = verticalSpacing;
1795 requestLayoutIfNecessary();
1796 }
1797 }
1798
1799 /**
1800 * Control how items are stretched to fill their space.
1801 *
1802 * @param stretchMode Either {@link #NO_STRETCH},
1803 * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
1804 *
1805 * @attr ref android.R.styleable#GridView_stretchMode
1806 */
1807 public void setStretchMode(int stretchMode) {
1808 if (stretchMode != mStretchMode) {
1809 mStretchMode = stretchMode;
1810 requestLayoutIfNecessary();
1811 }
1812 }
1813
1814 public int getStretchMode() {
1815 return mStretchMode;
1816 }
1817
1818 /**
1819 * Set the width of columns in the grid.
1820 *
1821 * @param columnWidth The column width, in pixels.
1822 *
1823 * @attr ref android.R.styleable#GridView_columnWidth
1824 */
1825 public void setColumnWidth(int columnWidth) {
1826 if (columnWidth != mRequestedColumnWidth) {
1827 mRequestedColumnWidth = columnWidth;
1828 requestLayoutIfNecessary();
1829 }
1830 }
1831
1832 /**
1833 * Set the number of columns in the grid
1834 *
1835 * @param numColumns The desired number of columns.
1836 *
1837 * @attr ref android.R.styleable#GridView_numColumns
1838 */
1839 public void setNumColumns(int numColumns) {
1840 if (numColumns != mRequestedNumColumns) {
1841 mRequestedNumColumns = numColumns;
1842 requestLayoutIfNecessary();
1843 }
1844 }
Andrew Sapperstein8d9db8e2010-05-13 17:01:03 -07001845
1846 /**
1847 * Get the number of columns in the grid.
1848 * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
1849 *
1850 * @attr ref android.R.styleable#GridView_numColumns
1851 *
1852 * @see #setNumColumns(int)
1853 */
1854 @ViewDebug.ExportedProperty
1855 public int getNumColumns() {
1856 return mNumColumns;
1857 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001858
1859 /**
1860 * Make sure views are touching the top or bottom edge, as appropriate for
1861 * our gravity
1862 */
1863 private void adjustViewsUpOrDown() {
1864 final int childCount = getChildCount();
1865
1866 if (childCount > 0) {
1867 int delta;
1868 View child;
1869
1870 if (!mStackFromBottom) {
1871 // Uh-oh -- we came up short. Slide all views up to make them
1872 // align with the top
1873 child = getChildAt(0);
1874 delta = child.getTop() - mListPadding.top;
1875 if (mFirstPosition != 0) {
1876 // It's OK to have some space above the first item if it is
1877 // part of the vertical spacing
1878 delta -= mVerticalSpacing;
1879 }
1880 if (delta < 0) {
1881 // We only are looking to see if we are too low, not too high
1882 delta = 0;
1883 }
1884 } else {
1885 // we are too high, slide all views down to align with bottom
1886 child = getChildAt(childCount - 1);
1887 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
1888
1889 if (mFirstPosition + childCount < mItemCount) {
1890 // It's OK to have some space below the last item if it is
1891 // part of the vertical spacing
1892 delta += mVerticalSpacing;
1893 }
1894
1895 if (delta > 0) {
1896 // We only are looking to see if we are too high, not too low
1897 delta = 0;
1898 }
1899 }
1900
1901 if (delta != 0) {
1902 offsetChildrenTopAndBottom(-delta);
1903 }
1904 }
1905 }
1906
1907 @Override
1908 protected int computeVerticalScrollExtent() {
1909 final int count = getChildCount();
1910 if (count > 0) {
1911 final int numColumns = mNumColumns;
1912 final int rowCount = (count + numColumns - 1) / numColumns;
1913
1914 int extent = rowCount * 100;
1915
1916 View view = getChildAt(0);
1917 final int top = view.getTop();
1918 int height = view.getHeight();
1919 if (height > 0) {
1920 extent += (top * 100) / height;
1921 }
1922
1923 view = getChildAt(count - 1);
1924 final int bottom = view.getBottom();
1925 height = view.getHeight();
1926 if (height > 0) {
1927 extent -= ((bottom - getHeight()) * 100) / height;
1928 }
1929
1930 return extent;
1931 }
1932 return 0;
1933 }
1934
1935 @Override
1936 protected int computeVerticalScrollOffset() {
1937 if (mFirstPosition >= 0 && getChildCount() > 0) {
1938 final View view = getChildAt(0);
1939 final int top = view.getTop();
1940 int height = view.getHeight();
1941 if (height > 0) {
Adam Powellf2a204e2010-02-12 15:25:33 -08001942 final int numColumns = mNumColumns;
1943 final int whichRow = mFirstPosition / numColumns;
1944 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
1945 return Math.max(whichRow * 100 - (top * 100) / height +
1946 (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001947 }
1948 }
1949 return 0;
1950 }
1951
1952 @Override
1953 protected int computeVerticalScrollRange() {
1954 // TODO: Account for vertical spacing too
1955 final int numColumns = mNumColumns;
1956 final int rowCount = (mItemCount + numColumns - 1) / numColumns;
Adam Powell637d3372010-08-25 14:37:03 -07001957 int result = Math.max(rowCount * 100, 0);
1958 if (mScrollY != 0) {
1959 // Compensate for overscroll
1960 result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
1961 }
1962 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001963 }
1964}
1965