PhotoView.java revision a7b78e224b1808895ea2c3d42ae385526dea12aa
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.data.Path; 22import com.android.gallery3d.ui.PositionRepository.Position; 23 24import android.content.Context; 25import android.graphics.Bitmap; 26import android.graphics.Color; 27import android.graphics.RectF; 28import android.os.Message; 29import android.view.GestureDetector; 30import android.view.MotionEvent; 31import android.view.ScaleGestureDetector; 32 33public class PhotoView extends GLView { 34 @SuppressWarnings("unused") 35 private static final String TAG = "PhotoView"; 36 37 public static final int INVALID_SIZE = -1; 38 39 private static final int MSG_TRANSITION_COMPLETE = 1; 40 private static final int MSG_SHOW_LOADING = 2; 41 42 private static final long DELAY_SHOW_LOADING = 250; // 250ms; 43 44 private static final int TRANS_NONE = 0; 45 private static final int TRANS_SWITCH_NEXT = 3; 46 private static final int TRANS_SWITCH_PREVIOUS = 4; 47 48 public static final int TRANS_SLIDE_IN_RIGHT = 1; 49 public static final int TRANS_SLIDE_IN_LEFT = 2; 50 public static final int TRANS_OPEN_ANIMATION = 5; 51 52 private static final int LOADING_INIT = 0; 53 private static final int LOADING_TIMEOUT = 1; 54 private static final int LOADING_COMPLETE = 2; 55 private static final int LOADING_FAIL = 3; 56 57 private static final int ENTRY_PREVIOUS = 0; 58 private static final int ENTRY_NEXT = 1; 59 60 private static final int IMAGE_GAP = 96; 61 private static final int SWITCH_THRESHOLD = 256; 62 private static final float SWIPE_THRESHOLD = 300f; 63 64 private static final float DEFAULT_TEXT_SIZE = 20; 65 66 public interface PhotoTapListener { 67 public void onSingleTapUp(int x, int y); 68 } 69 70 // the previous/next image entries 71 private final ScreenNailEntry mScreenNails[] = new ScreenNailEntry[2]; 72 73 private final ScaleGestureDetector mScaleDetector; 74 private final GestureDetector mGestureDetector; 75 private final DownUpDetector mDownUpDetector; 76 77 private PhotoTapListener mPhotoTapListener; 78 79 private final PositionController mPositionController; 80 81 private Model mModel; 82 private StringTexture mLoadingText; 83 private StringTexture mNoThumbnailText; 84 private int mTransitionMode = TRANS_NONE; 85 private final TileImageView mTileView; 86 private EdgeView mEdgeView; 87 private Texture mVideoPlayIcon; 88 89 private boolean mShowVideoPlayIcon; 90 private ProgressSpinner mLoadingSpinner; 91 92 private SynchronizedHandler mHandler; 93 94 private int mLoadingState = LOADING_COMPLETE; 95 96 private int mImageRotation; 97 98 private Path mOpenedItemPath; 99 private GalleryActivity mActivity; 100 101 public PhotoView(GalleryActivity activity) { 102 mActivity = activity; 103 mTileView = new TileImageView(activity); 104 addComponent(mTileView); 105 Context context = activity.getAndroidContext(); 106 mEdgeView = new EdgeView(context); 107 addComponent(mEdgeView); 108 mLoadingSpinner = new ProgressSpinner(context); 109 mLoadingText = StringTexture.newInstance( 110 context.getString(R.string.loading), 111 DEFAULT_TEXT_SIZE, Color.WHITE); 112 mNoThumbnailText = StringTexture.newInstance( 113 context.getString(R.string.no_thumbnail), 114 DEFAULT_TEXT_SIZE, Color.WHITE); 115 116 mHandler = new SynchronizedHandler(activity.getGLRoot()) { 117 @Override 118 public void handleMessage(Message message) { 119 switch (message.what) { 120 case MSG_TRANSITION_COMPLETE: { 121 onTransitionComplete(); 122 break; 123 } 124 case MSG_SHOW_LOADING: { 125 if (mLoadingState == LOADING_INIT) { 126 // We don't need the opening animation 127 mOpenedItemPath = null; 128 129 mLoadingSpinner.startAnimation(); 130 mLoadingState = LOADING_TIMEOUT; 131 invalidate(); 132 } 133 break; 134 } 135 default: throw new AssertionError(message.what); 136 } 137 } 138 }; 139 140 mGestureDetector = new GestureDetector(context, 141 new MyGestureListener(), null, true /* ignoreMultitouch */); 142 mScaleDetector = new ScaleGestureDetector(context, new MyScaleListener()); 143 mDownUpDetector = new DownUpDetector(new MyDownUpListener()); 144 145 for (int i = 0, n = mScreenNails.length; i < n; ++i) { 146 mScreenNails[i] = new ScreenNailEntry(); 147 } 148 149 mPositionController = new PositionController(this, context, mEdgeView); 150 mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play); 151 } 152 153 154 public void setModel(Model model) { 155 if (mModel == model) return; 156 mModel = model; 157 mTileView.setModel(model); 158 if (model != null) notifyOnNewImage(); 159 } 160 161 public void setPhotoTapListener(PhotoTapListener listener) { 162 mPhotoTapListener = listener; 163 } 164 165 private boolean setTileViewPosition(int centerX, int centerY, float scale) { 166 int inverseX = mPositionController.getImageWidth() - centerX; 167 int inverseY = mPositionController.getImageHeight() - centerY; 168 TileImageView t = mTileView; 169 int rotation = mImageRotation; 170 switch (rotation) { 171 case 0: return t.setPosition(centerX, centerY, scale, 0); 172 case 90: return t.setPosition(centerY, inverseX, scale, 90); 173 case 180: return t.setPosition(inverseX, inverseY, scale, 180); 174 case 270: return t.setPosition(inverseY, centerX, scale, 270); 175 default: throw new IllegalArgumentException(String.valueOf(rotation)); 176 } 177 } 178 179 public void setPosition(int centerX, int centerY, float scale) { 180 if (setTileViewPosition(centerX, centerY, scale)) { 181 layoutScreenNails(); 182 } 183 } 184 185 private void updateScreenNailEntry(int which, ImageData data) { 186 if (mTransitionMode == TRANS_SWITCH_NEXT 187 || mTransitionMode == TRANS_SWITCH_PREVIOUS) { 188 // ignore screen nail updating during switching 189 return; 190 } 191 ScreenNailEntry entry = mScreenNails[which]; 192 if (data == null) { 193 entry.set(false, null, 0); 194 } else { 195 entry.set(true, data.bitmap, data.rotation); 196 } 197 } 198 199 // -1 previous, 0 current, 1 next 200 public void notifyImageInvalidated(int which) { 201 switch (which) { 202 case -1: { 203 updateScreenNailEntry( 204 ENTRY_PREVIOUS, mModel.getPreviousImage()); 205 layoutScreenNails(); 206 invalidate(); 207 break; 208 } 209 case 1: { 210 updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage()); 211 layoutScreenNails(); 212 invalidate(); 213 break; 214 } 215 case 0: { 216 // mImageWidth and mImageHeight will get updated 217 mTileView.notifyModelInvalidated(); 218 219 mImageRotation = mModel.getImageRotation(); 220 if (((mImageRotation / 90) & 1) == 0) { 221 mPositionController.setImageSize( 222 mTileView.mImageWidth, mTileView.mImageHeight); 223 } else { 224 mPositionController.setImageSize( 225 mTileView.mImageHeight, mTileView.mImageWidth); 226 } 227 updateLoadingState(); 228 break; 229 } 230 } 231 } 232 233 private void updateLoadingState() { 234 // Possible transitions of mLoadingState: 235 // INIT --> TIMEOUT, COMPLETE, FAIL 236 // TIMEOUT --> COMPLETE, FAIL, INIT 237 // COMPLETE --> INIT 238 // FAIL --> INIT 239 if (mModel.getLevelCount() != 0 || mModel.getBackupImage() != null) { 240 mHandler.removeMessages(MSG_SHOW_LOADING); 241 mLoadingState = LOADING_COMPLETE; 242 } else if (mModel.isFailedToLoad()) { 243 mHandler.removeMessages(MSG_SHOW_LOADING); 244 mLoadingState = LOADING_FAIL; 245 // We don't want the opening animation after loading failure 246 mOpenedItemPath = null; 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