diff options
| author | Chih-Chung Chang <chihchung@google.com> | 2011-09-26 17:34:06 +0800 |
|---|---|---|
| committer | Chih-Chung Chang <chihchung@google.com> | 2011-09-27 11:07:40 +0800 |
| commit | ec4125492f17130f65e49160a17bd437e01128a7 (patch) | |
| tree | 7c004f4612254fa44c3ea79159d24a0415f73311 /src | |
| parent | 9696bf893a7391be9acbf243a5ec1d2e60c1482c (diff) | |
| download | android_packages_apps_Gallery2-ec4125492f17130f65e49160a17bd437e01128a7.tar.gz android_packages_apps_Gallery2-ec4125492f17130f65e49160a17bd437e01128a7.tar.bz2 android_packages_apps_Gallery2-ec4125492f17130f65e49160a17bd437e01128a7.zip | |
Move out PositionController from PhotoView.
Change-Id: Id1088ffbd67ff5e986fdf16f70ba24f1537d10ab
Diffstat (limited to 'src')
| -rw-r--r-- | src/com/android/gallery3d/ui/PhotoView.java | 505 | ||||
| -rw-r--r-- | src/com/android/gallery3d/ui/PositionController.java | 499 |
2 files changed, 535 insertions, 469 deletions
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index feb0ee778..dea9301ae 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -65,10 +65,6 @@ public class PhotoView extends GLView { private static final float DEFAULT_TEXT_SIZE = 20; - // We try to scale up the image to fill the screen. But in order not to - // scale too much for small icons, we limit the max up-scaling factor here. - private static final float SCALE_LIMIT = 4; - public interface PhotoTapListener { public void onSingleTapUp(int x, int y); } @@ -98,9 +94,6 @@ public class PhotoView extends GLView { private int mLoadingState = LOADING_COMPLETE; - private RectF mTempRect = new RectF(); - private float[] mTempPoints = new float[8]; - private int mImageRotation; private Path mOpenedItemPath; @@ -169,8 +162,8 @@ public class PhotoView extends GLView { } private boolean setTileViewPosition(int centerX, int centerY, float scale) { - int inverseX = mPositionController.mImageW - centerX; - int inverseY = mPositionController.mImageH - centerY; + int inverseX = mPositionController.getImageWidth() - centerX; + int inverseY = mPositionController.getImageHeight() - centerY; TileImageView t = mTileView; int rotation = mImageRotation; switch (rotation) { @@ -300,40 +293,6 @@ public class PhotoView extends GLView { return Math.max(0, (viewWidth - imageWidth) / 2); } - private RectF getImageBounds() { - PositionController p = mPositionController; - float points[] = mTempPoints; - - /* - * (p0,p1)----------(p2,p3) - * | | - * | | - * (p4,p5)----------(p6,p7) - */ - points[0] = points[4] = -p.mCurrentX; - points[1] = points[3] = -p.mCurrentY; - points[2] = points[6] = p.mImageW - p.mCurrentX; - points[5] = points[7] = p.mImageH - p.mCurrentY; - - RectF rect = mTempRect; - rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - - float scale = p.mCurrentScale; - float offsetX = p.mViewW / 2; - float offsetY = p.mViewH / 2; - for (int i = 0; i < 4; ++i) { - float x = points[i + i] * scale + offsetX; - float y = points[i + i + 1] * scale + offsetY; - if (x < rect.left) rect.left = x; - if (x > rect.right) rect.right = x; - if (y < rect.top) rect.top = y; - if (y > rect.bottom) rect.bottom = y; - } - return rect; - } - - /* * Here is how we layout the screen nails * @@ -354,7 +313,7 @@ public class PhotoView extends GLView { // Use the image width in AC, since we may fake the size if the // image is unavailable - RectF bounds = getImageBounds(); + RectF bounds = mPositionController.getImageBounds(); int left = Math.round(bounds.left); int right = Math.round(bounds.right); int gap = gapToSide(right - left, width); @@ -375,411 +334,6 @@ public class PhotoView extends GLView { } } - private static class PositionController { - private long mAnimationStartTime = NO_ANIMATION; - private static final long NO_ANIMATION = -1; - private static final long LAST_ANIMATION = -2; - - // Animation time in milliseconds. - private static final float ANIM_TIME_SCROLL = 0; - private static final float ANIM_TIME_SCALE = 50; - private static final float ANIM_TIME_SNAPBACK = 600; - private static final float ANIM_TIME_SLIDE = 400; - private static final float ANIM_TIME_ZOOM = 300; - - private int mAnimationKind; - private final static int ANIM_KIND_SCROLL = 0; - private final static int ANIM_KIND_SCALE = 1; - private final static int ANIM_KIND_SNAPBACK = 2; - private final static int ANIM_KIND_SLIDE = 3; - private final static int ANIM_KIND_ZOOM = 4; - - private PhotoView mViewer; - private int mImageW, mImageH; - private int mViewW, mViewH; - - // The X, Y are the coordinate on bitmap which shows on the center of - // the view. We always keep the mCurrent{X,Y,SCALE} sync with the actual - // values used currently. - private int mCurrentX, mFromX, mToX; - private int mCurrentY, mFromY, mToY; - private float mCurrentScale, mFromScale, mToScale; - - // The offsets from the center of the view to the user's focus point, - // converted to the bitmap domain. - private float mPrevOffsetX; - private float mPrevOffsetY; - private boolean mInScale; - private boolean mUseViewSize = true; - - // The limits for position and scale. - private float mScaleMin, mScaleMax = 4f; - - PositionController(PhotoView viewer) { - mViewer = viewer; - } - - public void setImageSize(int width, int height) { - - // If no image available, use view size. - if (width == 0 || height == 0) { - mUseViewSize = true; - mImageW = mViewW; - mImageH = mViewH; - mCurrentX = mImageW / 2; - mCurrentY = mImageH / 2; - mCurrentScale = 1; - mScaleMin = 1; - mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); - return; - } - - mUseViewSize = false; - - float ratio = Math.min( - (float) mImageW / width, (float) mImageH / height); - - mCurrentX = translate(mCurrentX, mImageW, width, ratio); - mCurrentY = translate(mCurrentY, mImageH, height, ratio); - mCurrentScale = mCurrentScale * ratio; - - mFromX = translate(mFromX, mImageW, width, ratio); - mFromY = translate(mFromY, mImageH, height, ratio); - mFromScale = mFromScale * ratio; - - mToX = translate(mToX, mImageW, width, ratio); - mToY = translate(mToY, mImageH, height, ratio); - mToScale = mToScale * ratio; - - mImageW = width; - mImageH = height; - - mScaleMin = getMinimalScale(width, height, 0); - - // Scale the new image to fit into the old one - if (mViewer.mOpenedItemPath != null) { - Position position = PositionRepository - .getInstance(mViewer.mActivity).get(Long.valueOf( - System.identityHashCode(mViewer.mOpenedItemPath))); - mViewer.mOpenedItemPath = null; - if (position != null) { - float scale = 240f / Math.min(width, height); - mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2; - mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2; - mCurrentScale = scale; - mViewer.mTransitionMode = TRANS_OPEN_ANIMATION; - startSnapback(); - } - } else if (mAnimationStartTime == NO_ANIMATION) { - mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax); - } - mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); - } - - public void zoomIn(float tapX, float tapY, float targetScale) { - if (targetScale > mScaleMax) targetScale = mScaleMax; - float scale = mCurrentScale; - float tempX = (tapX - mViewW / 2) / mCurrentScale + mCurrentX; - float tempY = (tapY - mViewH / 2) / mCurrentScale + mCurrentY; - - // mCurrentX + (mViewW / 2) * (1 / targetScale) < mImageW - // mCurrentX - (mViewW / 2) * (1 / targetScale) > 0 - float min = mViewW / 2.0f / targetScale; - float max = mImageW - mViewW / 2.0f / targetScale; - int targetX = (int) Utils.clamp(tempX, min, max); - - min = mViewH / 2.0f / targetScale; - max = mImageH - mViewH / 2.0f / targetScale; - int targetY = (int) Utils.clamp(tempY, min, max); - - // If the width of the image is less then the view, center the image - if (mImageW * targetScale < mViewW) targetX = mImageW / 2; - if (mImageH * targetScale < mViewH) targetY = mImageH / 2; - - startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM); - } - - public void resetToFullView() { - startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM); - } - - private float getMinimalScale(int w, int h, int rotation) { - return Math.min(SCALE_LIMIT, ((rotation / 90) & 0x01) == 0 - ? Math.min((float) mViewW / w, (float) mViewH / h) - : Math.min((float) mViewW / h, (float) mViewH / w)); - } - - private static int translate(int value, int size, int updateSize, float ratio) { - return Math.round( - (value + (updateSize * ratio - size) / 2f) / ratio); - } - - public void setViewSize(int viewW, int viewH) { - boolean needLayout = mViewW == 0 || mViewH == 0; - - mViewW = viewW; - mViewH = viewH; - - if (mUseViewSize) { - mImageW = viewW; - mImageH = viewH; - mCurrentX = mImageW / 2; - mCurrentY = mImageH / 2; - mCurrentScale = 1; - mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); - } else { - boolean wasMinScale = (mCurrentScale == mScaleMin); - mScaleMin = Math.min(SCALE_LIMIT, Math.min( - (float) viewW / mImageW, (float) viewH / mImageH)); - if (needLayout || mCurrentScale < mScaleMin || wasMinScale) { - mCurrentX = mImageW / 2; - mCurrentY = mImageH / 2; - mCurrentScale = mScaleMin; - mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); - } - } - } - - public void stopAnimation() { - mAnimationStartTime = NO_ANIMATION; - } - - public void skipAnimation() { - if (mAnimationStartTime == NO_ANIMATION) return; - mAnimationStartTime = NO_ANIMATION; - mCurrentX = mToX; - mCurrentY = mToY; - mCurrentScale = mToScale; - } - - public void scrollBy(float dx, float dy, int type) { - startAnimation(getTargetX() + Math.round(dx / mCurrentScale), - getTargetY() + Math.round(dy / mCurrentScale), - mCurrentScale, type); - } - - public void beginScale(float focusX, float focusY) { - mInScale = true; - mPrevOffsetX = (focusX - mViewW / 2f) / mCurrentScale; - mPrevOffsetY = (focusY - mViewH / 2f) / mCurrentScale; - } - - public void scaleBy(float s, float focusX, float focusY) { - - // The focus point should keep this position on the ImageView. - // So, mCurrentX + mPrevOffsetX = mCurrentX' + offsetX. - // mCurrentY + mPrevOffsetY = mCurrentY' + offsetY. - float offsetX = (focusX - mViewW / 2f) / mCurrentScale; - float offsetY = (focusY - mViewH / 2f) / mCurrentScale; - - startAnimation(getTargetX() - Math.round(offsetX - mPrevOffsetX), - getTargetY() - Math.round(offsetY - mPrevOffsetY), - getTargetScale() * s, ANIM_KIND_SCALE); - mPrevOffsetX = offsetX; - mPrevOffsetY = offsetY; - } - - public void endScale() { - mInScale = false; - startSnapbackIfNeeded(); - } - - public void up() { - startSnapback(); - } - - public void startSlideInAnimation(int fromX) { - mFromX = Math.round(fromX + (mImageW - mViewW) / 2f); - mFromY = Math.round(mImageH / 2f); - mCurrentX = mFromX; - mCurrentY = mFromY; - startAnimation(mImageW / 2, mImageH / 2, mCurrentScale, - ANIM_KIND_SLIDE); - } - - public void startHorizontalSlide(int distance) { - scrollBy(distance, 0, ANIM_KIND_SLIDE); - } - - private void startAnimation( - int centerX, int centerY, float scale, int kind) { - if (centerX == mCurrentX && centerY == mCurrentY - && scale == mCurrentScale) return; - - mFromX = mCurrentX; - mFromY = mCurrentY; - mFromScale = mCurrentScale; - - mToX = centerX; - mToY = centerY; - mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax); - - // If the scaled dimension is smaller than the view, - // force it to be in the center. - if (Math.floor(mImageH * mToScale) <= mViewH) { - mToY = mImageH / 2; - } - - mAnimationStartTime = SystemClock.uptimeMillis(); - mAnimationKind = kind; - if (advanceAnimation()) mViewer.invalidate(); - } - - // Returns true if redraw is needed. - public boolean advanceAnimation() { - if (mAnimationStartTime == NO_ANIMATION) { - return false; - } else if (mAnimationStartTime == LAST_ANIMATION) { - mAnimationStartTime = NO_ANIMATION; - if (mViewer.mTransitionMode != TRANS_NONE) { - mViewer.mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE); - return false; - } else { - return startSnapbackIfNeeded(); - } - } - - float animationTime; - if (mAnimationKind == ANIM_KIND_SCROLL) { - animationTime = ANIM_TIME_SCROLL; - } else if (mAnimationKind == ANIM_KIND_SCALE) { - animationTime = ANIM_TIME_SCALE; - } else if (mAnimationKind == ANIM_KIND_SLIDE) { - animationTime = ANIM_TIME_SLIDE; - } else if (mAnimationKind == ANIM_KIND_ZOOM) { - animationTime = ANIM_TIME_ZOOM; - } else /* if (mAnimationKind == ANIM_KIND_SNAPBACK) */ { - animationTime = ANIM_TIME_SNAPBACK; - } - - float progress; - if (animationTime == 0) { - progress = 1; - } else { - long now = SystemClock.uptimeMillis(); - progress = (now - mAnimationStartTime) / animationTime; - } - - if (progress >= 1) { - progress = 1; - mCurrentX = mToX; - mCurrentY = mToY; - mCurrentScale = mToScale; - mAnimationStartTime = LAST_ANIMATION; - } else { - float f = 1 - progress; - if (mAnimationKind == ANIM_KIND_SCROLL) { - progress = 1 - f; // linear - } else if (mAnimationKind == ANIM_KIND_SCALE) { - progress = 1 - f * f; // quadratic - } else /* if mAnimationKind is ANIM_KIND_SNAPBACK, - ANIM_KIND_ZOOM or ANIM_KIND_SLIDE */ { - progress = 1 - f * f * f * f * f; // x^5 - } - linearInterpolate(progress); - } - mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); - return true; - } - - private void linearInterpolate(float progress) { - // To linearly interpolate the position, we have to translate the - // coordinates. The meaning of the translated point (x, y) is the - // coordinates of the center of the bitmap on the view component. - float fromX = mViewW / 2f + (mImageW / 2f - mFromX) * mFromScale; - float toX = mViewW / 2f + (mImageW / 2f - mToX) * mToScale; - float currentX = fromX + progress * (toX - fromX); - - float fromY = mViewH / 2f + (mImageH / 2f - mFromY) * mFromScale; - float toY = mViewH / 2f + (mImageH / 2f - mToY) * mToScale; - float currentY = fromY + progress * (toY - fromY); - - mCurrentScale = mFromScale + progress * (mToScale - mFromScale); - mCurrentX = Math.round( - mImageW / 2f + (mViewW / 2f - currentX) / mCurrentScale); - mCurrentY = Math.round( - mImageH / 2f + (mViewH / 2f - currentY) / mCurrentScale); - } - - // Returns true if redraw is needed. - private boolean startSnapbackIfNeeded() { - if (mAnimationStartTime != NO_ANIMATION) return false; - if (mInScale) return false; - if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) { - return false; - } - return startSnapback(); - } - - public boolean startSnapback() { - boolean needAnimation = false; - int x = mCurrentX; - int y = mCurrentY; - float scale = mCurrentScale; - - if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) { - needAnimation = true; - scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax); - } - - // The number of pixels when the edge is aligned. - int left = (int) Math.ceil(mViewW / (2 * scale)); - int right = mImageW - left; - int top = (int) Math.ceil(mViewH / (2 * scale)); - int bottom = mImageH - top; - - if (mImageW * scale > mViewW) { - if (mCurrentX < left) { - needAnimation = true; - x = left; - } else if (mCurrentX > right) { - needAnimation = true; - x = right; - } - } else if (mCurrentX != mImageW / 2) { - needAnimation = true; - x = mImageW / 2; - } - - if (mImageH * scale > mViewH) { - if (mCurrentY < top) { - needAnimation = true; - y = top; - } else if (mCurrentY > bottom) { - needAnimation = true; - y = bottom; - } - } else if (mCurrentY != mImageH / 2) { - needAnimation = true; - y = mImageH / 2; - } - - if (needAnimation) { - startAnimation(x, y, scale, ANIM_KIND_SNAPBACK); - } - - return needAnimation; - } - - private float getTargetScale() { - if (mAnimationStartTime == NO_ANIMATION - || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentScale; - return mToScale; - } - - private int getTargetX() { - if (mAnimationStartTime == NO_ANIMATION - || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentX; - return mToX; - } - - private int getTargetY() { - if (mAnimationStartTime == NO_ANIMATION - || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentY; - return mToY; - } - } - @Override protected void render(GLCanvas canvas) { PositionController p = mPositionController; @@ -808,7 +362,7 @@ public class PhotoView extends GLView { // play icon is shown instead of the spinner. int w = getWidth(); int h = getHeight(); - int x = Math.round(getImageBounds().centerX()); + int x = Math.round(mPositionController.getImageBounds().centerX()); int y = h / 2; int s = Math.min(getWidth(), getHeight()) / 6; @@ -846,11 +400,6 @@ public class PhotoView extends GLView { } } - private static boolean isAlmostEquals(float a, float b) { - float diff = a - b; - return (diff < 0 ? -diff : diff) < 0.02f; - } - private boolean swipeImages(float velocity) { if (mTransitionMode != TRANS_NONE && mTransitionMode != TRANS_SWITCH_NEXT @@ -864,7 +413,7 @@ public class PhotoView extends GLView { // If the edge of the current photo is visible and the sweeping velocity // exceed the threshold, switch to next / previous image PositionController controller = mPositionController; - if (isAlmostEquals(controller.mCurrentScale, controller.mScaleMin)) { + if (controller.isAtMinimalScale()) { if (velocity < -SWIPE_THRESHOLD) { stopCurrentSwipingIfNeeded(); if (next.isEnabled()) { @@ -888,7 +437,7 @@ public class PhotoView extends GLView { if (mTransitionMode != TRANS_NONE) return false; // Decide whether to swiping to the next/prev image in the zoom-in case - RectF bounds = getImageBounds(); + RectF bounds = mPositionController.getImageBounds(); int left = Math.round(bounds.left); int right = Math.round(bounds.right); int threshold = SWITCH_THRESHOLD + gapToSide(right - left, width); @@ -916,8 +465,7 @@ public class PhotoView extends GLView { public boolean onScroll( MotionEvent e1, MotionEvent e2, float dx, float dy) { if (mTransitionMode != TRANS_NONE) return true; - mPositionController.scrollBy( - dx, dy, PositionController.ANIM_KIND_SCROLL); + mPositionController.startScroll(dx, dy); return true; } @@ -943,11 +491,11 @@ public class PhotoView extends GLView { public boolean onDoubleTap(MotionEvent e) { if (mTransitionMode != TRANS_NONE) return true; PositionController controller = mPositionController; - float scale = controller.mCurrentScale; + 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 || isAlmostEquals(scale, controller.mScaleMin)) { + if (scale <= 1.0f || controller.isAtMinimalScale()) { controller.zoomIn( e.getX(), e.getY(), Math.max(1.5f, scale * 1.5f)); } else { @@ -999,14 +547,10 @@ public class PhotoView extends GLView { PositionController a = mPositionController; a.stopAnimation(); switch (direction) { - case TRANS_SLIDE_IN_LEFT: { - mTransitionMode = TRANS_SLIDE_IN_LEFT; - a.startSlideInAnimation(a.mViewW); - break; - } + case TRANS_SLIDE_IN_LEFT: case TRANS_SLIDE_IN_RIGHT: { - mTransitionMode = TRANS_SLIDE_IN_RIGHT; - a.startSlideInAnimation(-a.mViewW); + mTransitionMode = direction; + a.startSlideInAnimation(direction); break; } default: throw new IllegalArgumentException(String.valueOf(direction)); @@ -1052,6 +596,10 @@ public class PhotoView extends GLView { mModel.previous(); } + public void notifyTransitionComplete() { + mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE); + } + private void onTransitionComplete() { int mode = mTransitionMode; mTransitionMode = TRANS_NONE; @@ -1064,7 +612,7 @@ public class PhotoView extends GLView { } } - private boolean isDown() { + public boolean isDown() { return mDownUpDetector.isDown(); } @@ -1195,4 +743,23 @@ public class PhotoView extends GLView { public void showVideoPlayIcon(boolean show) { mShowVideoPlayIcon = show; } + + public Position retrieveOldPosition() { + 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; + } } diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java new file mode 100644 index 000000000..8d5563e94 --- /dev/null +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2011 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; + +class PositionController { + private long mAnimationStartTime = NO_ANIMATION; + private static final long NO_ANIMATION = -1; + private static final long LAST_ANIMATION = -2; + + // Animation time in milliseconds. + private static final float ANIM_TIME_SCROLL = 0; + private static final float ANIM_TIME_SCALE = 50; + private static final float ANIM_TIME_SNAPBACK = 600; + private static final float ANIM_TIME_SLIDE = 400; + private static final float ANIM_TIME_ZOOM = 300; + + private int mAnimationKind; + private final static int ANIM_KIND_SCROLL = 0; + private final static int ANIM_KIND_SCALE = 1; + private final static int ANIM_KIND_SNAPBACK = 2; + private final static int ANIM_KIND_SLIDE = 3; + private final static int ANIM_KIND_ZOOM = 4; + + // We try to scale up the image to fill the screen. But in order not to + // scale too much for small icons, we limit the max up-scaling factor here. + private static final float SCALE_LIMIT = 4; + + private PhotoView mViewer; + private int mImageW, mImageH; + private int mViewW, mViewH; + + // The X, Y are the coordinate on bitmap which shows on the center of + // the view. We always keep the mCurrent{X,Y,SCALE} sync with the actual + // values used currently. + private int mCurrentX, mFromX, mToX; + private int mCurrentY, mFromY, mToY; + private float mCurrentScale, mFromScale, mToScale; + + // The offsets from the center of the view to the user's focus point, + // converted to the bitmap domain. + private float mPrevOffsetX; + private float mPrevOffsetY; + private boolean mInScale; + private boolean mUseViewSize = true; + + // The limits for position and scale. + private float mScaleMin, mScaleMax = 4f; + + private RectF mTempRect = new RectF(); + private float[] mTempPoints = new float[8]; + + PositionController(PhotoView viewer) { + mViewer = viewer; + } + + public void setImageSize(int width, int height) { + + // If no image available, use view size. + if (width == 0 || height == 0) { + mUseViewSize = true; + mImageW = mViewW; + mImageH = mViewH; + mCurrentX = mImageW / 2; + mCurrentY = mImageH / 2; + mCurrentScale = 1; + mScaleMin = 1; + mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); + return; + } + + mUseViewSize = false; + + float ratio = Math.min( + (float) mImageW / width, (float) mImageH / height); + + mCurrentX = translate(mCurrentX, mImageW, width, ratio); + mCurrentY = translate(mCurrentY, mImageH, height, ratio); + mCurrentScale = mCurrentScale * ratio; + + mFromX = translate(mFromX, mImageW, width, ratio); + mFromY = translate(mFromY, mImageH, height, ratio); + mFromScale = mFromScale * ratio; + + mToX = translate(mToX, mImageW, width, ratio); + mToY = translate(mToY, mImageH, height, ratio); + mToScale = mToScale * ratio; + + mImageW = width; + mImageH = height; + + mScaleMin = getMinimalScale(width, height, 0); + + // Scale the new image to fit into the old one + Position position = mViewer.retrieveOldPosition(); + if (position != null) { + float scale = 240f / Math.min(width, height); + mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2; + mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2; + mCurrentScale = scale; + mViewer.openAnimationStarted(); + startSnapback(); + } else if (mAnimationStartTime == NO_ANIMATION) { + mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax); + } + mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); + } + + public void zoomIn(float tapX, float tapY, float targetScale) { + if (targetScale > mScaleMax) targetScale = mScaleMax; + float scale = mCurrentScale; + float tempX = (tapX - mViewW / 2) / mCurrentScale + mCurrentX; + float tempY = (tapY - mViewH / 2) / mCurrentScale + mCurrentY; + + // mCurrentX + (mViewW / 2) * (1 / targetScale) < mImageW + // mCurrentX - (mViewW / 2) * (1 / targetScale) > 0 + float min = mViewW / 2.0f / targetScale; + float max = mImageW - mViewW / 2.0f / targetScale; + int targetX = (int) Utils.clamp(tempX, min, max); + + min = mViewH / 2.0f / targetScale; + max = mImageH - mViewH / 2.0f / targetScale; + int targetY = (int) Utils.clamp(tempY, min, max); + + // If the width of the image is less then the view, center the image + if (mImageW * targetScale < mViewW) targetX = mImageW / 2; + if (mImageH * targetScale < mViewH) targetY = mImageH / 2; + + startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM); + } + + public void resetToFullView() { + startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM); + } + + public float getMinimalScale(int w, int h, int rotation) { + return Math.min(SCALE_LIMIT, ((rotation / 90) & 0x01) == 0 + ? Math.min((float) mViewW / w, (float) mViewH / h) + : Math.min((float) mViewW / h, (float) mViewH / w)); + } + + private static int translate(int value, int size, int updateSize, float ratio) { + return Math.round( + (value + (updateSize * ratio - size) / 2f) / ratio); + } + + public void setViewSize(int viewW, int viewH) { + boolean needLayout = mViewW == 0 || mViewH == 0; + + mViewW = viewW; + mViewH = viewH; + + if (mUseViewSize) { + mImageW = viewW; + mImageH = viewH; + mCurrentX = mImageW / 2; + mCurrentY = mImageH / 2; + mCurrentScale = 1; + mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); + } else { + boolean wasMinScale = (mCurrentScale == mScaleMin); + mScaleMin = Math.min(SCALE_LIMIT, Math.min( + (float) viewW / mImageW, (float) viewH / mImageH)); + if (needLayout || mCurrentScale < mScaleMin || wasMinScale) { + mCurrentX = mImageW / 2; + mCurrentY = mImageH / 2; + mCurrentScale = mScaleMin; + mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); + } + } + } + + public void stopAnimation() { + mAnimationStartTime = NO_ANIMATION; + } + + public void skipAnimation() { + if (mAnimationStartTime == NO_ANIMATION) return; + mAnimationStartTime = NO_ANIMATION; + mCurrentX = mToX; + mCurrentY = mToY; + mCurrentScale = mToScale; + } + + public void scrollBy(float dx, float dy, int type) { + startAnimation(getTargetX() + Math.round(dx / mCurrentScale), + getTargetY() + Math.round(dy / mCurrentScale), + mCurrentScale, type); + } + + public void beginScale(float focusX, float focusY) { + mInScale = true; + mPrevOffsetX = (focusX - mViewW / 2f) / mCurrentScale; + mPrevOffsetY = (focusY - mViewH / 2f) / mCurrentScale; + } + + public void scaleBy(float s, float focusX, float focusY) { + + // The focus point should keep this position on the ImageView. + // So, mCurrentX + mPrevOffsetX = mCurrentX' + offsetX. + // mCurrentY + mPrevOffsetY = mCurrentY' + offsetY. + float offsetX = (focusX - mViewW / 2f) / mCurrentScale; + float offsetY = (focusY - mViewH / 2f) / mCurrentScale; + + startAnimation(getTargetX() - Math.round(offsetX - mPrevOffsetX), + getTargetY() - Math.round(offsetY - mPrevOffsetY), + getTargetScale() * s, ANIM_KIND_SCALE); + mPrevOffsetX = offsetX; + mPrevOffsetY = offsetY; + } + + public void endScale() { + mInScale = false; + startSnapbackIfNeeded(); + } + + public float getCurrentScale() { + return mCurrentScale; + } + + public boolean isAtMinimalScale() { + return isAlmostEquals(mCurrentScale, mScaleMin); + } + + private static boolean isAlmostEquals(float a, float b) { + float diff = a - b; + return (diff < 0 ? -diff : diff) < 0.02f; + } + + public void up() { + startSnapback(); + } + + public void startSlideInAnimation(int direction) { + int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ? + mViewW : -mViewW; + mFromX = Math.round(fromX + (mImageW - mViewW) / 2f); + mFromY = Math.round(mImageH / 2f); + mCurrentX = mFromX; + mCurrentY = mFromY; + startAnimation(mImageW / 2, mImageH / 2, mCurrentScale, + ANIM_KIND_SLIDE); + } + + public void startHorizontalSlide(int distance) { + scrollBy(distance, 0, ANIM_KIND_SLIDE); + } + + public void startScroll(float dx, float dy) { + scrollBy(dx, dy, ANIM_KIND_SCROLL); + } + + private void startAnimation( + int centerX, int centerY, float scale, int kind) { + if (centerX == mCurrentX && centerY == mCurrentY + && scale == mCurrentScale) return; + + mFromX = mCurrentX; + mFromY = mCurrentY; + mFromScale = mCurrentScale; + + mToX = centerX; + mToY = centerY; + mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax); + + // If the scaled dimension is smaller than the view, + // force it to be in the center. + if (Math.floor(mImageH * mToScale) <= mViewH) { + mToY = mImageH / 2; + } + + mAnimationStartTime = SystemClock.uptimeMillis(); + mAnimationKind = kind; + if (advanceAnimation()) mViewer.invalidate(); + } + + // Returns true if redraw is needed. + public boolean advanceAnimation() { + if (mAnimationStartTime == NO_ANIMATION) { + return false; + } else if (mAnimationStartTime == LAST_ANIMATION) { + mAnimationStartTime = NO_ANIMATION; + if (mViewer.isInTransition()) { + mViewer.notifyTransitionComplete(); + return false; + } else { + return startSnapbackIfNeeded(); + } + } + + float animationTime; + if (mAnimationKind == ANIM_KIND_SCROLL) { + animationTime = ANIM_TIME_SCROLL; + } else if (mAnimationKind == ANIM_KIND_SCALE) { + animationTime = ANIM_TIME_SCALE; + } else if (mAnimationKind == ANIM_KIND_SLIDE) { + animationTime = ANIM_TIME_SLIDE; + } else if (mAnimationKind == ANIM_KIND_ZOOM) { + animationTime = ANIM_TIME_ZOOM; + } else /* if (mAnimationKind == ANIM_KIND_SNAPBACK) */ { + animationTime = ANIM_TIME_SNAPBACK; + } + + float progress; + if (animationTime == 0) { + progress = 1; + } else { + long now = SystemClock.uptimeMillis(); + progress = (now - mAnimationStartTime) / animationTime; + } + + if (progress >= 1) { + progress = 1; + mCurrentX = mToX; + mCurrentY = mToY; + mCurrentScale = mToScale; + mAnimationStartTime = LAST_ANIMATION; + } else { + float f = 1 - progress; + if (mAnimationKind == ANIM_KIND_SCROLL) { + progress = 1 - f; // linear + } else if (mAnimationKind == ANIM_KIND_SCALE) { + progress = 1 - f * f; // quadratic + } else /* if mAnimationKind is ANIM_KIND_SNAPBACK, + ANIM_KIND_ZOOM or ANIM_KIND_SLIDE */ { + progress = 1 - f * f * f * f * f; // x^5 + } + linearInterpolate(progress); + } + mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale); + return true; + } + + private void linearInterpolate(float progress) { + // To linearly interpolate the position, we have to translate the + // coordinates. The meaning of the translated point (x, y) is the + // coordinates of the center of the bitmap on the view component. + float fromX = mViewW / 2f + (mImageW / 2f - mFromX) * mFromScale; + float toX = mViewW / 2f + (mImageW / 2f - mToX) * mToScale; + float currentX = fromX + progress * (toX - fromX); + + float fromY = mViewH / 2f + (mImageH / 2f - mFromY) * mFromScale; + float toY = mViewH / 2f + (mImageH / 2f - mToY) * mToScale; + float currentY = fromY + progress * (toY - fromY); + + mCurrentScale = mFromScale + progress * (mToScale - mFromScale); + mCurrentX = Math.round( + mImageW / 2f + (mViewW / 2f - currentX) / mCurrentScale); + mCurrentY = Math.round( + mImageH / 2f + (mViewH / 2f - currentY) / mCurrentScale); + } + + // Returns true if redraw is needed. + private boolean startSnapbackIfNeeded() { + if (mAnimationStartTime != NO_ANIMATION) return false; + if (mInScale) return false; + if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) { + return false; + } + return startSnapback(); + } + + public boolean startSnapback() { + boolean needAnimation = false; + int x = mCurrentX; + int y = mCurrentY; + float scale = mCurrentScale; + + if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) { + needAnimation = true; + scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax); + } + + // The number of pixels when the edge is aligned. + int left = (int) Math.ceil(mViewW / (2 * scale)); + int right = mImageW - left; + int top = (int) Math.ceil(mViewH / (2 * scale)); + int bottom = mImageH - top; + + if (mImageW * scale > mViewW) { + if (mCurrentX < left) { + needAnimation = true; + x = left; + } else if (mCurrentX > right) { + needAnimation = true; + x = right; + } + } else if (mCurrentX != mImageW / 2) { + needAnimation = true; + x = mImageW / 2; + } + + if (mImageH * scale > mViewH) { + if (mCurrentY < top) { + needAnimation = true; + y = top; + } else if (mCurrentY > bottom) { + needAnimation = true; + y = bottom; + } + } else if (mCurrentY != mImageH / 2) { + needAnimation = true; + y = mImageH / 2; + } + + if (needAnimation) { + startAnimation(x, y, scale, ANIM_KIND_SNAPBACK); + } + + return needAnimation; + } + + private float getTargetScale() { + if (mAnimationStartTime == NO_ANIMATION + || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentScale; + return mToScale; + } + + private int getTargetX() { + if (mAnimationStartTime == NO_ANIMATION + || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentX; + return mToX; + } + + private int getTargetY() { + if (mAnimationStartTime == NO_ANIMATION + || mAnimationKind == ANIM_KIND_SNAPBACK) return mCurrentY; + return mToY; + } + + public RectF getImageBounds() { + float points[] = mTempPoints; + + /* + * (p0,p1)----------(p2,p3) + * | | + * | | + * (p4,p5)----------(p6,p7) + */ + points[0] = points[4] = -mCurrentX; + points[1] = points[3] = -mCurrentY; + points[2] = points[6] = mImageW - mCurrentX; + points[5] = points[7] = mImageH - mCurrentY; + + RectF rect = mTempRect; + rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + + float scale = mCurrentScale; + float offsetX = mViewW / 2; + float offsetY = mViewH / 2; + for (int i = 0; i < 4; ++i) { + float x = points[i + i] * scale + offsetX; + float y = points[i + i + 1] * scale + offsetY; + if (x < rect.left) rect.left = x; + if (x > rect.right) rect.right = x; + if (y < rect.top) rect.top = y; + if (y > rect.bottom) rect.bottom = y; + } + return rect; + } + + public int getImageWidth() { + return mImageW; + } + + public int getImageHeight() { + return mImageH; + } +} |
