| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.gallery3d.ui; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.app.GalleryActivity; |
| import com.android.gallery3d.common.Utils; |
| import com.android.gallery3d.data.Path; |
| import com.android.gallery3d.ui.PositionRepository.Position; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.graphics.RectF; |
| import android.os.Message; |
| import android.os.SystemClock; |
| import android.view.GestureDetector; |
| import android.view.MotionEvent; |
| import android.view.ScaleGestureDetector; |
| |
| public class PhotoView extends GLView { |
| @SuppressWarnings("unused") |
| private static final String TAG = "PhotoView"; |
| |
| public static final int INVALID_SIZE = -1; |
| |
| private static final int MSG_TRANSITION_COMPLETE = 1; |
| private static final int MSG_SHOW_LOADING = 2; |
| |
| private static final long DELAY_SHOW_LOADING = 250; // 250ms; |
| |
| private static final int TRANS_NONE = 0; |
| private static final int TRANS_SWITCH_NEXT = 3; |
| private static final int TRANS_SWITCH_PREVIOUS = 4; |
| |
| public static final int TRANS_SLIDE_IN_RIGHT = 1; |
| public static final int TRANS_SLIDE_IN_LEFT = 2; |
| public static final int TRANS_OPEN_ANIMATION = 5; |
| |
| private static final int LOADING_INIT = 0; |
| private static final int LOADING_TIMEOUT = 1; |
| private static final int LOADING_COMPLETE = 2; |
| private static final int LOADING_FAIL = 3; |
| |
| private static final int ENTRY_PREVIOUS = 0; |
| private static final int ENTRY_NEXT = 1; |
| |
| private static final int IMAGE_GAP = 96; |
| private static final int SWITCH_THRESHOLD = 256; |
| private static final float SWIPE_THRESHOLD = 300f; |
| |
| private static final float DEFAULT_TEXT_SIZE = 20; |
| |
| public interface PhotoTapListener { |
| public void onSingleTapUp(int x, int y); |
| } |
| |
| // the previous/next image entries |
| private final ScreenNailEntry mScreenNails[] = new ScreenNailEntry[2]; |
| |
| private final ScaleGestureDetector mScaleDetector; |
| private final GestureDetector mGestureDetector; |
| private final DownUpDetector mDownUpDetector; |
| |
| private PhotoTapListener mPhotoTapListener; |
| |
| private final PositionController mPositionController; |
| |
| private Model mModel; |
| private StringTexture mLoadingText; |
| private StringTexture mNoThumbnailText; |
| private int mTransitionMode = TRANS_NONE; |
| private final TileImageView mTileView; |
| private EdgeView mEdgeView; |
| private Texture mVideoPlayIcon; |
| |
| private boolean mShowVideoPlayIcon; |
| private ProgressSpinner mLoadingSpinner; |
| |
| private SynchronizedHandler mHandler; |
| |
| private int mLoadingState = LOADING_COMPLETE; |
| |
| private int mImageRotation; |
| |
| private Path mOpenedItemPath; |
| private GalleryActivity mActivity; |
| |
| public PhotoView(GalleryActivity activity) { |
| mActivity = activity; |
| mTileView = new TileImageView(activity); |
| addComponent(mTileView); |
| Context context = activity.getAndroidContext(); |
| mEdgeView = new EdgeView(context); |
| addComponent(mEdgeView); |
| mLoadingSpinner = new ProgressSpinner(context); |
| mLoadingText = StringTexture.newInstance( |
| context.getString(R.string.loading), |
| DEFAULT_TEXT_SIZE, Color.WHITE); |
| mNoThumbnailText = StringTexture.newInstance( |
| context.getString(R.string.no_thumbnail), |
| DEFAULT_TEXT_SIZE, Color.WHITE); |
| |
| mHandler = new SynchronizedHandler(activity.getGLRoot()) { |
| @Override |
| public void handleMessage(Message message) { |
| switch (message.what) { |
| case MSG_TRANSITION_COMPLETE: { |
| onTransitionComplete(); |
| break; |
| } |
| case MSG_SHOW_LOADING: { |
| if (mLoadingState == LOADING_INIT) { |
| // We don't need the opening animation |
| mOpenedItemPath = null; |
| |
| mLoadingSpinner.startAnimation(); |
| mLoadingState = LOADING_TIMEOUT; |
| invalidate(); |
| } |
| break; |
| } |
| default: throw new AssertionError(message.what); |
| } |
| } |
| }; |
| |
| mGestureDetector = new GestureDetector(context, |
| new MyGestureListener(), null, true /* ignoreMultitouch */); |
| mScaleDetector = new ScaleGestureDetector(context, new MyScaleListener()); |
| mDownUpDetector = new DownUpDetector(new MyDownUpListener()); |
| |
| for (int i = 0, n = mScreenNails.length; i < n; ++i) { |
| mScreenNails[i] = new ScreenNailEntry(); |
| } |
| |
| mPositionController = new PositionController(this, context, mEdgeView); |
| mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play); |
| } |
| |
| |
| public void setModel(Model model) { |
| if (mModel == model) return; |
| mModel = model; |
| mTileView.setModel(model); |
| if (model != null) notifyOnNewImage(); |
| } |
| |
| public void setPhotoTapListener(PhotoTapListener listener) { |
| mPhotoTapListener = listener; |
| } |
| |
| private boolean setTileViewPosition(int centerX, int centerY, float scale) { |
| int inverseX = mPositionController.getImageWidth() - centerX; |
| int inverseY = mPositionController.getImageHeight() - centerY; |
| TileImageView t = mTileView; |
| int rotation = mImageRotation; |
| switch (rotation) { |
| case 0: return t.setPosition(centerX, centerY, scale, 0); |
| case 90: return t.setPosition(centerY, inverseX, scale, 90); |
| case 180: return t.setPosition(inverseX, inverseY, scale, 180); |
| case 270: return t.setPosition(inverseY, centerX, scale, 270); |
| default: throw new IllegalArgumentException(String.valueOf(rotation)); |
| } |
| } |
| |
| public void setPosition(int centerX, int centerY, float scale) { |
| if (setTileViewPosition(centerX, centerY, scale)) { |
| layoutScreenNails(); |
| } |
| } |
| |
| private void updateScreenNailEntry(int which, ImageData data) { |
| if (mTransitionMode == TRANS_SWITCH_NEXT |
| || mTransitionMode == TRANS_SWITCH_PREVIOUS) { |
| // ignore screen nail updating during switching |
| return; |
| } |
| ScreenNailEntry entry = mScreenNails[which]; |
| if (data == null) { |
| entry.set(false, null, 0); |
| } else { |
| entry.set(true, data.bitmap, data.rotation); |
| } |
| } |
| |
| // -1 previous, 0 current, 1 next |
| public void notifyImageInvalidated(int which) { |
| switch (which) { |
| case -1: { |
| updateScreenNailEntry( |
| ENTRY_PREVIOUS, mModel.getPreviousImage()); |
| layoutScreenNails(); |
| invalidate(); |
| break; |
| } |
| case 1: { |
| updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage()); |
| layoutScreenNails(); |
| invalidate(); |
| break; |
| } |
| case 0: { |
| // mImageWidth and mImageHeight will get updated |
| mTileView.notifyModelInvalidated(); |
| |
| mImageRotation = mModel.getImageRotation(); |
| if (((mImageRotation / 90) & 1) == 0) { |
| mPositionController.setImageSize( |
| mTileView.mImageWidth, mTileView.mImageHeight); |
| } else { |
| mPositionController.setImageSize( |
| mTileView.mImageHeight, mTileView.mImageWidth); |
| } |
| updateLoadingState(); |
| break; |
| } |
| } |
| } |
| |
| private void updateLoadingState() { |
| // Possible transitions of mLoadingState: |
| // INIT --> TIMEOUT, COMPLETE, FAIL |
| // TIMEOUT --> COMPLETE, FAIL, INIT |
| // COMPLETE --> INIT |
| // FAIL --> INIT |
| if (mModel.getLevelCount() != 0 || mModel.getBackupImage() != null) { |
| mHandler.removeMessages(MSG_SHOW_LOADING); |
| mLoadingState = LOADING_COMPLETE; |
| } else if (mModel.isFailedToLoad()) { |
| mHandler.removeMessages(MSG_SHOW_LOADING); |
| mLoadingState = LOADING_FAIL; |
| } else if (mLoadingState != LOADING_INIT) { |
| mLoadingState = LOADING_INIT; |
| mHandler.removeMessages(MSG_SHOW_LOADING); |
| mHandler.sendEmptyMessageDelayed( |
| MSG_SHOW_LOADING, DELAY_SHOW_LOADING); |
| } |
| } |
| |
| public void notifyModelInvalidated() { |
| if (mModel == null) { |
| updateScreenNailEntry(ENTRY_PREVIOUS, null); |
| updateScreenNailEntry(ENTRY_NEXT, null); |
| } else { |
| updateScreenNailEntry(ENTRY_PREVIOUS, mModel.getPreviousImage()); |
| updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage()); |
| } |
| layoutScreenNails(); |
| |
| if (mModel == null) { |
| mTileView.notifyModelInvalidated(); |
| mImageRotation = 0; |
| mPositionController.setImageSize(0, 0); |
| updateLoadingState(); |
| } else { |
| notifyImageInvalidated(0); |
| } |
| } |
| |
| @Override |
| protected boolean onTouch(MotionEvent event) { |
| mGestureDetector.onTouchEvent(event); |
| mScaleDetector.onTouchEvent(event); |
| mDownUpDetector.onTouchEvent(event); |
| return true; |
| } |
| |
| @Override |
| protected void onLayout( |
| boolean changeSize, int left, int top, int right, int bottom) { |
| mTileView.layout(left, top, right, bottom); |
| mEdgeView.layout(left, top, right, bottom); |
| if (changeSize) { |
| mPositionController.setViewSize(getWidth(), getHeight()); |
| for (ScreenNailEntry entry : mScreenNails) { |
| entry.updateDrawingSize(); |
| } |
| } |
| } |
| |
| private static int gapToSide(int imageWidth, int viewWidth) { |
| return Math.max(0, (viewWidth - imageWidth) / 2); |
| } |
| |
| /* |
| * Here is how we layout the screen nails |
| * |
| * previous current next |
| * ___________ ________________ __________ |
| * | _______ | | __________ | | ______ | |
| * | | | | | | right->| | | | | | |
| * | | |<-------->|<--left | | | | | | |
| * | |_______| | | | |__________| | | |______| | |
| * |___________| | |________________| |__________| |
| * | <--> gapToSide() |
| * | |
| * IMAGE_GAP + Max(previous.gapToSide(), current.gapToSide) |
| */ |
| private void layoutScreenNails() { |
| int width = getWidth(); |
| int height = getHeight(); |
| |
| // Use the image width in AC, since we may fake the size if the |
| // image is unavailable |
| RectF bounds = mPositionController.getImageBounds(); |
| int left = Math.round(bounds.left); |
| int right = Math.round(bounds.right); |
| int gap = gapToSide(right - left, width); |
| |
| // layout the previous image |
| ScreenNailEntry entry = mScreenNails[ENTRY_PREVIOUS]; |
| |
| if (entry.isEnabled()) { |
| entry.layoutRightEdgeAt(left - ( |
| IMAGE_GAP + Math.max(gap, entry.gapToSide()))); |
| } |
| |
| // layout the next image |
| entry = mScreenNails[ENTRY_NEXT]; |
| if (entry.isEnabled()) { |
| entry.layoutLeftEdgeAt(right + ( |
| IMAGE_GAP + Math.max(gap, entry.gapToSide()))); |
| } |
| } |
| |
| @Override |
| protected void render(GLCanvas canvas) { |
| PositionController p = mPositionController; |
| |
| // Draw the current photo |
| if (mLoadingState == LOADING_COMPLETE) { |
| super.render(canvas); |
| } |
| |
| // Draw the previous and the next photo |
| if (mTransitionMode != TRANS_SLIDE_IN_LEFT |
| && mTransitionMode != TRANS_SLIDE_IN_RIGHT |
| && mTransitionMode != TRANS_OPEN_ANIMATION) { |
| ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; |
| ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; |
| |
| if (prevNail.mVisible) prevNail.draw(canvas); |
| if (nextNail.mVisible) nextNail.draw(canvas); |
| } |
| |
| // Draw the progress spinner and the text below it |
| // |
| // (x, y) is where we put the center of the spinner. |
| // s is the size of the video play icon, and we use s to layout text |
| // because we want to keep the text at the same place when the video |
| // play icon is shown instead of the spinner. |
| int w = getWidth(); |
| int h = getHeight(); |
| int x = Math.round(mPositionController.getImageBounds().centerX()); |
| int y = h / 2; |
| int s = Math.min(getWidth(), getHeight()) / 6; |
| |
| if (mLoadingState == LOADING_TIMEOUT) { |
| StringTexture m = mLoadingText; |
| ProgressSpinner r = mLoadingSpinner; |
| r.draw(canvas, x - r.getWidth() / 2, y - r.getHeight() / 2); |
| m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5); |
| invalidate(); // we need to keep the spinner rotating |
| } else if (mLoadingState == LOADING_FAIL) { |
| StringTexture m = mNoThumbnailText; |
| m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5); |
| } |
| |
| // Draw the video play icon (in the place where the spinner was) |
| if (mShowVideoPlayIcon |
| && mLoadingState != LOADING_INIT |
| && mLoadingState != LOADING_TIMEOUT) { |
| mVideoPlayIcon.draw(canvas, x - s / 2, y - s / 2, s, s); |
| } |
| |
| if (mPositionController.advanceAnimation()) invalidate(); |
| } |
| |
| private void stopCurrentSwipingIfNeeded() { |
| // Enable fast sweeping |
| if (mTransitionMode == TRANS_SWITCH_NEXT) { |
| mTransitionMode = TRANS_NONE; |
| mPositionController.stopAnimation(); |
| switchToNextImage(); |
| } else if (mTransitionMode == TRANS_SWITCH_PREVIOUS) { |
| mTransitionMode = TRANS_NONE; |
| mPositionController.stopAnimation(); |
| switchToPreviousImage(); |
| } |
| } |
| |
| private boolean swipeImages(float velocity) { |
| if (mTransitionMode != TRANS_NONE |
| && mTransitionMode != TRANS_SWITCH_NEXT |
| && mTransitionMode != TRANS_SWITCH_PREVIOUS) return false; |
| |
| ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; |
| ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; |
| |
| int width = getWidth(); |
| |
| // If we are at the edge of the current photo and the sweeping velocity |
| // exceeds the threshold, switch to next / previous image. |
| PositionController controller = mPositionController; |
| boolean isMinimal = controller.isAtMinimalScale(); |
| |
| if (velocity < -SWIPE_THRESHOLD && |
| (isMinimal || controller.isAtRightEdge())) { |
| stopCurrentSwipingIfNeeded(); |
| if (next.isEnabled()) { |
| mTransitionMode = TRANS_SWITCH_NEXT; |
| controller.startHorizontalSlide(next.mOffsetX - width / 2); |
| return true; |
| } |
| } else if (velocity > SWIPE_THRESHOLD && |
| (isMinimal || controller.isAtLeftEdge())) { |
| stopCurrentSwipingIfNeeded(); |
| if (prev.isEnabled()) { |
| mTransitionMode = TRANS_SWITCH_PREVIOUS; |
| controller.startHorizontalSlide(prev.mOffsetX - width / 2); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| public boolean snapToNeighborImage() { |
| if (mTransitionMode != TRANS_NONE) return false; |
| |
| ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; |
| ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; |
| |
| int width = getWidth(); |
| PositionController controller = mPositionController; |
| |
| RectF bounds = controller.getImageBounds(); |
| int left = Math.round(bounds.left); |
| int right = Math.round(bounds.right); |
| int threshold = SWITCH_THRESHOLD + gapToSide(right - left, width); |
| |
| // If we have moved the picture a lot, switching. |
| if (next.isEnabled() && threshold < width - right) { |
| mTransitionMode = TRANS_SWITCH_NEXT; |
| controller.startHorizontalSlide(next.mOffsetX - width / 2); |
| return true; |
| } |
| if (prev.isEnabled() && threshold < left) { |
| mTransitionMode = TRANS_SWITCH_PREVIOUS; |
| controller.startHorizontalSlide(prev.mOffsetX - width / 2); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private boolean mIgnoreUpEvent = false; |
| |
| private class MyGestureListener |
| extends GestureDetector.SimpleOnGestureListener { |
| @Override |
| public boolean onScroll( |
| MotionEvent e1, MotionEvent e2, float dx, float dy) { |
| if (mTransitionMode != TRANS_NONE) return true; |
| |
| ScreenNailEntry next = mScreenNails[ENTRY_NEXT]; |
| ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS]; |
| |
| mPositionController.startScroll(dx, dy, next.isEnabled(), |
| prev.isEnabled()); |
| return true; |
| } |
| |
| @Override |
| public boolean onSingleTapUp(MotionEvent e) { |
| if (mPhotoTapListener != null) { |
| mPhotoTapListener.onSingleTapUp((int) e.getX(), (int) e.getY()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, |
| float velocityY) { |
| if (swipeImages(velocityX)) { |
| mIgnoreUpEvent = true; |
| } else if (mTransitionMode != TRANS_NONE) { |
| // do nothing |
| } else if (mPositionController.fling(velocityX, velocityY)) { |
| mIgnoreUpEvent = true; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onDoubleTap(MotionEvent e) { |
| if (mTransitionMode != TRANS_NONE) return true; |
| PositionController controller = mPositionController; |
| float scale = controller.getCurrentScale(); |
| // onDoubleTap happened on the second ACTION_DOWN. |
| // We need to ignore the next UP event. |
| mIgnoreUpEvent = true; |
| if (scale <= 1.0f || controller.isAtMinimalScale()) { |
| controller.zoomIn( |
| e.getX(), e.getY(), Math.max(1.5f, scale * 1.5f)); |
| } else { |
| controller.resetToFullView(); |
| } |
| return true; |
| } |
| } |
| |
| private class MyScaleListener |
| extends ScaleGestureDetector.SimpleOnScaleGestureListener { |
| |
| @Override |
| public boolean onScale(ScaleGestureDetector detector) { |
| float scale = detector.getScaleFactor(); |
| if (Float.isNaN(scale) || Float.isInfinite(scale) |
| || mTransitionMode != TRANS_NONE) return true; |
| mPositionController.scaleBy(scale, |
| detector.getFocusX(), detector.getFocusY()); |
| return true; |
| } |
| |
| @Override |
| public boolean onScaleBegin(ScaleGestureDetector detector) { |
| if (mTransitionMode != TRANS_NONE) return false; |
| mPositionController.beginScale( |
| detector.getFocusX(), detector.getFocusY()); |
| return true; |
| } |
| |
| @Override |
| public void onScaleEnd(ScaleGestureDetector detector) { |
| mPositionController.endScale(); |
| snapToNeighborImage(); |
| } |
| } |
| |
| public boolean jumpTo(int index) { |
| if (mTransitionMode != TRANS_NONE) return false; |
| mModel.jumpTo(index); |
| return true; |
| } |
| |
| public void notifyOnNewImage() { |
| mPositionController.setImageSize(0, 0); |
| } |
| |
| public void startSlideInAnimation(int direction) { |
| PositionController a = mPositionController; |
| a.stopAnimation(); |
| switch (direction) { |
| case TRANS_SLIDE_IN_LEFT: |
| case TRANS_SLIDE_IN_RIGHT: { |
| mTransitionMode = direction; |
| a.startSlideInAnimation(direction); |
| break; |
| } |
| default: throw new IllegalArgumentException(String.valueOf(direction)); |
| } |
| } |
| |
| private class MyDownUpListener implements DownUpDetector.DownUpListener { |
| public void onDown(MotionEvent e) { |
| } |
| |
| public void onUp(MotionEvent e) { |
| mEdgeView.onRelease(); |
| |
| if (mIgnoreUpEvent) { |
| mIgnoreUpEvent = false; |
| return; |
| } |
| if (!snapToNeighborImage() && mTransitionMode == TRANS_NONE) { |
| mPositionController.up(); |
| } |
| } |
| } |
| |
| private void switchToNextImage() { |
| // We update the texture here directly to prevent texture uploading. |
| ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; |
| ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; |
| mTileView.invalidateTiles(); |
| if (prevNail.mTexture != null) prevNail.mTexture.recycle(); |
| prevNail.mTexture = mTileView.mBackupImage; |
| mTileView.mBackupImage = nextNail.mTexture; |
| nextNail.mTexture = null; |
| mModel.next(); |
| } |
| |
| private void switchToPreviousImage() { |
| // We update the texture here directly to prevent texture uploading. |
| ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS]; |
| ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT]; |
| mTileView.invalidateTiles(); |
| if (nextNail.mTexture != null) nextNail.mTexture.recycle(); |
| nextNail.mTexture = mTileView.mBackupImage; |
| mTileView.mBackupImage = prevNail.mTexture; |
| nextNail.mTexture = null; |
| mModel.previous(); |
| } |
| |
| public void notifyTransitionComplete() { |
| mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE); |
| } |
| |
| private void onTransitionComplete() { |
| int mode = mTransitionMode; |
| mTransitionMode = TRANS_NONE; |
| |
| if (mModel == null) return; |
| if (mode == TRANS_SWITCH_NEXT) { |
| switchToNextImage(); |
| } else if (mode == TRANS_SWITCH_PREVIOUS) { |
| switchToPreviousImage(); |
| } |
| } |
| |
| public boolean isDown() { |
| return mDownUpDetector.isDown(); |
| } |
| |
| public static interface Model extends TileImageView.Model { |
| public void next(); |
| public void previous(); |
| public void jumpTo(int index); |
| public int getImageRotation(); |
| |
| // Return null if the specified image is unavailable. |
| public ImageData getNextImage(); |
| public ImageData getPreviousImage(); |
| } |
| |
| public static class ImageData { |
| public int rotation; |
| public Bitmap bitmap; |
| |
| public ImageData(Bitmap bitmap, int rotation) { |
| this.bitmap = bitmap; |
| this.rotation = rotation; |
| } |
| } |
| |
| private static int getRotated(int degree, int original, int theother) { |
| return ((degree / 90) & 1) == 0 ? original : theother; |
| } |
| |
| private class ScreenNailEntry { |
| private boolean mVisible; |
| private boolean mEnabled; |
| |
| private int mRotation; |
| private int mDrawWidth; |
| private int mDrawHeight; |
| private int mOffsetX; |
| |
| private BitmapTexture mTexture; |
| |
| public void set(boolean enabled, Bitmap bitmap, int rotation) { |
| mEnabled = enabled; |
| mRotation = rotation; |
| if (bitmap == null) { |
| if (mTexture != null) mTexture.recycle(); |
| mTexture = null; |
| } else { |
| if (mTexture != null) { |
| if (mTexture.getBitmap() != bitmap) { |
| mTexture.recycle(); |
| mTexture = new BitmapTexture(bitmap); |
| } |
| } else { |
| mTexture = new BitmapTexture(bitmap); |
| } |
| updateDrawingSize(); |
| } |
| } |
| |
| public void layoutRightEdgeAt(int x) { |
| mVisible = x > 0; |
| mOffsetX = x - getRotated( |
| mRotation, mDrawWidth, mDrawHeight) / 2; |
| } |
| |
| public void layoutLeftEdgeAt(int x) { |
| mVisible = x < getWidth(); |
| mOffsetX = x + getRotated( |
| mRotation, mDrawWidth, mDrawHeight) / 2; |
| } |
| |
| public int gapToSide() { |
| return ((mRotation / 90) & 1) != 0 |
| ? PhotoView.gapToSide(mDrawHeight, getWidth()) |
| : PhotoView.gapToSide(mDrawWidth, getWidth()); |
| } |
| |
| public void updateDrawingSize() { |
| if (mTexture == null) return; |
| |
| int width = mTexture.getWidth(); |
| int height = mTexture.getHeight(); |
| |
| // Calculate the initial scale that will used by PositionController |
| // (usually fit-to-screen) |
| float s = ((mRotation / 90) & 0x01) == 0 |
| ? mPositionController.getMinimalScale(width, height) |
| : mPositionController.getMinimalScale(height, width); |
| |
| mDrawWidth = Math.round(width * s); |
| mDrawHeight = Math.round(height * s); |
| } |
| |
| public boolean isEnabled() { |
| return mEnabled; |
| } |
| |
| public void draw(GLCanvas canvas) { |
| int x = mOffsetX; |
| int y = getHeight() / 2; |
| |
| if (mTexture != null) { |
| if (mRotation != 0) { |
| canvas.save(GLCanvas.SAVE_FLAG_MATRIX); |
| canvas.translate(x, y, 0); |
| canvas.rotate(mRotation, 0, 0, 1); //mRotation |
| canvas.translate(-x, -y, 0); |
| } |
| mTexture.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2, |
| mDrawWidth, mDrawHeight); |
| if (mRotation != 0) { |
| canvas.restore(); |
| } |
| } |
| } |
| } |
| |
| public void pause() { |
| mPositionController.skipAnimation(); |
| mTransitionMode = TRANS_NONE; |
| mTileView.freeTextures(); |
| for (ScreenNailEntry entry : mScreenNails) { |
| entry.set(false, null, 0); |
| } |
| } |
| |
| public void resume() { |
| mTileView.prepareTextures(); |
| } |
| |
| public void setOpenedItem(Path itemPath) { |
| mOpenedItemPath = itemPath; |
| } |
| |
| public void showVideoPlayIcon(boolean show) { |
| mShowVideoPlayIcon = show; |
| } |
| |
| // Returns the position saved by the previous page. |
| public Position retrieveSavedPosition() { |
| if (mOpenedItemPath != null) { |
| Position position = PositionRepository |
| .getInstance(mActivity).get(Long.valueOf( |
| System.identityHashCode(mOpenedItemPath))); |
| mOpenedItemPath = null; |
| return position; |
| } |
| return null; |
| } |
| |
| public void openAnimationStarted() { |
| mTransitionMode = TRANS_OPEN_ANIMATION; |
| } |
| |
| public boolean isInTransition() { |
| return mTransitionMode != TRANS_NONE; |
| } |
| } |