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 com.android.gallery3d.R; 20 import com.android.gallery3d.app.GalleryActivity; 21 import com.android.gallery3d.common.Utils; 22 import com.android.gallery3d.data.Path; 23 import com.android.gallery3d.ui.PositionRepository.Position; 24 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.graphics.Color; 28 import android.graphics.RectF; 29 import android.os.Message; 30 import android.os.SystemClock; 31 import android.view.GestureDetector; 32 import android.view.MotionEvent; 33 import android.view.ScaleGestureDetector; 34 35 public class PhotoView extends GLView { 36 @SuppressWarnings("unused") 37 private static final String TAG = "PhotoView"; 38 39 public static final int INVALID_SIZE = -1; 40 41 private static final int MSG_TRANSITION_COMPLETE = 1; 42 private static final int MSG_SHOW_LOADING = 2; 43 44 private static final long DELAY_SHOW_LOADING = 250; // 250ms; 45 46 private static final int TRANS_NONE = 0; 47 private static final int TRANS_SWITCH_NEXT = 3; 48 private static final int TRANS_SWITCH_PREVIOUS = 4; 49 50 public static final int TRANS_SLIDE_IN_RIGHT = 1; 51 public static final int TRANS_SLIDE_IN_LEFT = 2; 52 public static final int TRANS_OPEN_ANIMATION = 5; 53 54 private static final int LOADING_INIT = 0; 55 private static final int LOADING_TIMEOUT = 1; 56 private static final int LOADING_COMPLETE = 2; 57 private static final int LOADING_FAIL = 3; 58 59 private static final int ENTRY_PREVIOUS = 0; 60 private static final int ENTRY_NEXT = 1; 61 62 private static final int IMAGE_GAP = 96; 63 private static final int SWITCH_THRESHOLD = 256; 64 private static final float SWIPE_THRESHOLD = 300f; 65 66 private static final float DEFAULT_TEXT_SIZE = 20; 67 68 public interface PhotoTapListener { 69 public void onSingleTapUp(int x, int y); 70 } 71 72 // the previous/next image entries 73 private final ScreenNailEntry mScreenNails[] = new ScreenNailEntry[2]; 74 75 private final ScaleGestureDetector mScaleDetector; 76 private final GestureDetector mGestureDetector; 77 private final DownUpDetector mDownUpDetector; 78 79 private PhotoTapListener mPhotoTapListener; 80 81 private final PositionController mPositionController; 82 83 private Model mModel; 84 private StringTexture mLoadingText; 85 private StringTexture mNoThumbnailText; 86 private int mTransitionMode = TRANS_NONE; 87 private final TileImageView mTileView; 88 private EdgeView mEdgeView; 89 private Texture mVideoPlayIcon; 90 91 private boolean mShowVideoPlayIcon; 92 private ProgressSpinner mLoadingSpinner; 93 94 private SynchronizedHandler mHandler; 95 96 private int mLoadingState = LOADING_COMPLETE; 97 98 private int mImageRotation; 99 100 private Path mOpenedItemPath; 101 private GalleryActivity mActivity; 102 103 public PhotoView(GalleryActivity activity) { 104 mActivity = activity; 105 mTileView = new TileImageView(activity); 106 addComponent(mTileView); 107 Context context = activity.getAndroidContext(); 108 mEdgeView = new EdgeView(context); 109 addComponent(mEdgeView); 110 mLoadingSpinner = new ProgressSpinner(context); 111 mLoadingText = StringTexture.newInstance( 112 context.getString(R.string.loading), 113 DEFAULT_TEXT_SIZE, Color.WHITE); 114 mNoThumbnailText = StringTexture.newInstance( 115 context.getString(R.string.no_thumbnail), 116 DEFAULT_TEXT_SIZE, Color.WHITE); 117 118 mHandler = new SynchronizedHandler(activity.getGLRoot()) { 119 @Override 120 public void handleMessage(Message message) { 121 switch (message.what) { 122 case MSG_TRANSITION_COMPLETE: { 123 onTransitionComplete(); 124 break; 125 } 126 case MSG_SHOW_LOADING: { 127 if (mLoadingState == LOADING_INIT) { 128 // We don't need the opening animation 129 mOpenedItemPath = null; 130 131 mLoadingSpinner.startAnimation(); 132 mLoadingState = LOADING_TIMEOUT; 133 invalidate(); 134 } 135 break; 136 } 137 default: throw new AssertionError(message.what); 138 } 139 } 140 }; 141 142 mGestureDetector = new GestureDetector(context, 143 new MyGestureListener(), null, true /* ignoreMultitouch */); 144 mScaleDetector = new ScaleGestureDetector(context, new MyScaleListener()); 145 mDownUpDetector = new DownUpDetector(new MyDownUpListener()); 146 147 for (int i = 0, n = mScreenNails.length; i < n; ++i) { 148 mScreenNails[i] = new ScreenNailEntry(); 149 } 150 151 mPositionController = new PositionController(this, context, mEdgeView); 152 mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play); 153 } 154 155 156 public void setModel(Model model) { 157 if (mModel == model) return; 158 mModel = model; 159 mTileView.setModel(model); 160 if (model != null) notifyOnNewImage(); 161 } 162 163 public void setPhotoTapListener(PhotoTapListener listener) { 164 mPhotoTapListener = listener; 165 } 166 167 private boolean setTileViewPosition(int centerX, int centerY, float scale) { 168 int inverseX = mPositionController.getImageWidth() - centerX; 169 int inverseY = mPositionController.getImageHeight() - centerY; 170 TileImageView t = mTileView; 171 int rotation = mImageRotation; 172 switch (rotation) { 173 case 0: return t.setPosition(centerX, centerY, scale, 0); 174 case 90: return t.setPosition(centerY, inverseX, scale, 90); 175 case 180: return t.setPosition(inverseX, inverseY, scale, 180); 176 case 270: return t.setPosition(inverseY, centerX, scale, 270); 177 default: throw new IllegalArgumentException(String.valueOf(rotation)); 178 } 179 } 180 181 public void setPosition(int centerX, int centerY, float scale) { 182 if (setTileViewPosition(centerX, centerY, scale)) { 183 layoutScreenNails(); 184 } 185 } 186 187 private void updateScreenNailEntry(int which, ImageData data) { 188 if (mTransitionMode == TRANS_SWITCH_NEXT 189 || mTransitionMode == TRANS_SWITCH_PREVIOUS) { 190 // ignore screen nail updating during switching 191 return; 192 } 193 ScreenNailEntry entry = mScreenNails[which]; 194 if (data == null) { 195 entry.set(false, null, 0); 196 } else { 197 entry.set(true, data.bitmap, data.rotation); 198 } 199 } 200 201 // -1 previous, 0 current, 1 next 202 public void notifyImageInvalidated(int which) { 203 switch (which) { 204 case -1: { 205 updateScreenNailEntry( 206 ENTRY_PREVIOUS, mModel.getPreviousImage()); 207 layoutScreenNails(); 208 invalidate(); 209 break; 210 } 211 case 1: { 212 updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage()); 213 layoutScreenNails(); 214 invalidate(); 215 break; 216 } 217 case 0: { 218 // mImageWidth and mImageHeight will get updated 219 mTileView.notifyModelInvalidated(); 220 221 mImageRotation = mModel.getImageRotation(); 222 if (((mImageRotation / 90) & 1) == 0) { 223 mPositionController.setImageSize( 224 mTileView.mImageWidth, mTileView.mImageHeight); 225 } else { 226 mPositionController.setImageSize( 227 mTileView.mImageHeight, mTileView.mImageWidth); 228 } 229 updateLoadingState(); 230 break; 231 } 232 } 233 } 234 235 private void updateLoadingState() { 236 // Possible transitions of mLoadingState: 237 // INIT --> TIMEOUT, COMPLETE, FAIL 238 // TIMEOUT --> COMPLETE, FAIL, INIT 239 // COMPLETE --> INIT 240 // FAIL --> INIT 241 if (mModel.getLevelCount() != 0 || mModel.getBackupImage() != null) { 242 mHandler.removeMessages(MSG_SHOW_LOADING); 243 mLoadingState = LOADING_COMPLETE; 244 } else if (mModel.isFailedToLoad()) { 245 mHandler.removeMessages(MSG_SHOW_LOADING); 246 mLoadingState = LOADING_FAIL; 247 } else if (mLoadingState != LOADING_INIT) { 248 mLoadingState = LOADING_INIT; 249 mHandler.removeMessages(MSG_SHOW_LOADING); 250 mHandler.sendEmptyMessageDelayed( 251 MSG_SHOW_LOADING, DELAY_SHOW_LOADING); 252 } 253 } 254 255 public void notifyModelInvalidated() { 256 if (mModel == null) { 257 updateScreenNailEntry(ENTRY_PREVIOUS, null); 258 updateScreenNailEntry(ENTRY_NEXT, null); 259 } else { 260 updateScreenNailEntry(ENTRY_PREVIOUS, mModel.getPreviousImage()); 261 updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage()); 262 } 263 layoutScreenNails(); 264 265 if (mModel == null) { 266 mTileView.notifyModelInvalidated(); 267 mImageRotation = 0; 268 mPositionController.setImageSize(0, 0); 269 updateLoadingState(); 270 } else { 271 notifyImageInvalidated(0); 272 } 273 } 274 275 @Override 276 protected boolean onTouch(MotionEvent event) { 277 mGestureDetector.onTouchEvent(event); 278 mScaleDetector.onTouchEvent(event); 279 mDownUpDetector.onTouchEvent(event); 280 return true; 281 } 282 283 @Override 284 protected void onLayout( 285 boolean changeSize, int left, int top, int right, int bottom) { 286 mTileView.layout(left, top, right, bottom); 287 mEdgeView.layout(left, top, right, bottom); 288 if (changeSize) { 289 mPositionController.setViewSize(getWidth(), getHeight()); 290 for (ScreenNailEntry entry : mScreenNails) { 291 entry.updateDrawingSize(); 292 } 293 } 294 } 295 296 private static int gapToSide(int imageWidth, int viewWidth) { 297 return Math.max(0, (viewWidth - imageWidth) / 2); 298 } 299 300 /* 301 * Here is how we layout the screen nails 302 * 303 * previous current next 304 * ___________ ________________ __________ 305 * | _______ | | __________ | | ______ | 306 * | | | | | | right->| | | | | | 307 * | | |<-------->|<--left | | | | | | 308 * | |_______| | | | |__________| | | |______| | 309 * |___________| | |________________| |__________| 310 * | <--> gapToSide() 311 * | 312 * IMAGE_GAP + Max(previous.gapToSide(), current.gapToSide) 313 */ 314 private void layoutScreenNails() { 315 int width = getWidth(); 316 int height = getHeight(); 317 318 // Use the image width in AC, since we may fake the size if the 319 // image is unavailable 320 RectF bounds = mPositionController.getImageBounds(); 321 int left = Math.round(bounds.left); 322 int right = Math.round(bounds.right); 323 int gap = gapToSide(right - left, width); 324 325 // layout the previous image 326 ScreenNailEntry entry = mScreenNails[ENTRY_PREVIOUS]; 327 328 if (entry.isEnabled()) { 329 entry.layoutRightEdgeAt(left - ( 330 IMAGE_GAP + Math.max(gap, entry.gapToSide()))); 331 } 332 333 // layout the next image 334 entry = mScreenNails[ENTRY_NEXT]; 335 if (entry.isEnabled()) { 336 entry.layoutLeftEdgeAt(right + ( 337 IMAGE_GAP + Math.max(gap, entry.gapToSide()))); 338 } 339 } 340 341 @Override 342 protected void render(GLCanvas canvas) { 343 PositionController p = mPositionController; 344 345 // Draw the current photo 346 if (mLoadingState == LOADING_COMPLETE) { 347 super.render(canvas); 348 } 349 350 // Draw the previous and the next photo 351 if (mTransitionMode != TRANS_SLIDE_IN_LEFT 352 && mTransitionMode != TRANS_SLIDE_IN_RIGHT 353 && mTransitionMode != TRANS_OPEN_ANIMATION) { 354 ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; 355 ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; 356 357 if (prevNail.mVisible) prevNail.draw(canvas); 358 if (nextNail.mVisible) nextNail.draw(canvas); 359 } 360 361 // Draw the progress spinner and the text below it 362 // 363 // (x, y) is where we put the center of the spinner. 364 // s is the size of the video play icon, and we use s to layout text 365 // because we want to keep the text at the same place when the video 366 // play icon is shown instead of the spinner. 367 int w = getWidth(); 368 int h = getHeight(); 369 int x = Math.round(mPositionController.getImageBounds().centerX()); 370 int y = h / 2; 371 int s = Math.min(getWidth(), getHeight()) / 6; 372 373 if (mLoadingState == LOADING_TIMEOUT) { 374 StringTexture m = mLoadingText; 375 ProgressSpinner r = mLoadingSpinner; 376 r.draw(canvas, x - r.getWidth() / 2, y - r.getHeight() / 2); 377 m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5); 378 invalidate(); // we need to keep the spinner rotating 379 } else if (mLoadingState == LOADING_FAIL) { 380 StringTexture m = mNoThumbnailText; 381 m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5); 382 } 383 384 // Draw the video play icon (in the place where the spinner was) 385 if (mShowVideoPlayIcon 386 && mLoadingState != LOADING_INIT 387 && mLoadingState != LOADING_TIMEOUT) { 388 mVideoPlayIcon.draw(canvas, x - s / 2, y - s / 2, s, s); 389 } 390 391 if (mPositionController.advanceAnimation()) invalidate(); 392 } 393 394 private void stopCurrentSwipingIfNeeded() { 395 // Enable fast sweeping 396 if (mTransitionMode == TRANS_SWITCH_NEXT) { 397 mTransitionMode = TRANS_NONE; 398 mPositionController.stopAnimation(); 399 switchToNextImage(); 400 } else if (mTransitionMode == TRANS_SWITCH_PREVIOUS) { 401 mTransitionMode = TRANS_NONE; 402 mPositionController.stopAnimation(); 403 switchToPreviousImage(); 404 } 405 } 406 407 private boolean swipeImages(float velocity) { 408 if (mTransitionMode != TRANS_NONE 409 && mTransitionMode != TRANS_SWITCH_NEXT 410 && mTransitionMode != TRANS_SWITCH_PREVIOUS) return false; 411 412 ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; 413 ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; 414 415 int width = getWidth(); 416 417 // If we are at the edge of the current photo and the sweeping velocity 418 // exceeds the threshold, switch to next / previous image. 419 PositionController controller = mPositionController; 420 boolean isMinimal = controller.isAtMinimalScale(); 421 422 if (velocity < -SWIPE_THRESHOLD && 423 (isMinimal || controller.isAtRightEdge())) { 424 stopCurrentSwipingIfNeeded(); 425 if (next.isEnabled()) { 426 mTransitionMode = TRANS_SWITCH_NEXT; 427 controller.startHorizontalSlide(next.mOffsetX - width / 2); 428 return true; 429 } 430 } else if (velocity > SWIPE_THRESHOLD && 431 (isMinimal || controller.isAtLeftEdge())) { 432 stopCurrentSwipingIfNeeded(); 433 if (prev.isEnabled()) { 434 mTransitionMode = TRANS_SWITCH_PREVIOUS; 435 controller.startHorizontalSlide(prev.mOffsetX - width / 2); 436 return true; 437 } 438 } 439 440 return false; 441 } 442 443 public boolean snapToNeighborImage() { 444 if (mTransitionMode != TRANS_NONE) return false; 445 446 ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; 447 ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; 448 449 int width = getWidth(); 450 PositionController controller = mPositionController; 451 452 RectF bounds = controller.getImageBounds(); 453 int left = Math.round(bounds.left); 454 int right = Math.round(bounds.right); 455 int threshold = SWITCH_THRESHOLD + gapToSide(right - left, width); 456 457 // If we have moved the picture a lot, switching. 458 if (next.isEnabled() && threshold < width - right) { 459 mTransitionMode = TRANS_SWITCH_NEXT; 460 controller.startHorizontalSlide(next.mOffsetX - width / 2); 461 return true; 462 } 463 if (prev.isEnabled() && threshold < left) { 464 mTransitionMode = TRANS_SWITCH_PREVIOUS; 465 controller.startHorizontalSlide(prev.mOffsetX - width / 2); 466 return true; 467 } 468 469 return false; 470 } 471 472 private boolean mIgnoreUpEvent = false; 473 474 private class MyGestureListener 475 extends GestureDetector.SimpleOnGestureListener { 476 @Override 477 public boolean onScroll( 478 MotionEvent e1, MotionEvent e2, float dx, float dy) { 479 if (mTransitionMode != TRANS_NONE) return true; 480 481 ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; 482 ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; 483 484 mPositionController.startScroll(dx, dy, next.isEnabled(), 485 prev.isEnabled()); 486 return true; 487 } 488 489 @Override 490 public boolean onSingleTapUp(MotionEvent e) { 491 if (mPhotoTapListener != null) { 492 mPhotoTapListener.onSingleTapUp((int) e.getX(), (int) e.getY()); 493 } 494 return true; 495 } 496 497 @Override 498 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 499 float velocityY) { 500 if (swipeImages(velocityX)) { 501 mIgnoreUpEvent = true; 502 } else if (mTransitionMode != TRANS_NONE) { 503 // do nothing 504 } else if (mPositionController.fling(velocityX, velocityY)) { 505 mIgnoreUpEvent = true; 506 } 507 return true; 508 } 509 510 @Override 511 public boolean onDoubleTap(MotionEvent e) { 512 if (mTransitionMode != TRANS_NONE) return true; 513 PositionController controller = mPositionController; 514 float scale = controller.getCurrentScale(); 515 // onDoubleTap happened on the second ACTION_DOWN. 516 // We need to ignore the next UP event. 517 mIgnoreUpEvent = true; 518 if (scale <= 1.0f || controller.isAtMinimalScale()) { 519 controller.zoomIn( 520 e.getX(), e.getY(), Math.max(1.5f, scale * 1.5f)); 521 } else { 522 controller.resetToFullView(); 523 } 524 return true; 525 } 526 } 527 528 private class MyScaleListener 529 extends ScaleGestureDetector.SimpleOnScaleGestureListener { 530 531 @Override 532 public boolean onScale(ScaleGestureDetector detector) { 533 float scale = detector.getScaleFactor(); 534 if (Float.isNaN(scale) || Float.isInfinite(scale) 535 || mTransitionMode != TRANS_NONE) return true; 536 mPositionController.scaleBy(scale, 537 detector.getFocusX(), detector.getFocusY()); 538 return true; 539 } 540 541 @Override 542 public boolean onScaleBegin(ScaleGestureDetector detector) { 543 if (mTransitionMode != TRANS_NONE) return false; 544 mPositionController.beginScale( 545 detector.getFocusX(), detector.getFocusY()); 546 return true; 547 } 548 549 @Override 550 public void onScaleEnd(ScaleGestureDetector detector) { 551 mPositionController.endScale(); 552 snapToNeighborImage(); 553 } 554 } 555 556 public boolean jumpTo(int index) { 557 if (mTransitionMode != TRANS_NONE) return false; 558 mModel.jumpTo(index); 559 return true; 560 } 561 562 public void notifyOnNewImage() { 563 mPositionController.setImageSize(0, 0); 564 } 565 566 public void startSlideInAnimation(int direction) { 567 PositionController a = mPositionController; 568 a.stopAnimation(); 569 switch (direction) { 570 case TRANS_SLIDE_IN_LEFT: 571 case TRANS_SLIDE_IN_RIGHT: { 572 mTransitionMode = direction; 573 a.startSlideInAnimation(direction); 574 break; 575 } 576 default: throw new IllegalArgumentException(String.valueOf(direction)); 577 } 578 } 579 580 private class MyDownUpListener implements DownUpDetector.DownUpListener { 581 public void onDown(MotionEvent e) { 582 } 583 584 public void onUp(MotionEvent e) { 585 mEdgeView.onRelease(); 586 587 if (mIgnoreUpEvent) { 588 mIgnoreUpEvent = false; 589 return; 590 } 591 if (!snapToNeighborImage() && mTransitionMode == TRANS_NONE) { 592 mPositionController.up(); 593 } 594 } 595 } 596 597 private void switchToNextImage() { 598 // We update the texture here directly to prevent texture uploading. 599 ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; 600 ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; 601 mTileView.invalidateTiles(); 602 if (prevNail.mTexture != null) prevNail.mTexture.recycle(); 603 prevNail.mTexture = mTileView.mBackupImage; 604 mTileView.mBackupImage = nextNail.mTexture; 605 nextNail.mTexture = null; 606 mModel.next(); 607 } 608 609 private void switchToPreviousImage() { 610 // We update the texture here directly to prevent texture uploading. 611 ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; 612 ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; 613 mTileView.invalidateTiles(); 614 if (nextNail.mTexture != null) nextNail.mTexture.recycle(); 615 nextNail.mTexture = mTileView.mBackupImage; 616 mTileView.mBackupImage = prevNail.mTexture; 617 nextNail.mTexture = null; 618 mModel.previous(); 619 } 620 621 public void notifyTransitionComplete() { 622 mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE); 623 } 624 625 private void onTransitionComplete() { 626 int mode = mTransitionMode; 627 mTransitionMode = TRANS_NONE; 628 629 if (mModel == null) return; 630 if (mode == TRANS_SWITCH_NEXT) { 631 switchToNextImage(); 632 } else if (mode == TRANS_SWITCH_PREVIOUS) { 633 switchToPreviousImage(); 634 } 635 } 636 637 public boolean isDown() { 638 return mDownUpDetector.isDown(); 639 } 640 641 public static interface Model extends TileImageView.Model { 642 public void next(); 643 public void previous(); 644 public void jumpTo(int index); 645 public int getImageRotation(); 646 647 // Return null if the specified image is unavailable. 648 public ImageData getNextImage(); 649 public ImageData getPreviousImage(); 650 } 651 652 public static class ImageData { 653 public int rotation; 654 public Bitmap bitmap; 655 656 public ImageData(Bitmap bitmap, int rotation) { 657 this.bitmap = bitmap; 658 this.rotation = rotation; 659 } 660 } 661 662 private static int getRotated(int degree, int original, int theother) { 663 return ((degree / 90) & 1) == 0 ? original : theother; 664 } 665 666 private class ScreenNailEntry { 667 private boolean mVisible; 668 private boolean mEnabled; 669 670 private int mRotation; 671 private int mDrawWidth; 672 private int mDrawHeight; 673 private int mOffsetX; 674 675 private BitmapTexture mTexture; 676 677 public void set(boolean enabled, Bitmap bitmap, int rotation) { 678 mEnabled = enabled; 679 mRotation = rotation; 680 if (bitmap == null) { 681 if (mTexture != null) mTexture.recycle(); 682 mTexture = null; 683 } else { 684 if (mTexture != null) { 685 if (mTexture.getBitmap() != bitmap) { 686 mTexture.recycle(); 687 mTexture = new BitmapTexture(bitmap); 688 } 689 } else { 690 mTexture = new BitmapTexture(bitmap); 691 } 692 updateDrawingSize(); 693 } 694 } 695 696 public void layoutRightEdgeAt(int x) { 697 mVisible = x > 0; 698 mOffsetX = x - getRotated( 699 mRotation, mDrawWidth, mDrawHeight) / 2; 700 } 701 702 public void layoutLeftEdgeAt(int x) { 703 mVisible = x < getWidth(); 704 mOffsetX = x + getRotated( 705 mRotation, mDrawWidth, mDrawHeight) / 2; 706 } 707 708 public int gapToSide() { 709 return ((mRotation / 90) & 1) != 0 710 ? PhotoView.gapToSide(mDrawHeight, getWidth()) 711 : PhotoView.gapToSide(mDrawWidth, getWidth()); 712 } 713 714 public void updateDrawingSize() { 715 if (mTexture == null) return; 716 717 int width = mTexture.getWidth(); 718 int height = mTexture.getHeight(); 719 720 // Calculate the initial scale that will used by PositionController 721 // (usually fit-to-screen) 722 float s = ((mRotation / 90) & 0x01) == 0 723 ? mPositionController.getMinimalScale(width, height) 724 : mPositionController.getMinimalScale(height, width); 725 726 mDrawWidth = Math.round(width * s); 727 mDrawHeight = Math.round(height * s); 728 } 729 730 public boolean isEnabled() { 731 return mEnabled; 732 } 733 734 public void draw(GLCanvas canvas) { 735 int x = mOffsetX; 736 int y = getHeight() / 2; 737 738 if (mTexture != null) { 739 if (mRotation != 0) { 740 canvas.save(GLCanvas.SAVE_FLAG_MATRIX); 741 canvas.translate(x, y, 0); 742 canvas.rotate(mRotation, 0, 0, 1); //mRotation 743 canvas.translate(-x, -y, 0); 744 } 745 mTexture.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2, 746 mDrawWidth, mDrawHeight); 747 if (mRotation != 0) { 748 canvas.restore(); 749 } 750 } 751 } 752 } 753 754 public void pause() { 755 mPositionController.skipAnimation(); 756 mTransitionMode = TRANS_NONE; 757 mTileView.freeTextures(); 758 for (ScreenNailEntry entry : mScreenNails) { 759 entry.set(false, null, 0); 760 } 761 } 762 763 public void resume() { 764 mTileView.prepareTextures(); 765 } 766 767 public void setOpenedItem(Path itemPath) { 768 mOpenedItemPath = itemPath; 769 } 770 771 public void showVideoPlayIcon(boolean show) { 772 mShowVideoPlayIcon = show; 773 } 774 775 // Returns the position saved by the previous page. 776 public Position retrieveSavedPosition() { 777 if (mOpenedItemPath != null) { 778 Position position = PositionRepository 779 .getInstance(mActivity).get(Long.valueOf( 780 System.identityHashCode(mOpenedItemPath))); 781 mOpenedItemPath = null; 782 return position; 783 } 784 return null; 785 } 786 787 public void openAnimationStarted() { 788 mTransitionMode = TRANS_OPEN_ANIMATION; 789 } 790 791 public boolean isInTransition() { 792 return mTransitionMode != TRANS_NONE; 793 } 794 } 795