Home | History | Annotate | Download | only in ui
      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