blob: 236c8aa7228931ad179b9adbb3748357aef536d1 [file] [log] [blame]
Chih-Chung Changec412542011-09-26 17:34:06 +08001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.ui;
18
Chih-Chung Changec412542011-09-26 17:34:06 +080019import com.android.gallery3d.common.Utils;
Chih-Chung Changec412542011-09-26 17:34:06 +080020import com.android.gallery3d.ui.PositionRepository.Position;
Chih-Chung Chang8f568da2012-01-05 12:00:53 +080021import com.android.gallery3d.util.GalleryUtils;
Chih-Chung Changec412542011-09-26 17:34:06 +080022
23import android.content.Context;
Chih-Chung Changec412542011-09-26 17:34:06 +080024import android.graphics.RectF;
Chih-Chung Changec412542011-09-26 17:34:06 +080025import android.os.SystemClock;
Chih-Chung Changfd914132012-02-11 07:19:47 +080026import android.util.FloatMath;
Chih-Chung Changec412542011-09-26 17:34:06 +080027
28class PositionController {
Yuli Huang2ce3c3b2012-02-23 22:26:12 +080029 public static final int IMAGE_AT_LEFT_EDGE = 1;
30 public static final int IMAGE_AT_RIGHT_EDGE = 2;
31 public static final int IMAGE_AT_TOP_EDGE = 4;
32 public static final int IMAGE_AT_BOTTOM_EDGE = 8;
33
Chih-Chung Changec412542011-09-26 17:34:06 +080034 private long mAnimationStartTime = NO_ANIMATION;
35 private static final long NO_ANIMATION = -1;
36 private static final long LAST_ANIMATION = -2;
37
Chih-Chung Changec412542011-09-26 17:34:06 +080038 private int mAnimationKind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080039 private float mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +080040 private final static int ANIM_KIND_SCROLL = 0;
41 private final static int ANIM_KIND_SCALE = 1;
42 private final static int ANIM_KIND_SNAPBACK = 2;
43 private final static int ANIM_KIND_SLIDE = 3;
44 private final static int ANIM_KIND_ZOOM = 4;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080045 private final static int ANIM_KIND_FLING = 5;
Chih-Chung Changec412542011-09-26 17:34:06 +080046
Chih-Chung Chang676170e2011-09-30 18:33:17 +080047 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
48 private final static int ANIM_TIME[] = {
49 0, // ANIM_KIND_SCROLL
50 50, // ANIM_KIND_SCALE
51 600, // ANIM_KIND_SNAPBACK
52 400, // ANIM_KIND_SLIDE
53 300, // ANIM_KIND_ZOOM
Chih-Chung Changb3aab902011-10-03 21:11:39 +080054 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Chang676170e2011-09-30 18:33:17 +080055 };
56
Chih-Chung Changec412542011-09-26 17:34:06 +080057 // We try to scale up the image to fill the screen. But in order not to
58 // scale too much for small icons, we limit the max up-scaling factor here.
59 private static final float SCALE_LIMIT = 4;
Chih-Chung Chang8f568da2012-01-05 12:00:53 +080060 private static final int sHorizontalSlack = GalleryUtils.dpToPixel(12);
Chih-Chung Changec412542011-09-26 17:34:06 +080061
62 private PhotoView mViewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080063 private EdgeView mEdgeView;
Chih-Chung Changec412542011-09-26 17:34:06 +080064 private int mImageW, mImageH;
65 private int mViewW, mViewH;
66
67 // The X, Y are the coordinate on bitmap which shows on the center of
Chih-Chung Chang676170e2011-09-30 18:33:17 +080068 // the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
Chih-Chung Changec412542011-09-26 17:34:06 +080069 // values used currently.
70 private int mCurrentX, mFromX, mToX;
71 private int mCurrentY, mFromY, mToY;
72 private float mCurrentScale, mFromScale, mToScale;
73
Chih-Chung Chang676170e2011-09-30 18:33:17 +080074 // The focus point of the scaling gesture (in bitmap coordinates).
Chih-Chung Changb3aab902011-10-03 21:11:39 +080075 private int mFocusBitmapX;
76 private int mFocusBitmapY;
Chih-Chung Changec412542011-09-26 17:34:06 +080077 private boolean mInScale;
Chih-Chung Changec412542011-09-26 17:34:06 +080078
Chih-Chung Chang676170e2011-09-30 18:33:17 +080079 // The minimum and maximum scale we allow.
80 private float mScaleMin, mScaleMax = SCALE_LIMIT;
81
Chih-Chung Changb3aab902011-10-03 21:11:39 +080082 // This is used by the fling animation
83 private FlingScroller mScroller;
84
85 // The bound of the stable region, see the comments above
86 // calculateStableBound() for details.
87 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
88
Chih-Chung Chang676170e2011-09-30 18:33:17 +080089 // Assume the image size is the same as view size before we know the actual
90 // size of image.
91 private boolean mUseViewSize = true;
Chih-Chung Changec412542011-09-26 17:34:06 +080092
93 private RectF mTempRect = new RectF();
94 private float[] mTempPoints = new float[8];
95
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080096 public PositionController(PhotoView viewer, Context context,
97 EdgeView edgeView) {
Chih-Chung Changec412542011-09-26 17:34:06 +080098 mViewer = viewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080099 mEdgeView = edgeView;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800100 mScroller = new FlingScroller();
Chih-Chung Changec412542011-09-26 17:34:06 +0800101 }
102
103 public void setImageSize(int width, int height) {
104
105 // If no image available, use view size.
106 if (width == 0 || height == 0) {
107 mUseViewSize = true;
108 mImageW = mViewW;
109 mImageH = mViewH;
110 mCurrentX = mImageW / 2;
111 mCurrentY = mImageH / 2;
112 mCurrentScale = 1;
113 mScaleMin = 1;
114 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
115 return;
116 }
117
118 mUseViewSize = false;
119
120 float ratio = Math.min(
121 (float) mImageW / width, (float) mImageH / height);
122
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800123 // See the comment above translate() for details.
Chih-Chung Changec412542011-09-26 17:34:06 +0800124 mCurrentX = translate(mCurrentX, mImageW, width, ratio);
125 mCurrentY = translate(mCurrentY, mImageH, height, ratio);
126 mCurrentScale = mCurrentScale * ratio;
127
128 mFromX = translate(mFromX, mImageW, width, ratio);
129 mFromY = translate(mFromY, mImageH, height, ratio);
130 mFromScale = mFromScale * ratio;
131
132 mToX = translate(mToX, mImageW, width, ratio);
133 mToY = translate(mToY, mImageH, height, ratio);
134 mToScale = mToScale * ratio;
135
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800136 mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
137 mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
138
Chih-Chung Changec412542011-09-26 17:34:06 +0800139 mImageW = width;
140 mImageH = height;
141
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800142 mScaleMin = getMinimalScale(mImageW, mImageH);
Chih-Chung Changec412542011-09-26 17:34:06 +0800143
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800144 // Start animation from the saved position if we have one.
145 Position position = mViewer.retrieveSavedPosition();
Chih-Chung Changec412542011-09-26 17:34:06 +0800146 if (position != null) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800147 // The animation starts from 240 pixels and centers at the image
148 // at the saved position.
Chih-Chung Changec412542011-09-26 17:34:06 +0800149 float scale = 240f / Math.min(width, height);
150 mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2;
151 mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2;
152 mCurrentScale = scale;
153 mViewer.openAnimationStarted();
154 startSnapback();
155 } else if (mAnimationStartTime == NO_ANIMATION) {
156 mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
157 }
158 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
159 }
160
161 public void zoomIn(float tapX, float tapY, float targetScale) {
162 if (targetScale > mScaleMax) targetScale = mScaleMax;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800163
164 // Convert the tap position to image coordinate
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800165 int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
166 int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
Chih-Chung Changec412542011-09-26 17:34:06 +0800167
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800168 calculateStableBound(targetScale);
169 int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
170 int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800171
172 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
173 }
174
175 public void resetToFullView() {
176 startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
177 }
178
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800179 public float getMinimalScale(int w, int h) {
180 return Math.min(SCALE_LIMIT,
181 Math.min((float) mViewW / w, (float) mViewH / h));
Chih-Chung Changec412542011-09-26 17:34:06 +0800182 }
183
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800184 // Translate a coordinate on bitmap if the bitmap size changes.
185 // If the aspect ratio doesn't change, it's easy:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800186 //
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800187 // r = w / w' (= h / h')
188 // x' = x / r
189 // y' = y / r
190 //
191 // However the aspect ratio may change. That happens when the user slides
192 // a image before it's loaded, we don't know the actual aspect ratio, so
193 // we will assume one. When we receive the actual bitmap size, we need to
194 // translate the coordinate from the old bitmap into the new bitmap.
195 //
196 // What we want to do is center the bitmap at the original position.
197 //
198 // ...+--+...
199 // . | | .
200 // . | | .
201 // ...+--+...
202 //
203 // First we scale down the new bitmap by a factor r = min(w/w', h/h').
204 // Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
205 // to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
206 // the old bitmap maps to (x', y') in the new bitmap, where
207 // x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
208 // y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800209 private static int translate(int value, int size, int newSize, float ratio) {
210 return Math.round(newSize / 2f + (value - size / 2f) / ratio);
Chih-Chung Changec412542011-09-26 17:34:06 +0800211 }
212
213 public void setViewSize(int viewW, int viewH) {
214 boolean needLayout = mViewW == 0 || mViewH == 0;
215
216 mViewW = viewW;
217 mViewH = viewH;
218
219 if (mUseViewSize) {
220 mImageW = viewW;
221 mImageH = viewH;
222 mCurrentX = mImageW / 2;
223 mCurrentY = mImageH / 2;
224 mCurrentScale = 1;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800225 mScaleMin = 1;
Chih-Chung Changec412542011-09-26 17:34:06 +0800226 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800227 return;
228 }
229
230 // In most cases we want to keep the scaling factor intact when the
231 // view size changes. The cases we want to reset the scaling factor
232 // (to fit the view if possible) are (1) the scaling factor is too
233 // small for the new view size (2) the scaling factor has not been
234 // changed by the user.
235 boolean wasMinScale = (mCurrentScale == mScaleMin);
236 mScaleMin = getMinimalScale(mImageW, mImageH);
237
238 if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
239 mCurrentX = mImageW / 2;
240 mCurrentY = mImageH / 2;
241 mCurrentScale = mScaleMin;
242 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800243 }
244 }
245
246 public void stopAnimation() {
247 mAnimationStartTime = NO_ANIMATION;
248 }
249
250 public void skipAnimation() {
251 if (mAnimationStartTime == NO_ANIMATION) return;
252 mAnimationStartTime = NO_ANIMATION;
253 mCurrentX = mToX;
254 mCurrentY = mToY;
255 mCurrentScale = mToScale;
256 }
257
Chih-Chung Changec412542011-09-26 17:34:06 +0800258 public void beginScale(float focusX, float focusY) {
259 mInScale = true;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800260 mFocusBitmapX = Math.round(mCurrentX +
261 (focusX - mViewW / 2f) / mCurrentScale);
262 mFocusBitmapY = Math.round(mCurrentY +
263 (focusY - mViewH / 2f) / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800264 }
265
266 public void scaleBy(float s, float focusX, float focusY) {
267
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800268 // We want to keep the focus point (on the bitmap) the same as when
269 // we begin the scale guesture, that is,
270 //
271 // mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
272 //
273 s *= getTargetScale();
274 int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
275 int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
Chih-Chung Changec412542011-09-26 17:34:06 +0800276
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800277 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800278 }
279
280 public void endScale() {
281 mInScale = false;
282 startSnapbackIfNeeded();
283 }
284
285 public float getCurrentScale() {
286 return mCurrentScale;
287 }
288
289 public boolean isAtMinimalScale() {
290 return isAlmostEquals(mCurrentScale, mScaleMin);
291 }
292
293 private static boolean isAlmostEquals(float a, float b) {
294 float diff = a - b;
295 return (diff < 0 ? -diff : diff) < 0.02f;
296 }
297
298 public void up() {
299 startSnapback();
300 }
301
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800302 // |<--| (1/2) * mImageW
303 // +-------+-------+-------+
304 // | | | |
305 // | | o | |
306 // | | | |
307 // +-------+-------+-------+
308 // |<----------| (3/2) * mImageW
309 // Slide in the image from left or right.
310 // Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
311 // Sliding from left: mCurrentX = (1/2) * mImageW
312 // right: mCurrentX = (3/2) * mImageW
Chih-Chung Changec412542011-09-26 17:34:06 +0800313 public void startSlideInAnimation(int direction) {
314 int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800315 mImageW / 2 : 3 * mImageW / 2;
316 mFromX = Math.round(fromX);
Chih-Chung Changec412542011-09-26 17:34:06 +0800317 mFromY = Math.round(mImageH / 2f);
318 mCurrentX = mFromX;
319 mCurrentY = mFromY;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800320 startAnimation(
321 mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800322 }
323
324 public void startHorizontalSlide(int distance) {
325 scrollBy(distance, 0, ANIM_KIND_SLIDE);
326 }
327
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800328 private void scrollBy(float dx, float dy, int type) {
329 startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
330 getTargetY() + Math.round(dy / mCurrentScale),
331 mCurrentScale, type);
332 }
333
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800334 public void startScroll(float dx, float dy, boolean hasNext,
335 boolean hasPrev) {
336 int x = getTargetX() + Math.round(dx / mCurrentScale);
337 int y = getTargetY() + Math.round(dy / mCurrentScale);
338
339 calculateStableBound(mCurrentScale);
340
341 // Vertical direction: If we have space to move in the vertical
342 // direction, we show the edge effect when scrolling reaches the edge.
343 if (mBoundTop != mBoundBottom) {
344 if (y < mBoundTop) {
345 mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
346 } else if (y > mBoundBottom) {
347 mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
348 }
349 }
350
351 y = Utils.clamp(y, mBoundTop, mBoundBottom);
352
353 // Horizontal direction: we show the edge effect when the scrolling
354 // tries to go left of the first image or go right of the last image.
355 if (!hasPrev && x < mBoundLeft) {
356 int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
357 mEdgeView.onPull(pixels, EdgeView.LEFT);
358 x = mBoundLeft;
359 } else if (!hasNext && x > mBoundRight) {
360 int pixels = Math.round((x - mBoundRight) * mCurrentScale);
361 mEdgeView.onPull(pixels, EdgeView.RIGHT);
362 x = mBoundRight;
363 }
364
365 startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
366 }
367
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800368 public boolean fling(float velocityX, float velocityY) {
369 // We only want to do fling when the picture is zoomed-in.
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800370 if (viewWiderThanScaledImage(mCurrentScale) &&
371 viewHigherThanScaledImage(mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800372 return false;
373 }
374
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800375 // We only allow flinging in the directions where it won't go over the
376 // picture.
377 int edges = getImageAtEdges();
378 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
379 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
380 velocityX = 0;
381 }
382 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
383 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
384 velocityY = 0;
385 }
386 if (isAlmostEquals(velocityX, 0) && isAlmostEquals(velocityY, 0)) {
387 return false;
388 }
389
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800390 mScroller.fling(mCurrentX, mCurrentY,
391 Math.round(-velocityX / mCurrentScale),
392 Math.round(-velocityY / mCurrentScale),
393 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
394 int targetX = mScroller.getFinalX();
395 int targetY = mScroller.getFinalY();
396 mAnimationDuration = mScroller.getDuration();
397 startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
398 return true;
399 }
400
Chih-Chung Changec412542011-09-26 17:34:06 +0800401 private void startAnimation(
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800402 int targetX, int targetY, float scale, int kind) {
403 if (targetX == mCurrentX && targetY == mCurrentY
Chih-Chung Changec412542011-09-26 17:34:06 +0800404 && scale == mCurrentScale) return;
405
406 mFromX = mCurrentX;
407 mFromY = mCurrentY;
408 mFromScale = mCurrentScale;
409
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800410 mToX = targetX;
411 mToY = targetY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800412 mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax);
413
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800414 // If the scaled height is smaller than the view height,
Chih-Chung Changec412542011-09-26 17:34:06 +0800415 // force it to be in the center.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800416 // (We do for height only, not width, because the user may
417 // want to scroll to the previous/next image.)
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800418 if (viewHigherThanScaledImage(mToScale)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800419 mToY = mImageH / 2;
420 }
421
422 mAnimationStartTime = SystemClock.uptimeMillis();
423 mAnimationKind = kind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800424 if (mAnimationKind != ANIM_KIND_FLING) {
425 mAnimationDuration = ANIM_TIME[mAnimationKind];
426 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800427 if (advanceAnimation()) mViewer.invalidate();
428 }
429
430 // Returns true if redraw is needed.
431 public boolean advanceAnimation() {
432 if (mAnimationStartTime == NO_ANIMATION) {
433 return false;
434 } else if (mAnimationStartTime == LAST_ANIMATION) {
435 mAnimationStartTime = NO_ANIMATION;
436 if (mViewer.isInTransition()) {
437 mViewer.notifyTransitionComplete();
438 return false;
439 } else {
440 return startSnapbackIfNeeded();
441 }
442 }
443
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800444 long now = SystemClock.uptimeMillis();
Chih-Chung Changec412542011-09-26 17:34:06 +0800445 float progress;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800446 if (mAnimationDuration == 0) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800447 progress = 1;
448 } else {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800449 progress = (now - mAnimationStartTime) / mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +0800450 }
451
452 if (progress >= 1) {
453 progress = 1;
454 mCurrentX = mToX;
455 mCurrentY = mToY;
456 mCurrentScale = mToScale;
457 mAnimationStartTime = LAST_ANIMATION;
458 } else {
459 float f = 1 - progress;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800460 switch (mAnimationKind) {
461 case ANIM_KIND_SCROLL:
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800462 case ANIM_KIND_FLING:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800463 progress = 1 - f; // linear
464 break;
465 case ANIM_KIND_SCALE:
466 progress = 1 - f * f; // quadratic
467 break;
468 case ANIM_KIND_SNAPBACK:
469 case ANIM_KIND_ZOOM:
470 case ANIM_KIND_SLIDE:
471 progress = 1 - f * f * f * f * f; // x^5
472 break;
Chih-Chung Changec412542011-09-26 17:34:06 +0800473 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800474 if (mAnimationKind == ANIM_KIND_FLING) {
475 flingInterpolate(progress);
476 } else {
477 linearInterpolate(progress);
478 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800479 }
480 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
481 return true;
482 }
483
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800484 private void flingInterpolate(float progress) {
485 mScroller.computeScrollOffset(progress);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800486 int oldX = mCurrentX;
487 int oldY = mCurrentY;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800488 mCurrentX = mScroller.getCurrX();
489 mCurrentY = mScroller.getCurrY();
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800490
491 // Check if we hit the edges; show edge effects if we do.
492 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
493 int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
494 mEdgeView.onAbsorb(v, EdgeView.LEFT);
495 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
496 int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
497 mEdgeView.onAbsorb(v, EdgeView.RIGHT);
498 }
499
500 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
501 int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
502 mEdgeView.onAbsorb(v, EdgeView.TOP);
503 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
504 int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
505 mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
506 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800507 }
508
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800509 // Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
Chih-Chung Changec412542011-09-26 17:34:06 +0800510 private void linearInterpolate(float progress) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800511 // To linearly interpolate the position on view coordinates, we do the
512 // following steps:
513 // (1) convert a bitmap position (x, y) to view coordinates:
514 // from: (x - mFromX) * mFromScale + mViewW / 2
515 // to: (x - mToX) * mToScale + mViewW / 2
516 // (2) interpolate between the "from" and "to" coordinates:
517 // (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
518 // + mViewW / 2
519 // should be equal to
520 // (x - mCurrentX) * mCurrentScale + mViewW / 2
521 // (3) The x-related terms in the above equation can be removed because
522 // mFromScale * (1 - p) + ToScale * p = mCurrentScale
523 // (4) Solve for mCurrentX, we have mCurrentX =
524 // (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
525 float fromX = mFromX * mFromScale;
526 float toX = mToX * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800527 float currentX = fromX + progress * (toX - fromX);
528
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800529 float fromY = mFromY * mFromScale;
530 float toY = mToY * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800531 float currentY = fromY + progress * (toY - fromY);
532
533 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800534 mCurrentX = Math.round(currentX / mCurrentScale);
535 mCurrentY = Math.round(currentY / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800536 }
537
538 // Returns true if redraw is needed.
539 private boolean startSnapbackIfNeeded() {
540 if (mAnimationStartTime != NO_ANIMATION) return false;
541 if (mInScale) return false;
542 if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
543 return false;
544 }
545 return startSnapback();
546 }
547
548 public boolean startSnapback() {
549 boolean needAnimation = false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800550 float scale = mCurrentScale;
551
552 if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) {
553 needAnimation = true;
554 scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
555 }
556
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800557 calculateStableBound(scale, sHorizontalSlack);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800558 int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
559 int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800560
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800561 if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800562 needAnimation = true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800563 }
564
565 if (needAnimation) {
566 startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
567 }
568
569 return needAnimation;
570 }
571
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800572 // Calculates the stable region of mCurrent{X/Y}, where "stable" means
573 //
574 // (1) If the dimension of scaled image >= view dimension, we will not
575 // see black region outside the image (at that dimension).
576 // (2) If the dimension of scaled image < view dimension, we will center
577 // the scaled image.
578 //
579 // We might temporarily go out of this stable during user interaction,
580 // but will "snap back" after user stops interaction.
581 //
582 // The results are stored in mBound{Left/Right/Top/Bottom}.
583 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800584 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
585 // is used to extend the stable region by some pixels on each side
586 // horizontally.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800587 private void calculateStableBound(float scale) {
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800588 calculateStableBound(scale, 0f);
589 }
590
591 private void calculateStableBound(float scale, float horizontalSlack) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800592 // The number of pixels between the center of the view
593 // and the edge when the edge is aligned.
Chih-Chung Changfd914132012-02-11 07:19:47 +0800594 mBoundLeft = (int) FloatMath.ceil((mViewW - horizontalSlack) / (2 * scale));
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800595 mBoundRight = mImageW - mBoundLeft;
Chih-Chung Changfd914132012-02-11 07:19:47 +0800596 mBoundTop = (int) FloatMath.ceil(mViewH / (2 * scale));
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800597 mBoundBottom = mImageH - mBoundTop;
598
599 // If the scaled height is smaller than the view height,
600 // force it to be in the center.
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800601 if (viewHigherThanScaledImage(scale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800602 mBoundTop = mBoundBottom = mImageH / 2;
603 }
604
605 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800606 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800607 mBoundLeft = mBoundRight = mImageW / 2;
608 }
609 }
610
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800611 private boolean viewHigherThanScaledImage(float scale) {
612 return FloatMath.floor(mImageH * scale) <= mViewH;
613 }
614
615 private boolean viewWiderThanScaledImage(float scale) {
616 return FloatMath.floor(mImageW * scale) <= mViewW;
617 }
618
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800619 private boolean useCurrentValueAsTarget() {
620 return mAnimationStartTime == NO_ANIMATION ||
621 mAnimationKind == ANIM_KIND_SNAPBACK ||
622 mAnimationKind == ANIM_KIND_FLING;
623 }
624
Chih-Chung Changec412542011-09-26 17:34:06 +0800625 private float getTargetScale() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800626 return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800627 }
628
629 private int getTargetX() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800630 return useCurrentValueAsTarget() ? mCurrentX : mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +0800631 }
632
633 private int getTargetY() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800634 return useCurrentValueAsTarget() ? mCurrentY : mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800635 }
636
637 public RectF getImageBounds() {
638 float points[] = mTempPoints;
639
640 /*
641 * (p0,p1)----------(p2,p3)
642 * | |
643 * | |
644 * (p4,p5)----------(p6,p7)
645 */
646 points[0] = points[4] = -mCurrentX;
647 points[1] = points[3] = -mCurrentY;
648 points[2] = points[6] = mImageW - mCurrentX;
649 points[5] = points[7] = mImageH - mCurrentY;
650
651 RectF rect = mTempRect;
652 rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
653 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
654
655 float scale = mCurrentScale;
656 float offsetX = mViewW / 2;
657 float offsetY = mViewH / 2;
658 for (int i = 0; i < 4; ++i) {
659 float x = points[i + i] * scale + offsetX;
660 float y = points[i + i + 1] * scale + offsetY;
661 if (x < rect.left) rect.left = x;
662 if (x > rect.right) rect.right = x;
663 if (y < rect.top) rect.top = y;
664 if (y > rect.bottom) rect.bottom = y;
665 }
666 return rect;
667 }
668
669 public int getImageWidth() {
670 return mImageW;
671 }
672
673 public int getImageHeight() {
674 return mImageH;
675 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800676
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800677 public int getImageAtEdges() {
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800678 calculateStableBound(mCurrentScale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800679 int edges = 0;
680 if (mCurrentX <= mBoundLeft) {
681 edges |= IMAGE_AT_LEFT_EDGE;
682 }
683 if (mCurrentX >= mBoundRight) {
684 edges |= IMAGE_AT_RIGHT_EDGE;
685 }
686 if (mCurrentY <= mBoundTop) {
687 edges |= IMAGE_AT_TOP_EDGE;
688 }
689 if (mCurrentY >= mBoundBottom) {
690 edges |= IMAGE_AT_BOTTOM_EDGE;
691 }
692 return edges;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800693 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800694}