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