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