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.annotation.NonNull; 20 import android.annotation.Widget; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Rect; 24 import android.os.Bundle; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.ContextMenu.ContextMenuInfo; 28 import android.view.GestureDetector; 29 import android.view.Gravity; 30 import android.view.HapticFeedbackConstants; 31 import android.view.KeyEvent; 32 import android.view.MotionEvent; 33 import android.view.SoundEffectConstants; 34 import android.view.View; 35 import android.view.ViewConfiguration; 36 import android.view.ViewGroup; 37 import android.view.accessibility.AccessibilityNodeInfo; 38 import android.view.animation.Transformation; 39 40 import com.android.internal.R; 41 42 /** 43 * A view that shows items in a center-locked, horizontally scrolling list. 44 * <p> 45 * The default values for the Gallery assume you will be using 46 * {@link android.R.styleable#Theme_galleryItemBackground} as the background for 47 * each View given to the Gallery from the Adapter. If you are not doing this, 48 * you may need to adjust some Gallery properties, such as the spacing. 49 * <p> 50 * Views given to the Gallery should use {@link Gallery.LayoutParams} as their 51 * layout parameters type. 52 * 53 * @attr ref android.R.styleable#Gallery_animationDuration 54 * @attr ref android.R.styleable#Gallery_spacing 55 * @attr ref android.R.styleable#Gallery_gravity 56 * 57 * @deprecated This widget is no longer supported. Other horizontally scrolling 58 * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager} 59 * from the support library. 60 */ 61 @Deprecated 62 @Widget 63 public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener { 64 65 private static final String TAG = "Gallery"; 66 67 private static final boolean localLOGV = false; 68 69 /** 70 * Duration in milliseconds from the start of a scroll during which we're 71 * unsure whether the user is scrolling or flinging. 72 */ 73 private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250; 74 75 /** 76 * Horizontal spacing between items. 77 */ 78 private int mSpacing = 0; 79 80 /** 81 * How long the transition animation should run when a child view changes 82 * position, measured in milliseconds. 83 */ 84 private int mAnimationDuration = 400; 85 86 /** 87 * The alpha of items that are not selected. 88 */ 89 private float mUnselectedAlpha; 90 91 /** 92 * Left most edge of a child seen so far during layout. 93 */ 94 private int mLeftMost; 95 96 /** 97 * Right most edge of a child seen so far during layout. 98 */ 99 private int mRightMost; 100 101 private int mGravity; 102 103 /** 104 * Helper for detecting touch gestures. 105 */ 106 private GestureDetector mGestureDetector; 107 108 /** 109 * The position of the item that received the user's down touch. 110 */ 111 private int mDownTouchPosition; 112 113 /** 114 * The view of the item that received the user's down touch. 115 */ 116 private View mDownTouchView; 117 118 /** 119 * Executes the delta scrolls from a fling or scroll movement. 120 */ 121 private FlingRunnable mFlingRunnable = new FlingRunnable(); 122 123 /** 124 * Sets mSuppressSelectionChanged = false. This is used to set it to false 125 * in the future. It will also trigger a selection changed. 126 */ 127 private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() { 128 @Override 129 public void run() { 130 mSuppressSelectionChanged = false; 131 selectionChanged(); 132 } 133 }; 134 135 /** 136 * When fling runnable runs, it resets this to false. Any method along the 137 * path until the end of its run() can set this to true to abort any 138 * remaining fling. For example, if we've reached either the leftmost or 139 * rightmost item, we will set this to true. 140 */ 141 private boolean mShouldStopFling; 142 143 /** 144 * The currently selected item's child. 145 */ 146 private View mSelectedChild; 147 148 /** 149 * Whether to continuously callback on the item selected listener during a 150 * fling. 151 */ 152 private boolean mShouldCallbackDuringFling = true; 153 154 /** 155 * Whether to callback when an item that is not selected is clicked. 156 */ 157 private boolean mShouldCallbackOnUnselectedItemClick = true; 158 159 /** 160 * If true, do not callback to item selected listener. 161 */ 162 private boolean mSuppressSelectionChanged; 163 164 /** 165 * If true, we have received the "invoke" (center or enter buttons) key 166 * down. This is checked before we action on the "invoke" key up, and is 167 * subsequently cleared. 168 */ 169 private boolean mReceivedInvokeKeyDown; 170 171 private AdapterContextMenuInfo mContextMenuInfo; 172 173 /** 174 * If true, this onScroll is the first for this user's drag (remember, a 175 * drag sends many onScrolls). 176 */ 177 private boolean mIsFirstScroll; 178 179 /** 180 * If true, mFirstPosition is the position of the rightmost child, and 181 * the children are ordered right to left. 182 */ 183 private boolean mIsRtl = true; 184 185 /** 186 * Offset between the center of the selected child view and the center of the Gallery. 187 * Used to reset position correctly during layout. 188 */ 189 private int mSelectedCenterOffset; 190 191 public Gallery(Context context) { 192 this(context, null); 193 } 194 195 public Gallery(Context context, AttributeSet attrs) { 196 this(context, attrs, R.attr.galleryStyle); 197 } 198 199 public Gallery(Context context, AttributeSet attrs, int defStyleAttr) { 200 this(context, attrs, defStyleAttr, 0); 201 } 202 203 public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 204 super(context, attrs, defStyleAttr, defStyleRes); 205 206 final TypedArray a = context.obtainStyledAttributes( 207 attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes); 208 209 int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1); 210 if (index >= 0) { 211 setGravity(index); 212 } 213 214 int animationDuration = 215 a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1); 216 if (animationDuration > 0) { 217 setAnimationDuration(animationDuration); 218 } 219 220 int spacing = 221 a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0); 222 setSpacing(spacing); 223 224 float unselectedAlpha = a.getFloat( 225 com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f); 226 setUnselectedAlpha(unselectedAlpha); 227 228 a.recycle(); 229 230 // We draw the selected item last (because otherwise the item to the 231 // right overlaps it) 232 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER; 233 234 mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS; 235 } 236 237 @Override 238 protected void onAttachedToWindow() { 239 super.onAttachedToWindow(); 240 241 if (mGestureDetector == null) { 242 mGestureDetector = new GestureDetector(getContext(), this); 243 mGestureDetector.setIsLongpressEnabled(true); 244 } 245 } 246 247 /** 248 * Whether or not to callback on any {@link #getOnItemSelectedListener()} 249 * while the items are being flinged. If false, only the final selected item 250 * will cause the callback. If true, all items between the first and the 251 * final will cause callbacks. 252 * 253 * @param shouldCallback Whether or not to callback on the listener while 254 * the items are being flinged. 255 */ 256 public void setCallbackDuringFling(boolean shouldCallback) { 257 mShouldCallbackDuringFling = shouldCallback; 258 } 259 260 /** 261 * Whether or not to callback when an item that is not selected is clicked. 262 * If false, the item will become selected (and re-centered). If true, the 263 * {@link #getOnItemClickListener()} will get the callback. 264 * 265 * @param shouldCallback Whether or not to callback on the listener when a 266 * item that is not selected is clicked. 267 * @hide 268 */ 269 public void setCallbackOnUnselectedItemClick(boolean shouldCallback) { 270 mShouldCallbackOnUnselectedItemClick = shouldCallback; 271 } 272 273 /** 274 * Sets how long the transition animation should run when a child view 275 * changes position. Only relevant if animation is turned on. 276 * 277 * @param animationDurationMillis The duration of the transition, in 278 * milliseconds. 279 * 280 * @attr ref android.R.styleable#Gallery_animationDuration 281 */ 282 public void setAnimationDuration(int animationDurationMillis) { 283 mAnimationDuration = animationDurationMillis; 284 } 285 286 /** 287 * Sets the spacing between items in a Gallery 288 * 289 * @param spacing The spacing in pixels between items in the Gallery 290 * 291 * @attr ref android.R.styleable#Gallery_spacing 292 */ 293 public void setSpacing(int spacing) { 294 mSpacing = spacing; 295 } 296 297 /** 298 * Sets the alpha of items that are not selected in the Gallery. 299 * 300 * @param unselectedAlpha the alpha for the items that are not selected. 301 * 302 * @attr ref android.R.styleable#Gallery_unselectedAlpha 303 */ 304 public void setUnselectedAlpha(float unselectedAlpha) { 305 mUnselectedAlpha = unselectedAlpha; 306 } 307 308 @Override 309 protected boolean getChildStaticTransformation(View child, Transformation t) { 310 311 t.clear(); 312 t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha); 313 314 return true; 315 } 316 317 @Override 318 protected int computeHorizontalScrollExtent() { 319 // Only 1 item is considered to be selected 320 return 1; 321 } 322 323 @Override 324 protected int computeHorizontalScrollOffset() { 325 // Current scroll position is the same as the selected position 326 return mSelectedPosition; 327 } 328 329 @Override 330 protected int computeHorizontalScrollRange() { 331 // Scroll range is the same as the item count 332 return mItemCount; 333 } 334 335 @Override 336 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 337 return p instanceof LayoutParams; 338 } 339 340 @Override 341 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 342 return new LayoutParams(p); 343 } 344 345 @Override 346 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 347 return new LayoutParams(getContext(), attrs); 348 } 349 350 @Override 351 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 352 /* 353 * Gallery expects Gallery.LayoutParams. 354 */ 355 return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 356 ViewGroup.LayoutParams.WRAP_CONTENT); 357 } 358 359 @Override 360 protected void onLayout(boolean changed, int l, int t, int r, int b) { 361 super.onLayout(changed, l, t, r, b); 362 363 /* 364 * Remember that we are in layout to prevent more layout request from 365 * being generated. 366 */ 367 mInLayout = true; 368 layout(0, false); 369 mInLayout = false; 370 } 371 372 @Override 373 int getChildHeight(View child) { 374 return child.getMeasuredHeight(); 375 } 376 377 /** 378 * Tracks a motion scroll. In reality, this is used to do just about any 379 * movement to items (touch scroll, arrow-key scroll, set an item as selected). 380 * 381 * @param deltaX Change in X from the previous event. 382 */ 383 void trackMotionScroll(int deltaX) { 384 385 if (getChildCount() == 0) { 386 return; 387 } 388 389 boolean toLeft = deltaX < 0; 390 391 int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX); 392 if (limitedDeltaX != deltaX) { 393 // The above call returned a limited amount, so stop any scrolls/flings 394 mFlingRunnable.endFling(false); 395 onFinishedMovement(); 396 } 397 398 offsetChildrenLeftAndRight(limitedDeltaX); 399 400 detachOffScreenChildren(toLeft); 401 402 if (toLeft) { 403 // If moved left, there will be empty space on the right 404 fillToGalleryRight(); 405 } else { 406 // Similarly, empty space on the left 407 fillToGalleryLeft(); 408 } 409 410 // Clear unused views 411 mRecycler.clear(); 412 413 setSelectionToCenterChild(); 414 415 final View selChild = mSelectedChild; 416 if (selChild != null) { 417 final int childLeft = selChild.getLeft(); 418 final int childCenter = selChild.getWidth() / 2; 419 final int galleryCenter = getWidth() / 2; 420 mSelectedCenterOffset = childLeft + childCenter - galleryCenter; 421 } 422 423 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. 424 425 invalidate(); 426 } 427 428 int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) { 429 int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0; 430 View extremeChild = getChildAt(extremeItemPosition - mFirstPosition); 431 432 if (extremeChild == null) { 433 return deltaX; 434 } 435 436 int extremeChildCenter = getCenterOfView(extremeChild); 437 int galleryCenter = getCenterOfGallery(); 438 439 if (motionToLeft) { 440 if (extremeChildCenter <= galleryCenter) { 441 442 // The extreme child is past his boundary point! 443 return 0; 444 } 445 } else { 446 if (extremeChildCenter >= galleryCenter) { 447 448 // The extreme child is past his boundary point! 449 return 0; 450 } 451 } 452 453 int centerDifference = galleryCenter - extremeChildCenter; 454 455 return motionToLeft 456 ? Math.max(centerDifference, deltaX) 457 : Math.min(centerDifference, deltaX); 458 } 459 460 /** 461 * Offset the horizontal location of all children of this view by the 462 * specified number of pixels. 463 * 464 * @param offset the number of pixels to offset 465 */ 466 private void offsetChildrenLeftAndRight(int offset) { 467 for (int i = getChildCount() - 1; i >= 0; i--) { 468 getChildAt(i).offsetLeftAndRight(offset); 469 } 470 } 471 472 /** 473 * @return The center of this Gallery. 474 */ 475 private int getCenterOfGallery() { 476 return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft; 477 } 478 479 /** 480 * @return The center of the given view. 481 */ 482 private static int getCenterOfView(View view) { 483 return view.getLeft() + view.getWidth() / 2; 484 } 485 486 /** 487 * Detaches children that are off the screen (i.e.: Gallery bounds). 488 * 489 * @param toLeft Whether to detach children to the left of the Gallery, or 490 * to the right. 491 */ 492 private void detachOffScreenChildren(boolean toLeft) { 493 int numChildren = getChildCount(); 494 int firstPosition = mFirstPosition; 495 int start = 0; 496 int count = 0; 497 498 if (toLeft) { 499 final int galleryLeft = mPaddingLeft; 500 for (int i = 0; i < numChildren; i++) { 501 int n = mIsRtl ? (numChildren - 1 - i) : i; 502 final View child = getChildAt(n); 503 if (child.getRight() >= galleryLeft) { 504 break; 505 } else { 506 start = n; 507 count++; 508 mRecycler.put(firstPosition + n, child); 509 } 510 } 511 if (!mIsRtl) { 512 start = 0; 513 } 514 } else { 515 final int galleryRight = getWidth() - mPaddingRight; 516 for (int i = numChildren - 1; i >= 0; i--) { 517 int n = mIsRtl ? numChildren - 1 - i : i; 518 final View child = getChildAt(n); 519 if (child.getLeft() <= galleryRight) { 520 break; 521 } else { 522 start = n; 523 count++; 524 mRecycler.put(firstPosition + n, child); 525 } 526 } 527 if (mIsRtl) { 528 start = 0; 529 } 530 } 531 532 detachViewsFromParent(start, count); 533 534 if (toLeft != mIsRtl) { 535 mFirstPosition += count; 536 } 537 } 538 539 /** 540 * Scrolls the items so that the selected item is in its 'slot' (its center 541 * is the gallery's center). 542 */ 543 private void scrollIntoSlots() { 544 545 if (getChildCount() == 0 || mSelectedChild == null) return; 546 547 int selectedCenter = getCenterOfView(mSelectedChild); 548 int targetCenter = getCenterOfGallery(); 549 550 int scrollAmount = targetCenter - selectedCenter; 551 if (scrollAmount != 0) { 552 mFlingRunnable.startUsingDistance(scrollAmount); 553 } else { 554 onFinishedMovement(); 555 } 556 } 557 558 private void onFinishedMovement() { 559 if (mSuppressSelectionChanged) { 560 mSuppressSelectionChanged = false; 561 562 // We haven't been callbacking during the fling, so do it now 563 super.selectionChanged(); 564 } 565 mSelectedCenterOffset = 0; 566 invalidate(); 567 } 568 569 @Override 570 void selectionChanged() { 571 if (!mSuppressSelectionChanged) { 572 super.selectionChanged(); 573 } 574 } 575 576 /** 577 * Looks for the child that is closest to the center and sets it as the 578 * selected child. 579 */ 580 private void setSelectionToCenterChild() { 581 582 View selView = mSelectedChild; 583 if (mSelectedChild == null) return; 584 585 int galleryCenter = getCenterOfGallery(); 586 587 // Common case where the current selected position is correct 588 if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) { 589 return; 590 } 591 592 // TODO better search 593 int closestEdgeDistance = Integer.MAX_VALUE; 594 int newSelectedChildIndex = 0; 595 for (int i = getChildCount() - 1; i >= 0; i--) { 596 597 View child = getChildAt(i); 598 599 if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) { 600 // This child is in the center 601 newSelectedChildIndex = i; 602 break; 603 } 604 605 int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter), 606 Math.abs(child.getRight() - galleryCenter)); 607 if (childClosestEdgeDistance < closestEdgeDistance) { 608 closestEdgeDistance = childClosestEdgeDistance; 609 newSelectedChildIndex = i; 610 } 611 } 612 613 int newPos = mFirstPosition + newSelectedChildIndex; 614 615 if (newPos != mSelectedPosition) { 616 setSelectedPositionInt(newPos); 617 setNextSelectedPositionInt(newPos); 618 checkSelectionChanged(); 619 } 620 } 621 622 /** 623 * Creates and positions all views for this Gallery. 624 * <p> 625 * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes 626 * care of repositioning, adding, and removing children. 627 * 628 * @param delta Change in the selected position. +1 means the selection is 629 * moving to the right, so views are scrolling to the left. -1 630 * means the selection is moving to the left. 631 */ 632 @Override 633 void layout(int delta, boolean animate) { 634 635 mIsRtl = isLayoutRtl(); 636 637 int childrenLeft = mSpinnerPadding.left; 638 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right; 639 640 if (mDataChanged) { 641 handleDataChanged(); 642 } 643 644 // Handle an empty gallery by removing all views. 645 if (mItemCount == 0) { 646 resetList(); 647 return; 648 } 649 650 // Update to the new selected position. 651 if (mNextSelectedPosition >= 0) { 652 setSelectedPositionInt(mNextSelectedPosition); 653 } 654 655 // All views go in recycler while we are in layout 656 recycleAllViews(); 657 658 // Clear out old views 659 //removeAllViewsInLayout(); 660 detachAllViewsFromParent(); 661 662 /* 663 * These will be used to give initial positions to views entering the 664 * gallery as we scroll 665 */ 666 mRightMost = 0; 667 mLeftMost = 0; 668 669 // Make selected view and center it 670 671 /* 672 * mFirstPosition will be decreased as we add views to the left later 673 * on. The 0 for x will be offset in a couple lines down. 674 */ 675 mFirstPosition = mSelectedPosition; 676 View sel = makeAndAddView(mSelectedPosition, 0, 0, true); 677 678 // Put the selected child in the center 679 int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) + 680 mSelectedCenterOffset; 681 sel.offsetLeftAndRight(selectedOffset); 682 683 fillToGalleryRight(); 684 fillToGalleryLeft(); 685 686 // Flush any cached views that did not get reused above 687 mRecycler.clear(); 688 689 invalidate(); 690 checkSelectionChanged(); 691 692 mDataChanged = false; 693 mNeedSync = false; 694 setNextSelectedPositionInt(mSelectedPosition); 695 696 updateSelectedItemMetadata(); 697 } 698 699 private void fillToGalleryLeft() { 700 if (mIsRtl) { 701 fillToGalleryLeftRtl(); 702 } else { 703 fillToGalleryLeftLtr(); 704 } 705 } 706 707 private void fillToGalleryLeftRtl() { 708 int itemSpacing = mSpacing; 709 int galleryLeft = mPaddingLeft; 710 int numChildren = getChildCount(); 711 int numItems = mItemCount; 712 713 // Set state for initial iteration 714 View prevIterationView = getChildAt(numChildren - 1); 715 int curPosition; 716 int curRightEdge; 717 718 if (prevIterationView != null) { 719 curPosition = mFirstPosition + numChildren; 720 curRightEdge = prevIterationView.getLeft() - itemSpacing; 721 } else { 722 // No children available! 723 mFirstPosition = curPosition = mItemCount - 1; 724 curRightEdge = mRight - mLeft - mPaddingRight; 725 mShouldStopFling = true; 726 } 727 728 while (curRightEdge > galleryLeft && curPosition < mItemCount) { 729 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, 730 curRightEdge, false); 731 732 // Set state for next iteration 733 curRightEdge = prevIterationView.getLeft() - itemSpacing; 734 curPosition++; 735 } 736 } 737 738 private void fillToGalleryLeftLtr() { 739 int itemSpacing = mSpacing; 740 int galleryLeft = mPaddingLeft; 741 742 // Set state for initial iteration 743 View prevIterationView = getChildAt(0); 744 int curPosition; 745 int curRightEdge; 746 747 if (prevIterationView != null) { 748 curPosition = mFirstPosition - 1; 749 curRightEdge = prevIterationView.getLeft() - itemSpacing; 750 } else { 751 // No children available! 752 curPosition = 0; 753 curRightEdge = mRight - mLeft - mPaddingRight; 754 mShouldStopFling = true; 755 } 756 757 while (curRightEdge > galleryLeft && curPosition >= 0) { 758 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, 759 curRightEdge, false); 760 761 // Remember some state 762 mFirstPosition = curPosition; 763 764 // Set state for next iteration 765 curRightEdge = prevIterationView.getLeft() - itemSpacing; 766 curPosition--; 767 } 768 } 769 770 private void fillToGalleryRight() { 771 if (mIsRtl) { 772 fillToGalleryRightRtl(); 773 } else { 774 fillToGalleryRightLtr(); 775 } 776 } 777 778 private void fillToGalleryRightRtl() { 779 int itemSpacing = mSpacing; 780 int galleryRight = mRight - mLeft - mPaddingRight; 781 782 // Set state for initial iteration 783 View prevIterationView = getChildAt(0); 784 int curPosition; 785 int curLeftEdge; 786 787 if (prevIterationView != null) { 788 curPosition = mFirstPosition -1; 789 curLeftEdge = prevIterationView.getRight() + itemSpacing; 790 } else { 791 curPosition = 0; 792 curLeftEdge = mPaddingLeft; 793 mShouldStopFling = true; 794 } 795 796 while (curLeftEdge < galleryRight && curPosition >= 0) { 797 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, 798 curLeftEdge, true); 799 800 // Remember some state 801 mFirstPosition = curPosition; 802 803 // Set state for next iteration 804 curLeftEdge = prevIterationView.getRight() + itemSpacing; 805 curPosition--; 806 } 807 } 808 809 private void fillToGalleryRightLtr() { 810 int itemSpacing = mSpacing; 811 int galleryRight = mRight - mLeft - mPaddingRight; 812 int numChildren = getChildCount(); 813 int numItems = mItemCount; 814 815 // Set state for initial iteration 816 View prevIterationView = getChildAt(numChildren - 1); 817 int curPosition; 818 int curLeftEdge; 819 820 if (prevIterationView != null) { 821 curPosition = mFirstPosition + numChildren; 822 curLeftEdge = prevIterationView.getRight() + itemSpacing; 823 } else { 824 mFirstPosition = curPosition = mItemCount - 1; 825 curLeftEdge = mPaddingLeft; 826 mShouldStopFling = true; 827 } 828 829 while (curLeftEdge < galleryRight && curPosition < numItems) { 830 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, 831 curLeftEdge, true); 832 833 // Set state for next iteration 834 curLeftEdge = prevIterationView.getRight() + itemSpacing; 835 curPosition++; 836 } 837 } 838 839 /** 840 * Obtain a view, either by pulling an existing view from the recycler or by 841 * getting a new one from the adapter. If we are animating, make sure there 842 * is enough information in the view's layout parameters to animate from the 843 * old to new positions. 844 * 845 * @param position Position in the gallery for the view to obtain 846 * @param offset Offset from the selected position 847 * @param x X-coordinate indicating where this view should be placed. This 848 * will either be the left or right edge of the view, depending on 849 * the fromLeft parameter 850 * @param fromLeft Are we positioning views based on the left edge? (i.e., 851 * building from left to right)? 852 * @return A view that has been added to the gallery 853 */ 854 private View makeAndAddView(int position, int offset, int x, boolean fromLeft) { 855 856 View child; 857 if (!mDataChanged) { 858 child = mRecycler.get(position); 859 if (child != null) { 860 // Can reuse an existing view 861 int childLeft = child.getLeft(); 862 863 // Remember left and right edges of where views have been placed 864 mRightMost = Math.max(mRightMost, childLeft 865 + child.getMeasuredWidth()); 866 mLeftMost = Math.min(mLeftMost, childLeft); 867 868 // Position the view 869 setUpChild(child, offset, x, fromLeft); 870 871 return child; 872 } 873 } 874 875 // Nothing found in the recycler -- ask the adapter for a view 876 child = mAdapter.getView(position, null, this); 877 878 // Position the view 879 setUpChild(child, offset, x, fromLeft); 880 881 return child; 882 } 883 884 /** 885 * Helper for makeAndAddView to set the position of a view and fill out its 886 * layout parameters. 887 * 888 * @param child The view to position 889 * @param offset Offset from the selected position 890 * @param x X-coordinate indicating where this view should be placed. This 891 * will either be the left or right edge of the view, depending on 892 * the fromLeft parameter 893 * @param fromLeft Are we positioning views based on the left edge? (i.e., 894 * building from left to right)? 895 */ 896 private void setUpChild(View child, int offset, int x, boolean fromLeft) { 897 898 // Respect layout params that are already in the view. Otherwise 899 // make some up... 900 Gallery.LayoutParams lp = (Gallery.LayoutParams) child.getLayoutParams(); 901 if (lp == null) { 902 lp = (Gallery.LayoutParams) generateDefaultLayoutParams(); 903 } 904 905 addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp, true); 906 907 child.setSelected(offset == 0); 908 909 // Get measure specs 910 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, 911 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height); 912 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 913 mSpinnerPadding.left + mSpinnerPadding.right, lp.width); 914 915 // Measure child 916 child.measure(childWidthSpec, childHeightSpec); 917 918 int childLeft; 919 int childRight; 920 921 // Position vertically based on gravity setting 922 int childTop = calculateTop(child, true); 923 int childBottom = childTop + child.getMeasuredHeight(); 924 925 int width = child.getMeasuredWidth(); 926 if (fromLeft) { 927 childLeft = x; 928 childRight = childLeft + width; 929 } else { 930 childLeft = x - width; 931 childRight = x; 932 } 933 934 child.layout(childLeft, childTop, childRight, childBottom); 935 } 936 937 /** 938 * Figure out vertical placement based on mGravity 939 * 940 * @param child Child to place 941 * @return Where the top of the child should be 942 */ 943 private int calculateTop(View child, boolean duringLayout) { 944 int myHeight = duringLayout ? getMeasuredHeight() : getHeight(); 945 int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight(); 946 947 int childTop = 0; 948 949 switch (mGravity) { 950 case Gravity.TOP: 951 childTop = mSpinnerPadding.top; 952 break; 953 case Gravity.CENTER_VERTICAL: 954 int availableSpace = myHeight - mSpinnerPadding.bottom 955 - mSpinnerPadding.top - childHeight; 956 childTop = mSpinnerPadding.top + (availableSpace / 2); 957 break; 958 case Gravity.BOTTOM: 959 childTop = myHeight - mSpinnerPadding.bottom - childHeight; 960 break; 961 } 962 return childTop; 963 } 964 965 @Override 966 public boolean onTouchEvent(MotionEvent event) { 967 968 // Give everything to the gesture detector 969 boolean retValue = mGestureDetector.onTouchEvent(event); 970 971 int action = event.getAction(); 972 if (action == MotionEvent.ACTION_UP) { 973 // Helper method for lifted finger 974 onUp(); 975 } else if (action == MotionEvent.ACTION_CANCEL) { 976 onCancel(); 977 } 978 979 return retValue; 980 } 981 982 @Override 983 public boolean onSingleTapUp(MotionEvent e) { 984 985 if (mDownTouchPosition >= 0) { 986 987 // An item tap should make it selected, so scroll to this child. 988 scrollToChild(mDownTouchPosition - mFirstPosition); 989 990 // Also pass the click so the client knows, if it wants to. 991 if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) { 992 performItemClick(mDownTouchView, mDownTouchPosition, mAdapter 993 .getItemId(mDownTouchPosition)); 994 } 995 996 return true; 997 } 998 999 return false; 1000 } 1001 1002 @Override 1003 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 1004 1005 if (!mShouldCallbackDuringFling) { 1006 // We want to suppress selection changes 1007 1008 // Remove any future code to set mSuppressSelectionChanged = false 1009 removeCallbacks(mDisableSuppressSelectionChangedRunnable); 1010 1011 // This will get reset once we scroll into slots 1012 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; 1013 } 1014 1015 // Fling the gallery! 1016 mFlingRunnable.startUsingVelocity((int) -velocityX); 1017 1018 return true; 1019 } 1020 1021 @Override 1022 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 1023 1024 if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX())); 1025 1026 /* 1027 * Now's a good time to tell our parent to stop intercepting our events! 1028 * The user has moved more than the slop amount, since GestureDetector 1029 * ensures this before calling this method. Also, if a parent is more 1030 * interested in this touch's events than we are, it would have 1031 * intercepted them by now (for example, we can assume when a Gallery is 1032 * in the ListView, a vertical scroll would not end up in this method 1033 * since a ListView would have intercepted it by now). 1034 */ 1035 mParent.requestDisallowInterceptTouchEvent(true); 1036 1037 // As the user scrolls, we want to callback selection changes so related- 1038 // info on the screen is up-to-date with the gallery's selection 1039 if (!mShouldCallbackDuringFling) { 1040 if (mIsFirstScroll) { 1041 /* 1042 * We're not notifying the client of selection changes during 1043 * the fling, and this scroll could possibly be a fling. Don't 1044 * do selection changes until we're sure it is not a fling. 1045 */ 1046 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; 1047 postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT); 1048 } 1049 } else { 1050 if (mSuppressSelectionChanged) mSuppressSelectionChanged = false; 1051 } 1052 1053 // Track the motion 1054 trackMotionScroll(-1 * (int) distanceX); 1055 1056 mIsFirstScroll = false; 1057 return true; 1058 } 1059 1060 @Override 1061 public boolean onDown(MotionEvent e) { 1062 1063 // Kill any existing fling/scroll 1064 mFlingRunnable.stop(false); 1065 1066 // Get the item's view that was touched 1067 mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY()); 1068 1069 if (mDownTouchPosition >= 0) { 1070 mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition); 1071 mDownTouchView.setPressed(true); 1072 } 1073 1074 // Reset the multiple-scroll tracking state 1075 mIsFirstScroll = true; 1076 1077 // Must return true to get matching events for this down event. 1078 return true; 1079 } 1080 1081 /** 1082 * Called when a touch event's action is MotionEvent.ACTION_UP. 1083 */ 1084 void onUp() { 1085 1086 if (mFlingRunnable.mScroller.isFinished()) { 1087 scrollIntoSlots(); 1088 } 1089 1090 dispatchUnpress(); 1091 } 1092 1093 /** 1094 * Called when a touch event's action is MotionEvent.ACTION_CANCEL. 1095 */ 1096 void onCancel() { 1097 onUp(); 1098 } 1099 1100 @Override 1101 public void onLongPress(@NonNull MotionEvent e) { 1102 if (mDownTouchPosition < 0) { 1103 return; 1104 } 1105 1106 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 1107 1108 final long id = getItemIdAtPosition(mDownTouchPosition); 1109 dispatchLongPress(mDownTouchView, mDownTouchPosition, id, e.getX(), e.getY(), true); 1110 } 1111 1112 // Unused methods from GestureDetector.OnGestureListener below 1113 1114 @Override 1115 public void onShowPress(MotionEvent e) { 1116 } 1117 1118 // Unused methods from GestureDetector.OnGestureListener above 1119 1120 private void dispatchPress(View child) { 1121 1122 if (child != null) { 1123 child.setPressed(true); 1124 } 1125 1126 setPressed(true); 1127 } 1128 1129 private void dispatchUnpress() { 1130 1131 for (int i = getChildCount() - 1; i >= 0; i--) { 1132 getChildAt(i).setPressed(false); 1133 } 1134 1135 setPressed(false); 1136 } 1137 1138 @Override 1139 public void dispatchSetSelected(boolean selected) { 1140 /* 1141 * We don't want to pass the selected state given from its parent to its 1142 * children since this widget itself has a selected state to give to its 1143 * children. 1144 */ 1145 } 1146 1147 @Override 1148 protected void dispatchSetPressed(boolean pressed) { 1149 1150 // Show the pressed state on the selected child 1151 if (mSelectedChild != null) { 1152 mSelectedChild.setPressed(pressed); 1153 } 1154 } 1155 1156 @Override 1157 protected ContextMenuInfo getContextMenuInfo() { 1158 return mContextMenuInfo; 1159 } 1160 1161 @Override 1162 public boolean showContextMenuForChild(View originalView) { 1163 if (isShowingContextMenuWithCoords()) { 1164 return false; 1165 } 1166 return showContextMenuForChildInternal(originalView, 0, 0, false); 1167 } 1168 1169 @Override 1170 public boolean showContextMenuForChild(View originalView, float x, float y) { 1171 return showContextMenuForChildInternal(originalView, x, y, true); 1172 } 1173 1174 private boolean showContextMenuForChildInternal(View originalView, float x, float y, 1175 boolean useOffsets) { 1176 final int longPressPosition = getPositionForView(originalView); 1177 if (longPressPosition < 0) { 1178 return false; 1179 } 1180 1181 final long longPressId = mAdapter.getItemId(longPressPosition); 1182 return dispatchLongPress(originalView, longPressPosition, longPressId, x, y, useOffsets); 1183 } 1184 1185 @Override 1186 public boolean showContextMenu() { 1187 return showContextMenuInternal(0, 0, false); 1188 } 1189 1190 @Override 1191 public boolean showContextMenu(float x, float y) { 1192 return showContextMenuInternal(x, y, true); 1193 } 1194 1195 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) { 1196 if (isPressed() && mSelectedPosition >= 0) { 1197 final int index = mSelectedPosition - mFirstPosition; 1198 final View v = getChildAt(index); 1199 return dispatchLongPress(v, mSelectedPosition, mSelectedRowId, x, y, useOffsets); 1200 } 1201 1202 return false; 1203 } 1204 1205 private boolean dispatchLongPress(View view, int position, long id, float x, float y, 1206 boolean useOffsets) { 1207 boolean handled = false; 1208 1209 if (mOnItemLongClickListener != null) { 1210 handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView, 1211 mDownTouchPosition, id); 1212 } 1213 1214 if (!handled) { 1215 mContextMenuInfo = new AdapterContextMenuInfo(view, position, id); 1216 1217 if (useOffsets) { 1218 handled = super.showContextMenuForChild(view, x, y); 1219 } else { 1220 handled = super.showContextMenuForChild(this); 1221 } 1222 } 1223 1224 if (handled) { 1225 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 1226 } 1227 1228 return handled; 1229 } 1230 1231 @Override 1232 public boolean dispatchKeyEvent(KeyEvent event) { 1233 // Gallery steals all key events 1234 return event.dispatch(this, null, null); 1235 } 1236 1237 /** 1238 * Handles left, right, and clicking 1239 * @see android.view.View#onKeyDown 1240 */ 1241 @Override 1242 public boolean onKeyDown(int keyCode, KeyEvent event) { 1243 switch (keyCode) { 1244 1245 case KeyEvent.KEYCODE_DPAD_LEFT: 1246 if (moveDirection(-1)) { 1247 playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 1248 return true; 1249 } 1250 break; 1251 case KeyEvent.KEYCODE_DPAD_RIGHT: 1252 if (moveDirection(1)) { 1253 playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 1254 return true; 1255 } 1256 break; 1257 case KeyEvent.KEYCODE_DPAD_CENTER: 1258 case KeyEvent.KEYCODE_ENTER: 1259 mReceivedInvokeKeyDown = true; 1260 // fallthrough to default handling 1261 } 1262 1263 return super.onKeyDown(keyCode, event); 1264 } 1265 1266 @Override 1267 public boolean onKeyUp(int keyCode, KeyEvent event) { 1268 if (KeyEvent.isConfirmKey(keyCode)) { 1269 if (mReceivedInvokeKeyDown) { 1270 if (mItemCount > 0) { 1271 dispatchPress(mSelectedChild); 1272 postDelayed(new Runnable() { 1273 @Override 1274 public void run() { 1275 dispatchUnpress(); 1276 } 1277 }, ViewConfiguration.getPressedStateDuration()); 1278 1279 int selectedIndex = mSelectedPosition - mFirstPosition; 1280 performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter 1281 .getItemId(mSelectedPosition)); 1282 } 1283 } 1284 1285 // Clear the flag 1286 mReceivedInvokeKeyDown = false; 1287 return true; 1288 } 1289 return super.onKeyUp(keyCode, event); 1290 } 1291 1292 boolean moveDirection(int direction) { 1293 direction = isLayoutRtl() ? -direction : direction; 1294 int targetPosition = mSelectedPosition + direction; 1295 1296 if (mItemCount > 0 && targetPosition >= 0 && targetPosition < mItemCount) { 1297 scrollToChild(targetPosition - mFirstPosition); 1298 return true; 1299 } else { 1300 return false; 1301 } 1302 } 1303 1304 private boolean scrollToChild(int childPosition) { 1305 View child = getChildAt(childPosition); 1306 1307 if (child != null) { 1308 int distance = getCenterOfGallery() - getCenterOfView(child); 1309 mFlingRunnable.startUsingDistance(distance); 1310 return true; 1311 } 1312 1313 return false; 1314 } 1315 1316 @Override 1317 void setSelectedPositionInt(int position) { 1318 super.setSelectedPositionInt(position); 1319 1320 // Updates any metadata we keep about the selected item. 1321 updateSelectedItemMetadata(); 1322 } 1323 1324 private void updateSelectedItemMetadata() { 1325 1326 View oldSelectedChild = mSelectedChild; 1327 1328 View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition); 1329 if (child == null) { 1330 return; 1331 } 1332 1333 child.setSelected(true); 1334 child.setFocusable(true); 1335 1336 if (hasFocus()) { 1337 child.requestFocus(); 1338 } 1339 1340 // We unfocus the old child down here so the above hasFocus check 1341 // returns true 1342 if (oldSelectedChild != null && oldSelectedChild != child) { 1343 1344 // Make sure its drawable state doesn't contain 'selected' 1345 oldSelectedChild.setSelected(false); 1346 1347 // Make sure it is not focusable anymore, since otherwise arrow keys 1348 // can make this one be focused 1349 oldSelectedChild.setFocusable(false); 1350 } 1351 1352 } 1353 1354 /** 1355 * Describes how the child views are aligned. 1356 * @param gravity 1357 * 1358 * @attr ref android.R.styleable#Gallery_gravity 1359 */ 1360 public void setGravity(int gravity) 1361 { 1362 if (mGravity != gravity) { 1363 mGravity = gravity; 1364 requestLayout(); 1365 } 1366 } 1367 1368 @Override 1369 protected int getChildDrawingOrder(int childCount, int i) { 1370 int selectedIndex = mSelectedPosition - mFirstPosition; 1371 1372 // Just to be safe 1373 if (selectedIndex < 0) return i; 1374 1375 if (i == childCount - 1) { 1376 // Draw the selected child last 1377 return selectedIndex; 1378 } else if (i >= selectedIndex) { 1379 // Move the children after the selected child earlier one 1380 return i + 1; 1381 } else { 1382 // Keep the children before the selected child the same 1383 return i; 1384 } 1385 } 1386 1387 @Override 1388 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 1389 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1390 1391 /* 1392 * The gallery shows focus by focusing the selected item. So, give 1393 * focus to our selected item instead. We steal keys from our 1394 * selected item elsewhere. 1395 */ 1396 if (gainFocus && mSelectedChild != null) { 1397 mSelectedChild.requestFocus(direction); 1398 mSelectedChild.setSelected(true); 1399 } 1400 1401 } 1402 1403 @Override 1404 public CharSequence getAccessibilityClassName() { 1405 return Gallery.class.getName(); 1406 } 1407 1408 /** @hide */ 1409 @Override 1410 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1411 super.onInitializeAccessibilityNodeInfoInternal(info); 1412 info.setScrollable(mItemCount > 1); 1413 if (isEnabled()) { 1414 if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { 1415 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1416 } 1417 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) { 1418 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1419 } 1420 } 1421 } 1422 1423 /** @hide */ 1424 @Override 1425 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 1426 if (super.performAccessibilityActionInternal(action, arguments)) { 1427 return true; 1428 } 1429 switch (action) { 1430 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 1431 if (isEnabled() && mItemCount > 0 && mSelectedPosition < mItemCount - 1) { 1432 final int currentChildIndex = mSelectedPosition - mFirstPosition; 1433 return scrollToChild(currentChildIndex + 1); 1434 } 1435 } return false; 1436 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 1437 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) { 1438 final int currentChildIndex = mSelectedPosition - mFirstPosition; 1439 return scrollToChild(currentChildIndex - 1); 1440 } 1441 } return false; 1442 } 1443 return false; 1444 } 1445 1446 /** 1447 * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to 1448 * initiate a fling. Each frame of the fling is handled in {@link #run()}. 1449 * A FlingRunnable will keep re-posting itself until the fling is done. 1450 */ 1451 private class FlingRunnable implements Runnable { 1452 /** 1453 * Tracks the decay of a fling scroll 1454 */ 1455 private Scroller mScroller; 1456 1457 /** 1458 * X value reported by mScroller on the previous fling 1459 */ 1460 private int mLastFlingX; 1461 1462 public FlingRunnable() { 1463 mScroller = new Scroller(getContext()); 1464 } 1465 1466 private void startCommon() { 1467 // Remove any pending flings 1468 removeCallbacks(this); 1469 } 1470 1471 public void startUsingVelocity(int initialVelocity) { 1472 if (initialVelocity == 0) return; 1473 1474 startCommon(); 1475 1476 int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0; 1477 mLastFlingX = initialX; 1478 mScroller.fling(initialX, 0, initialVelocity, 0, 1479 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); 1480 post(this); 1481 } 1482 1483 public void startUsingDistance(int distance) { 1484 if (distance == 0) return; 1485 1486 startCommon(); 1487 1488 mLastFlingX = 0; 1489 mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration); 1490 post(this); 1491 } 1492 1493 public void stop(boolean scrollIntoSlots) { 1494 removeCallbacks(this); 1495 endFling(scrollIntoSlots); 1496 } 1497 1498 private void endFling(boolean scrollIntoSlots) { 1499 /* 1500 * Force the scroller's status to finished (without setting its 1501 * position to the end) 1502 */ 1503 mScroller.forceFinished(true); 1504 1505 if (scrollIntoSlots) scrollIntoSlots(); 1506 } 1507 1508 @Override 1509 public void run() { 1510 1511 if (mItemCount == 0) { 1512 endFling(true); 1513 return; 1514 } 1515 1516 mShouldStopFling = false; 1517 1518 final Scroller scroller = mScroller; 1519 boolean more = scroller.computeScrollOffset(); 1520 final int x = scroller.getCurrX(); 1521 1522 // Flip sign to convert finger direction to list items direction 1523 // (e.g. finger moving down means list is moving towards the top) 1524 int delta = mLastFlingX - x; 1525 1526 // Pretend that each frame of a fling scroll is a touch scroll 1527 if (delta > 0) { 1528 // Moving towards the left. Use leftmost view as mDownTouchPosition 1529 mDownTouchPosition = mIsRtl ? (mFirstPosition + getChildCount() - 1) : 1530 mFirstPosition; 1531 1532 // Don't fling more than 1 screen 1533 delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta); 1534 } else { 1535 // Moving towards the right. Use rightmost view as mDownTouchPosition 1536 int offsetToLast = getChildCount() - 1; 1537 mDownTouchPosition = mIsRtl ? mFirstPosition : 1538 (mFirstPosition + getChildCount() - 1); 1539 1540 // Don't fling more than 1 screen 1541 delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta); 1542 } 1543 1544 trackMotionScroll(delta); 1545 1546 if (more && !mShouldStopFling) { 1547 mLastFlingX = x; 1548 post(this); 1549 } else { 1550 endFling(true); 1551 } 1552 } 1553 1554 } 1555 1556 /** 1557 * Gallery extends LayoutParams to provide a place to hold current 1558 * Transformation information along with previous position/transformation 1559 * info. 1560 */ 1561 public static class LayoutParams extends ViewGroup.LayoutParams { 1562 public LayoutParams(Context c, AttributeSet attrs) { 1563 super(c, attrs); 1564 } 1565 1566 public LayoutParams(int w, int h) { 1567 super(w, h); 1568 } 1569 1570 public LayoutParams(ViewGroup.LayoutParams source) { 1571 super(source); 1572 } 1573 } 1574 } 1575