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