1 /* 2 * Copyright (C) 2010 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 com.android.gallery3d.ui; 18 19 import android.graphics.Rect; 20 import android.os.Handler; 21 import android.view.GestureDetector; 22 import android.view.MotionEvent; 23 import android.view.animation.DecelerateInterpolator; 24 25 import com.android.gallery3d.anim.Animation; 26 import com.android.gallery3d.app.AbstractGalleryActivity; 27 import com.android.gallery3d.common.Utils; 28 import com.android.gallery3d.glrenderer.GLCanvas; 29 30 public class SlotView extends GLView { 31 @SuppressWarnings("unused") 32 private static final String TAG = "SlotView"; 33 34 private static final boolean WIDE = true; 35 private static final int INDEX_NONE = -1; 36 37 public static final int RENDER_MORE_PASS = 1; 38 public static final int RENDER_MORE_FRAME = 2; 39 40 public interface Listener { 41 public void onDown(int index); 42 public void onUp(boolean followedByLongPress); 43 public void onSingleTapUp(int index); 44 public void onLongTap(int index); 45 public void onScrollPositionChanged(int position, int total); 46 } 47 48 public static class SimpleListener implements Listener { 49 @Override public void onDown(int index) {} 50 @Override public void onUp(boolean followedByLongPress) {} 51 @Override public void onSingleTapUp(int index) {} 52 @Override public void onLongTap(int index) {} 53 @Override public void onScrollPositionChanged(int position, int total) {} 54 } 55 56 public static interface SlotRenderer { 57 public void prepareDrawing(); 58 public void onVisibleRangeChanged(int visibleStart, int visibleEnd); 59 public void onSlotSizeChanged(int width, int height); 60 public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height); 61 } 62 63 private final GestureDetector mGestureDetector; 64 private final ScrollerHelper mScroller; 65 private final Paper mPaper = new Paper(); 66 67 private Listener mListener; 68 private UserInteractionListener mUIListener; 69 70 private boolean mMoreAnimation = false; 71 private SlotAnimation mAnimation = null; 72 private final Layout mLayout = new Layout(); 73 private int mStartIndex = INDEX_NONE; 74 75 // whether the down action happened while the view is scrolling. 76 private boolean mDownInScrolling; 77 private int mOverscrollEffect = OVERSCROLL_3D; 78 private final Handler mHandler; 79 80 private SlotRenderer mRenderer; 81 82 private int[] mRequestRenderSlots = new int[16]; 83 84 public static final int OVERSCROLL_3D = 0; 85 public static final int OVERSCROLL_SYSTEM = 1; 86 public static final int OVERSCROLL_NONE = 2; 87 88 // to prevent allocating memory 89 private final Rect mTempRect = new Rect(); 90 91 public SlotView(AbstractGalleryActivity activity, Spec spec) { 92 mGestureDetector = new GestureDetector(activity, new MyGestureListener()); 93 mScroller = new ScrollerHelper(activity); 94 mHandler = new SynchronizedHandler(activity.getGLRoot()); 95 setSlotSpec(spec); 96 } 97 98 public void setSlotRenderer(SlotRenderer slotDrawer) { 99 mRenderer = slotDrawer; 100 if (mRenderer != null) { 101 mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight); 102 mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd()); 103 } 104 } 105 106 public void setCenterIndex(int index) { 107 int slotCount = mLayout.mSlotCount; 108 if (index < 0 || index >= slotCount) { 109 return; 110 } 111 Rect rect = mLayout.getSlotRect(index, mTempRect); 112 int position = WIDE 113 ? (rect.left + rect.right - getWidth()) / 2 114 : (rect.top + rect.bottom - getHeight()) / 2; 115 setScrollPosition(position); 116 } 117 118 public void makeSlotVisible(int index) { 119 Rect rect = mLayout.getSlotRect(index, mTempRect); 120 int visibleBegin = WIDE ? mScrollX : mScrollY; 121 int visibleLength = WIDE ? getWidth() : getHeight(); 122 int visibleEnd = visibleBegin + visibleLength; 123 int slotBegin = WIDE ? rect.left : rect.top; 124 int slotEnd = WIDE ? rect.right : rect.bottom; 125 126 int position = visibleBegin; 127 if (visibleLength < slotEnd - slotBegin) { 128 position = visibleBegin; 129 } else if (slotBegin < visibleBegin) { 130 position = slotBegin; 131 } else if (slotEnd > visibleEnd) { 132 position = slotEnd - visibleLength; 133 } 134 135 setScrollPosition(position); 136 } 137 138 public void setScrollPosition(int position) { 139 position = Utils.clamp(position, 0, mLayout.getScrollLimit()); 140 mScroller.setPosition(position); 141 updateScrollPosition(position, false); 142 } 143 144 public void setSlotSpec(Spec spec) { 145 mLayout.setSlotSpec(spec); 146 } 147 148 @Override 149 public void addComponent(GLView view) { 150 throw new UnsupportedOperationException(); 151 } 152 153 @Override 154 protected void onLayout(boolean changeSize, int l, int t, int r, int b) { 155 if (!changeSize) return; 156 157 // Make sure we are still at a resonable scroll position after the size 158 // is changed (like orientation change). We choose to keep the center 159 // visible slot still visible. This is arbitrary but reasonable. 160 int visibleIndex = 161 (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2; 162 mLayout.setSize(r - l, b - t); 163 makeSlotVisible(visibleIndex); 164 if (mOverscrollEffect == OVERSCROLL_3D) { 165 mPaper.setSize(r - l, b - t); 166 } 167 } 168 169 public void startScatteringAnimation(RelativePosition position) { 170 mAnimation = new ScatteringAnimation(position); 171 mAnimation.start(); 172 if (mLayout.mSlotCount != 0) invalidate(); 173 } 174 175 public void startRisingAnimation() { 176 mAnimation = new RisingAnimation(); 177 mAnimation.start(); 178 if (mLayout.mSlotCount != 0) invalidate(); 179 } 180 181 private void updateScrollPosition(int position, boolean force) { 182 if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return; 183 if (WIDE) { 184 mScrollX = position; 185 } else { 186 mScrollY = position; 187 } 188 mLayout.setScrollPosition(position); 189 onScrollPositionChanged(position); 190 } 191 192 protected void onScrollPositionChanged(int newPosition) { 193 int limit = mLayout.getScrollLimit(); 194 mListener.onScrollPositionChanged(newPosition, limit); 195 } 196 197 public Rect getSlotRect(int slotIndex) { 198 return mLayout.getSlotRect(slotIndex, new Rect()); 199 } 200 201 @Override 202 protected boolean onTouch(MotionEvent event) { 203 if (mUIListener != null) mUIListener.onUserInteraction(); 204 mGestureDetector.onTouchEvent(event); 205 switch (event.getAction()) { 206 case MotionEvent.ACTION_DOWN: 207 mDownInScrolling = !mScroller.isFinished(); 208 mScroller.forceFinished(); 209 break; 210 case MotionEvent.ACTION_UP: 211 mPaper.onRelease(); 212 invalidate(); 213 break; 214 } 215 return true; 216 } 217 218 public void setListener(Listener listener) { 219 mListener = listener; 220 } 221 222 public void setUserInteractionListener(UserInteractionListener listener) { 223 mUIListener = listener; 224 } 225 226 public void setOverscrollEffect(int kind) { 227 mOverscrollEffect = kind; 228 mScroller.setOverfling(kind == OVERSCROLL_SYSTEM); 229 } 230 231 private static int[] expandIntArray(int array[], int capacity) { 232 while (array.length < capacity) { 233 array = new int[array.length * 2]; 234 } 235 return array; 236 } 237 238 @Override 239 protected void render(GLCanvas canvas) { 240 super.render(canvas); 241 242 if (mRenderer == null) return; 243 mRenderer.prepareDrawing(); 244 245 long animTime = AnimationTime.get(); 246 boolean more = mScroller.advanceAnimation(animTime); 247 more |= mLayout.advanceAnimation(animTime); 248 int oldX = mScrollX; 249 updateScrollPosition(mScroller.getPosition(), false); 250 251 boolean paperActive = false; 252 if (mOverscrollEffect == OVERSCROLL_3D) { 253 // Check if an edge is reached and notify mPaper if so. 254 int newX = mScrollX; 255 int limit = mLayout.getScrollLimit(); 256 if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) { 257 float v = mScroller.getCurrVelocity(); 258 if (newX == limit) v = -v; 259 260 // I don't know why, but getCurrVelocity() can return NaN. 261 if (!Float.isNaN(v)) { 262 mPaper.edgeReached(v); 263 } 264 } 265 paperActive = mPaper.advanceAnimation(); 266 } 267 268 more |= paperActive; 269 270 if (mAnimation != null) { 271 more |= mAnimation.calculate(animTime); 272 } 273 274 canvas.translate(-mScrollX, -mScrollY); 275 276 int requestCount = 0; 277 int requestedSlot[] = expandIntArray(mRequestRenderSlots, 278 mLayout.mVisibleEnd - mLayout.mVisibleStart); 279 280 for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) { 281 int r = renderItem(canvas, i, 0, paperActive); 282 if ((r & RENDER_MORE_FRAME) != 0) more = true; 283 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i; 284 } 285 286 for (int pass = 1; requestCount != 0; ++pass) { 287 int newCount = 0; 288 for (int i = 0; i < requestCount; ++i) { 289 int r = renderItem(canvas, 290 requestedSlot[i], pass, paperActive); 291 if ((r & RENDER_MORE_FRAME) != 0) more = true; 292 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i; 293 } 294 requestCount = newCount; 295 } 296 297 canvas.translate(mScrollX, mScrollY); 298 299 if (more) invalidate(); 300 301 final UserInteractionListener listener = mUIListener; 302 if (mMoreAnimation && !more && listener != null) { 303 mHandler.post(new Runnable() { 304 @Override 305 public void run() { 306 listener.onUserInteractionEnd(); 307 } 308 }); 309 } 310 mMoreAnimation = more; 311 } 312 313 private int renderItem( 314 GLCanvas canvas, int index, int pass, boolean paperActive) { 315 canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX); 316 Rect rect = mLayout.getSlotRect(index, mTempRect); 317 if (paperActive) { 318 canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0); 319 } else { 320 canvas.translate(rect.left, rect.top, 0); 321 } 322 if (mAnimation != null && mAnimation.isActive()) { 323 mAnimation.apply(canvas, index, rect); 324 } 325 int result = mRenderer.renderSlot( 326 canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top); 327 canvas.restore(); 328 return result; 329 } 330 331 public static abstract class SlotAnimation extends Animation { 332 protected float mProgress = 0; 333 334 public SlotAnimation() { 335 setInterpolator(new DecelerateInterpolator(4)); 336 setDuration(1500); 337 } 338 339 @Override 340 protected void onCalculate(float progress) { 341 mProgress = progress; 342 } 343 344 abstract public void apply(GLCanvas canvas, int slotIndex, Rect target); 345 } 346 347 public static class RisingAnimation extends SlotAnimation { 348 private static final int RISING_DISTANCE = 128; 349 350 @Override 351 public void apply(GLCanvas canvas, int slotIndex, Rect target) { 352 canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress)); 353 } 354 } 355 356 public static class ScatteringAnimation extends SlotAnimation { 357 private int PHOTO_DISTANCE = 1000; 358 private RelativePosition mCenter; 359 360 public ScatteringAnimation(RelativePosition center) { 361 mCenter = center; 362 } 363 364 @Override 365 public void apply(GLCanvas canvas, int slotIndex, Rect target) { 366 canvas.translate( 367 (mCenter.getX() - target.centerX()) * (1 - mProgress), 368 (mCenter.getY() - target.centerY()) * (1 - mProgress), 369 slotIndex * PHOTO_DISTANCE * (1 - mProgress)); 370 canvas.setAlpha(mProgress); 371 } 372 } 373 374 // This Spec class is used to specify the size of each slot in the SlotView. 375 // There are two ways to do it: 376 // 377 // (1) Specify slotWidth and slotHeight: they specify the width and height 378 // of each slot. The number of rows and the gap between slots will be 379 // determined automatically. 380 // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number 381 // of rows in landscape/portrait mode and the gap between slots. The 382 // width and height of each slot is determined automatically. 383 // 384 // The initial value of -1 means they are not specified. 385 public static class Spec { 386 public int slotWidth = -1; 387 public int slotHeight = -1; 388 public int slotHeightAdditional = 0; 389 390 public int rowsLand = -1; 391 public int rowsPort = -1; 392 public int slotGap = -1; 393 } 394 395 public class Layout { 396 397 private int mVisibleStart; 398 private int mVisibleEnd; 399 400 private int mSlotCount; 401 private int mSlotWidth; 402 private int mSlotHeight; 403 private int mSlotGap; 404 405 private Spec mSpec; 406 407 private int mWidth; 408 private int mHeight; 409 410 private int mUnitCount; 411 private int mContentLength; 412 private int mScrollPosition; 413 414 private IntegerAnimation mVerticalPadding = new IntegerAnimation(); 415 private IntegerAnimation mHorizontalPadding = new IntegerAnimation(); 416 417 public void setSlotSpec(Spec spec) { 418 mSpec = spec; 419 } 420 421 public boolean setSlotCount(int slotCount) { 422 if (slotCount == mSlotCount) return false; 423 if (mSlotCount != 0) { 424 mHorizontalPadding.setEnabled(true); 425 mVerticalPadding.setEnabled(true); 426 } 427 mSlotCount = slotCount; 428 int hPadding = mHorizontalPadding.getTarget(); 429 int vPadding = mVerticalPadding.getTarget(); 430 initLayoutParameters(); 431 return vPadding != mVerticalPadding.getTarget() 432 || hPadding != mHorizontalPadding.getTarget(); 433 } 434 435 public Rect getSlotRect(int index, Rect rect) { 436 int col, row; 437 if (WIDE) { 438 col = index / mUnitCount; 439 row = index - col * mUnitCount; 440 } else { 441 row = index / mUnitCount; 442 col = index - row * mUnitCount; 443 } 444 445 int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap); 446 int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap); 447 rect.set(x, y, x + mSlotWidth, y + mSlotHeight); 448 return rect; 449 } 450 451 public int getSlotWidth() { 452 return mSlotWidth; 453 } 454 455 public int getSlotHeight() { 456 return mSlotHeight; 457 } 458 459 // Calculate 460 // (1) mUnitCount: the number of slots we can fit into one column (or row). 461 // (2) mContentLength: the width (or height) we need to display all the 462 // columns (rows). 463 // (3) padding[]: the vertical and horizontal padding we need in order 464 // to put the slots towards to the center of the display. 465 // 466 // The "major" direction is the direction the user can scroll. The other 467 // direction is the "minor" direction. 468 // 469 // The comments inside this method are the description when the major 470 // directon is horizontal (X), and the minor directon is vertical (Y). 471 private void initLayoutParameters( 472 int majorLength, int minorLength, /* The view width and height */ 473 int majorUnitSize, int minorUnitSize, /* The slot width and height */ 474 int[] padding) { 475 int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap); 476 if (unitCount == 0) unitCount = 1; 477 mUnitCount = unitCount; 478 479 // We put extra padding above and below the column. 480 int availableUnits = Math.min(mUnitCount, mSlotCount); 481 int usedMinorLength = availableUnits * minorUnitSize + 482 (availableUnits - 1) * mSlotGap; 483 padding[0] = (minorLength - usedMinorLength) / 2; 484 485 // Then calculate how many columns we need for all slots. 486 int count = ((mSlotCount + mUnitCount - 1) / mUnitCount); 487 mContentLength = count * majorUnitSize + (count - 1) * mSlotGap; 488 489 // If the content length is less then the screen width, put 490 // extra padding in left and right. 491 padding[1] = Math.max(0, (majorLength - mContentLength) / 2); 492 } 493 494 private void initLayoutParameters() { 495 // Initialize mSlotWidth and mSlotHeight from mSpec 496 if (mSpec.slotWidth != -1) { 497 mSlotGap = 0; 498 mSlotWidth = mSpec.slotWidth; 499 mSlotHeight = mSpec.slotHeight; 500 } else { 501 int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort; 502 mSlotGap = mSpec.slotGap; 503 mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows); 504 mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional; 505 } 506 507 if (mRenderer != null) { 508 mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight); 509 } 510 511 int[] padding = new int[2]; 512 if (WIDE) { 513 initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding); 514 mVerticalPadding.startAnimateTo(padding[0]); 515 mHorizontalPadding.startAnimateTo(padding[1]); 516 } else { 517 initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding); 518 mVerticalPadding.startAnimateTo(padding[1]); 519 mHorizontalPadding.startAnimateTo(padding[0]); 520 } 521 updateVisibleSlotRange(); 522 } 523 524 public void setSize(int width, int height) { 525 mWidth = width; 526 mHeight = height; 527 initLayoutParameters(); 528 } 529 530 private void updateVisibleSlotRange() { 531 int position = mScrollPosition; 532 533 if (WIDE) { 534 int startCol = position / (mSlotWidth + mSlotGap); 535 int start = Math.max(0, mUnitCount * startCol); 536 int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) / 537 (mSlotWidth + mSlotGap); 538 int end = Math.min(mSlotCount, mUnitCount * endCol); 539 setVisibleRange(start, end); 540 } else { 541 int startRow = position / (mSlotHeight + mSlotGap); 542 int start = Math.max(0, mUnitCount * startRow); 543 int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) / 544 (mSlotHeight + mSlotGap); 545 int end = Math.min(mSlotCount, mUnitCount * endRow); 546 setVisibleRange(start, end); 547 } 548 } 549 550 public void setScrollPosition(int position) { 551 if (mScrollPosition == position) return; 552 mScrollPosition = position; 553 updateVisibleSlotRange(); 554 } 555 556 private void setVisibleRange(int start, int end) { 557 if (start == mVisibleStart && end == mVisibleEnd) return; 558 if (start < end) { 559 mVisibleStart = start; 560 mVisibleEnd = end; 561 } else { 562 mVisibleStart = mVisibleEnd = 0; 563 } 564 if (mRenderer != null) { 565 mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd); 566 } 567 } 568 569 public int getVisibleStart() { 570 return mVisibleStart; 571 } 572 573 public int getVisibleEnd() { 574 return mVisibleEnd; 575 } 576 577 public int getSlotIndexByPosition(float x, float y) { 578 int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0); 579 int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition); 580 581 absoluteX -= mHorizontalPadding.get(); 582 absoluteY -= mVerticalPadding.get(); 583 584 if (absoluteX < 0 || absoluteY < 0) { 585 return INDEX_NONE; 586 } 587 588 int columnIdx = absoluteX / (mSlotWidth + mSlotGap); 589 int rowIdx = absoluteY / (mSlotHeight + mSlotGap); 590 591 if (!WIDE && columnIdx >= mUnitCount) { 592 return INDEX_NONE; 593 } 594 595 if (WIDE && rowIdx >= mUnitCount) { 596 return INDEX_NONE; 597 } 598 599 if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) { 600 return INDEX_NONE; 601 } 602 603 if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) { 604 return INDEX_NONE; 605 } 606 607 int index = WIDE 608 ? (columnIdx * mUnitCount + rowIdx) 609 : (rowIdx * mUnitCount + columnIdx); 610 611 return index >= mSlotCount ? INDEX_NONE : index; 612 } 613 614 public int getScrollLimit() { 615 int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight; 616 return limit <= 0 ? 0 : limit; 617 } 618 619 public boolean advanceAnimation(long animTime) { 620 // use '|' to make sure both sides will be executed 621 return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime); 622 } 623 } 624 625 private class MyGestureListener implements GestureDetector.OnGestureListener { 626 private boolean isDown; 627 628 // We call the listener's onDown() when our onShowPress() is called and 629 // call the listener's onUp() when we receive any further event. 630 @Override 631 public void onShowPress(MotionEvent e) { 632 GLRoot root = getGLRoot(); 633 root.lockRenderThread(); 634 try { 635 if (isDown) return; 636 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY()); 637 if (index != INDEX_NONE) { 638 isDown = true; 639 mListener.onDown(index); 640 } 641 } finally { 642 root.unlockRenderThread(); 643 } 644 } 645 646 private void cancelDown(boolean byLongPress) { 647 if (!isDown) return; 648 isDown = false; 649 mListener.onUp(byLongPress); 650 } 651 652 @Override 653 public boolean onDown(MotionEvent e) { 654 return false; 655 } 656 657 @Override 658 public boolean onFling(MotionEvent e1, 659 MotionEvent e2, float velocityX, float velocityY) { 660 cancelDown(false); 661 int scrollLimit = mLayout.getScrollLimit(); 662 if (scrollLimit == 0) return false; 663 float velocity = WIDE ? velocityX : velocityY; 664 mScroller.fling((int) -velocity, 0, scrollLimit); 665 if (mUIListener != null) mUIListener.onUserInteractionBegin(); 666 invalidate(); 667 return true; 668 } 669 670 @Override 671 public boolean onScroll(MotionEvent e1, 672 MotionEvent e2, float distanceX, float distanceY) { 673 cancelDown(false); 674 float distance = WIDE ? distanceX : distanceY; 675 int overDistance = mScroller.startScroll( 676 Math.round(distance), 0, mLayout.getScrollLimit()); 677 if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) { 678 mPaper.overScroll(overDistance); 679 } 680 invalidate(); 681 return true; 682 } 683 684 @Override 685 public boolean onSingleTapUp(MotionEvent e) { 686 cancelDown(false); 687 if (mDownInScrolling) return true; 688 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY()); 689 if (index != INDEX_NONE) mListener.onSingleTapUp(index); 690 return true; 691 } 692 693 @Override 694 public void onLongPress(MotionEvent e) { 695 cancelDown(true); 696 if (mDownInScrolling) return; 697 lockRendering(); 698 try { 699 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY()); 700 if (index != INDEX_NONE) mListener.onLongTap(index); 701 } finally { 702 unlockRendering(); 703 } 704 } 705 } 706 707 public void setStartIndex(int index) { 708 mStartIndex = index; 709 } 710 711 // Return true if the layout parameters have been changed 712 public boolean setSlotCount(int slotCount) { 713 boolean changed = mLayout.setSlotCount(slotCount); 714 715 // mStartIndex is applied the first time setSlotCount is called. 716 if (mStartIndex != INDEX_NONE) { 717 setCenterIndex(mStartIndex); 718 mStartIndex = INDEX_NONE; 719 } 720 // Reset the scroll position to avoid scrolling over the updated limit. 721 setScrollPosition(WIDE ? mScrollX : mScrollY); 722 return changed; 723 } 724 725 public int getVisibleStart() { 726 return mLayout.getVisibleStart(); 727 } 728 729 public int getVisibleEnd() { 730 return mLayout.getVisibleEnd(); 731 } 732 733 public int getScrollX() { 734 return mScrollX; 735 } 736 737 public int getScrollY() { 738 return mScrollY; 739 } 740 741 public Rect getSlotRect(int slotIndex, GLView rootPane) { 742 // Get slot rectangle relative to this root pane. 743 Rect offset = new Rect(); 744 rootPane.getBoundsOf(this, offset); 745 Rect r = getSlotRect(slotIndex); 746 r.offset(offset.left - getScrollX(), 747 offset.top - getScrollY()); 748 return r; 749 } 750 751 private static class IntegerAnimation extends Animation { 752 private int mTarget; 753 private int mCurrent = 0; 754 private int mFrom = 0; 755 private boolean mEnabled = false; 756 757 public void setEnabled(boolean enabled) { 758 mEnabled = enabled; 759 } 760 761 public void startAnimateTo(int target) { 762 if (!mEnabled) { 763 mTarget = mCurrent = target; 764 return; 765 } 766 if (target == mTarget) return; 767 768 mFrom = mCurrent; 769 mTarget = target; 770 setDuration(180); 771 start(); 772 } 773 774 public int get() { 775 return mCurrent; 776 } 777 778 public int getTarget() { 779 return mTarget; 780 } 781 782 @Override 783 protected void onCalculate(float progress) { 784 mCurrent = Math.round(mFrom + progress * (mTarget - mFrom)); 785 if (progress == 1f) mEnabled = false; 786 } 787 } 788 } 789