blob: b4dac973fab84250b6073aa02d6b0dab925ba619 [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
19import com.android.gallery3d.R;
20import com.android.gallery3d.app.GalleryActivity;
21import com.android.gallery3d.common.Utils;
22import com.android.gallery3d.data.Path;
23import com.android.gallery3d.ui.PositionRepository.Position;
Chih-Chung Chang8f568da2012-01-05 12:00:53 +080024import com.android.gallery3d.util.GalleryUtils;
Chih-Chung Changec412542011-09-26 17:34:06 +080025
26import android.content.Context;
27import android.graphics.Bitmap;
28import android.graphics.Color;
29import android.graphics.RectF;
30import android.os.Message;
31import android.os.SystemClock;
32import android.view.GestureDetector;
33import android.view.MotionEvent;
34import android.view.ScaleGestureDetector;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080035import android.widget.Scroller;
Chih-Chung Changec412542011-09-26 17:34:06 +080036
37class PositionController {
Chih-Chung Changb3aab902011-10-03 21:11:39 +080038 private static final String TAG = "PositionController";
Chih-Chung Changec412542011-09-26 17:34:06 +080039 private long mAnimationStartTime = NO_ANIMATION;
40 private static final long NO_ANIMATION = -1;
41 private static final long LAST_ANIMATION = -2;
42
Chih-Chung Changec412542011-09-26 17:34:06 +080043 private int mAnimationKind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080044 private float mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +080045 private final static int ANIM_KIND_SCROLL = 0;
46 private final static int ANIM_KIND_SCALE = 1;
47 private final static int ANIM_KIND_SNAPBACK = 2;
48 private final static int ANIM_KIND_SLIDE = 3;
49 private final static int ANIM_KIND_ZOOM = 4;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080050 private final static int ANIM_KIND_FLING = 5;
Chih-Chung Changec412542011-09-26 17:34:06 +080051
Chih-Chung Chang676170e2011-09-30 18:33:17 +080052 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
53 private final static int ANIM_TIME[] = {
54 0, // ANIM_KIND_SCROLL
55 50, // ANIM_KIND_SCALE
56 600, // ANIM_KIND_SNAPBACK
57 400, // ANIM_KIND_SLIDE
58 300, // ANIM_KIND_ZOOM
Chih-Chung Changb3aab902011-10-03 21:11:39 +080059 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Chang676170e2011-09-30 18:33:17 +080060 };
61
Chih-Chung Changec412542011-09-26 17:34:06 +080062 // We try to scale up the image to fill the screen. But in order not to
63 // scale too much for small icons, we limit the max up-scaling factor here.
64 private static final float SCALE_LIMIT = 4;
Chih-Chung Chang8f568da2012-01-05 12:00:53 +080065 private static final int sHorizontalSlack = GalleryUtils.dpToPixel(12);
Chih-Chung Changec412542011-09-26 17:34:06 +080066
67 private PhotoView mViewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080068 private EdgeView mEdgeView;
Chih-Chung Changec412542011-09-26 17:34:06 +080069 private int mImageW, mImageH;
70 private int mViewW, mViewH;
71
72 // The X, Y are the coordinate on bitmap which shows on the center of
Chih-Chung Chang676170e2011-09-30 18:33:17 +080073 // the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
Chih-Chung Changec412542011-09-26 17:34:06 +080074 // values used currently.
75 private int mCurrentX, mFromX, mToX;
76 private int mCurrentY, mFromY, mToY;
77 private float mCurrentScale, mFromScale, mToScale;
78
Chih-Chung Chang676170e2011-09-30 18:33:17 +080079 // The focus point of the scaling gesture (in bitmap coordinates).
Chih-Chung Changb3aab902011-10-03 21:11:39 +080080 private int mFocusBitmapX;
81 private int mFocusBitmapY;
Chih-Chung Changec412542011-09-26 17:34:06 +080082 private boolean mInScale;
Chih-Chung Changec412542011-09-26 17:34:06 +080083
Chih-Chung Chang676170e2011-09-30 18:33:17 +080084 // The minimum and maximum scale we allow.
85 private float mScaleMin, mScaleMax = SCALE_LIMIT;
86
Chih-Chung Changb3aab902011-10-03 21:11:39 +080087 // This is used by the fling animation
88 private FlingScroller mScroller;
89
90 // The bound of the stable region, see the comments above
91 // calculateStableBound() for details.
92 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
93
Chih-Chung Chang676170e2011-09-30 18:33:17 +080094 // Assume the image size is the same as view size before we know the actual
95 // size of image.
96 private boolean mUseViewSize = true;
Chih-Chung Changec412542011-09-26 17:34:06 +080097
98 private RectF mTempRect = new RectF();
99 private float[] mTempPoints = new float[8];
100
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800101 public PositionController(PhotoView viewer, Context context,
102 EdgeView edgeView) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800103 mViewer = viewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800104 mEdgeView = edgeView;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800105 mScroller = new FlingScroller();
Chih-Chung Changec412542011-09-26 17:34:06 +0800106 }
107
108 public void setImageSize(int width, int height) {
109
110 // If no image available, use view size.
111 if (width == 0 || height == 0) {
112 mUseViewSize = true;
113 mImageW = mViewW;
114 mImageH = mViewH;
115 mCurrentX = mImageW / 2;
116 mCurrentY = mImageH / 2;
117 mCurrentScale = 1;
118 mScaleMin = 1;
119 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
120 return;
121 }
122
123 mUseViewSize = false;
124
125 float ratio = Math.min(
126 (float) mImageW / width, (float) mImageH / height);
127
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800128 // See the comment above translate() for details.
Chih-Chung Changec412542011-09-26 17:34:06 +0800129 mCurrentX = translate(mCurrentX, mImageW, width, ratio);
130 mCurrentY = translate(mCurrentY, mImageH, height, ratio);
131 mCurrentScale = mCurrentScale * ratio;
132
133 mFromX = translate(mFromX, mImageW, width, ratio);
134 mFromY = translate(mFromY, mImageH, height, ratio);
135 mFromScale = mFromScale * ratio;
136
137 mToX = translate(mToX, mImageW, width, ratio);
138 mToY = translate(mToY, mImageH, height, ratio);
139 mToScale = mToScale * ratio;
140
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800141 mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
142 mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
143
Chih-Chung Changec412542011-09-26 17:34:06 +0800144 mImageW = width;
145 mImageH = height;
146
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800147 mScaleMin = getMinimalScale(mImageW, mImageH);
Chih-Chung Changec412542011-09-26 17:34:06 +0800148
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800149 // Start animation from the saved position if we have one.
150 Position position = mViewer.retrieveSavedPosition();
Chih-Chung Changec412542011-09-26 17:34:06 +0800151 if (position != null) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800152 // The animation starts from 240 pixels and centers at the image
153 // at the saved position.
Chih-Chung Changec412542011-09-26 17:34:06 +0800154 float scale = 240f / Math.min(width, height);
155 mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2;
156 mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2;
157 mCurrentScale = scale;
158 mViewer.openAnimationStarted();
159 startSnapback();
160 } else if (mAnimationStartTime == NO_ANIMATION) {
161 mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
162 }
163 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
164 }
165
166 public void zoomIn(float tapX, float tapY, float targetScale) {
167 if (targetScale > mScaleMax) targetScale = mScaleMax;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800168
169 // Convert the tap position to image coordinate
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800170 int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
171 int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
Chih-Chung Changec412542011-09-26 17:34:06 +0800172
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800173 calculateStableBound(targetScale);
174 int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
175 int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800176
177 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
178 }
179
180 public void resetToFullView() {
181 startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
182 }
183
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800184 public float getMinimalScale(int w, int h) {
185 return Math.min(SCALE_LIMIT,
186 Math.min((float) mViewW / w, (float) mViewH / h));
Chih-Chung Changec412542011-09-26 17:34:06 +0800187 }
188
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800189 // Translate a coordinate on bitmap if the bitmap size changes.
190 // If the aspect ratio doesn't change, it's easy:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800191 //
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800192 // r = w / w' (= h / h')
193 // x' = x / r
194 // y' = y / r
195 //
196 // However the aspect ratio may change. That happens when the user slides
197 // a image before it's loaded, we don't know the actual aspect ratio, so
198 // we will assume one. When we receive the actual bitmap size, we need to
199 // translate the coordinate from the old bitmap into the new bitmap.
200 //
201 // What we want to do is center the bitmap at the original position.
202 //
203 // ...+--+...
204 // . | | .
205 // . | | .
206 // ...+--+...
207 //
208 // First we scale down the new bitmap by a factor r = min(w/w', h/h').
209 // Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
210 // to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
211 // the old bitmap maps to (x', y') in the new bitmap, where
212 // x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
213 // y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800214 private static int translate(int value, int size, int newSize, float ratio) {
215 return Math.round(newSize / 2f + (value - size / 2f) / ratio);
Chih-Chung Changec412542011-09-26 17:34:06 +0800216 }
217
218 public void setViewSize(int viewW, int viewH) {
219 boolean needLayout = mViewW == 0 || mViewH == 0;
220
221 mViewW = viewW;
222 mViewH = viewH;
223
224 if (mUseViewSize) {
225 mImageW = viewW;
226 mImageH = viewH;
227 mCurrentX = mImageW / 2;
228 mCurrentY = mImageH / 2;
229 mCurrentScale = 1;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800230 mScaleMin = 1;
Chih-Chung Changec412542011-09-26 17:34:06 +0800231 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800232 return;
233 }
234
235 // In most cases we want to keep the scaling factor intact when the
236 // view size changes. The cases we want to reset the scaling factor
237 // (to fit the view if possible) are (1) the scaling factor is too
238 // small for the new view size (2) the scaling factor has not been
239 // changed by the user.
240 boolean wasMinScale = (mCurrentScale == mScaleMin);
241 mScaleMin = getMinimalScale(mImageW, mImageH);
242
243 if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
244 mCurrentX = mImageW / 2;
245 mCurrentY = mImageH / 2;
246 mCurrentScale = mScaleMin;
247 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800248 }
249 }
250
251 public void stopAnimation() {
252 mAnimationStartTime = NO_ANIMATION;
253 }
254
255 public void skipAnimation() {
256 if (mAnimationStartTime == NO_ANIMATION) return;
257 mAnimationStartTime = NO_ANIMATION;
258 mCurrentX = mToX;
259 mCurrentY = mToY;
260 mCurrentScale = mToScale;
261 }
262
Chih-Chung Changec412542011-09-26 17:34:06 +0800263 public void beginScale(float focusX, float focusY) {
264 mInScale = true;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800265 mFocusBitmapX = Math.round(mCurrentX +
266 (focusX - mViewW / 2f) / mCurrentScale);
267 mFocusBitmapY = Math.round(mCurrentY +
268 (focusY - mViewH / 2f) / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800269 }
270
271 public void scaleBy(float s, float focusX, float focusY) {
272
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800273 // We want to keep the focus point (on the bitmap) the same as when
274 // we begin the scale guesture, that is,
275 //
276 // mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
277 //
278 s *= getTargetScale();
279 int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
280 int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
Chih-Chung Changec412542011-09-26 17:34:06 +0800281
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800282 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800283 }
284
285 public void endScale() {
286 mInScale = false;
287 startSnapbackIfNeeded();
288 }
289
290 public float getCurrentScale() {
291 return mCurrentScale;
292 }
293
294 public boolean isAtMinimalScale() {
295 return isAlmostEquals(mCurrentScale, mScaleMin);
296 }
297
298 private static boolean isAlmostEquals(float a, float b) {
299 float diff = a - b;
300 return (diff < 0 ? -diff : diff) < 0.02f;
301 }
302
303 public void up() {
304 startSnapback();
305 }
306
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800307 // |<--| (1/2) * mImageW
308 // +-------+-------+-------+
309 // | | | |
310 // | | o | |
311 // | | | |
312 // +-------+-------+-------+
313 // |<----------| (3/2) * mImageW
314 // Slide in the image from left or right.
315 // Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
316 // Sliding from left: mCurrentX = (1/2) * mImageW
317 // right: mCurrentX = (3/2) * mImageW
Chih-Chung Changec412542011-09-26 17:34:06 +0800318 public void startSlideInAnimation(int direction) {
319 int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800320 mImageW / 2 : 3 * mImageW / 2;
321 mFromX = Math.round(fromX);
Chih-Chung Changec412542011-09-26 17:34:06 +0800322 mFromY = Math.round(mImageH / 2f);
323 mCurrentX = mFromX;
324 mCurrentY = mFromY;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800325 startAnimation(
326 mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800327 }
328
329 public void startHorizontalSlide(int distance) {
330 scrollBy(distance, 0, ANIM_KIND_SLIDE);
331 }
332
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800333 private void scrollBy(float dx, float dy, int type) {
334 startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
335 getTargetY() + Math.round(dy / mCurrentScale),
336 mCurrentScale, type);
337 }
338
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800339 public void startScroll(float dx, float dy, boolean hasNext,
340 boolean hasPrev) {
341 int x = getTargetX() + Math.round(dx / mCurrentScale);
342 int y = getTargetY() + Math.round(dy / mCurrentScale);
343
344 calculateStableBound(mCurrentScale);
345
346 // Vertical direction: If we have space to move in the vertical
347 // direction, we show the edge effect when scrolling reaches the edge.
348 if (mBoundTop != mBoundBottom) {
349 if (y < mBoundTop) {
350 mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
351 } else if (y > mBoundBottom) {
352 mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
353 }
354 }
355
356 y = Utils.clamp(y, mBoundTop, mBoundBottom);
357
358 // Horizontal direction: we show the edge effect when the scrolling
359 // tries to go left of the first image or go right of the last image.
360 if (!hasPrev && x < mBoundLeft) {
361 int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
362 mEdgeView.onPull(pixels, EdgeView.LEFT);
363 x = mBoundLeft;
364 } else if (!hasNext && x > mBoundRight) {
365 int pixels = Math.round((x - mBoundRight) * mCurrentScale);
366 mEdgeView.onPull(pixels, EdgeView.RIGHT);
367 x = mBoundRight;
368 }
369
370 startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
371 }
372
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800373 public boolean fling(float velocityX, float velocityY) {
374 // We only want to do fling when the picture is zoomed-in.
375 if (mImageW * mCurrentScale <= mViewW &&
376 mImageH * mCurrentScale <= mViewH) {
377 return false;
378 }
379
380 calculateStableBound(mCurrentScale);
381 mScroller.fling(mCurrentX, mCurrentY,
382 Math.round(-velocityX / mCurrentScale),
383 Math.round(-velocityY / mCurrentScale),
384 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
385 int targetX = mScroller.getFinalX();
386 int targetY = mScroller.getFinalY();
387 mAnimationDuration = mScroller.getDuration();
388 startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
389 return true;
390 }
391
Chih-Chung Changec412542011-09-26 17:34:06 +0800392 private void startAnimation(
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800393 int targetX, int targetY, float scale, int kind) {
394 if (targetX == mCurrentX && targetY == mCurrentY
Chih-Chung Changec412542011-09-26 17:34:06 +0800395 && scale == mCurrentScale) return;
396
397 mFromX = mCurrentX;
398 mFromY = mCurrentY;
399 mFromScale = mCurrentScale;
400
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800401 mToX = targetX;
402 mToY = targetY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800403 mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax);
404
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800405 // If the scaled height is smaller than the view height,
Chih-Chung Changec412542011-09-26 17:34:06 +0800406 // force it to be in the center.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800407 // (We do for height only, not width, because the user may
408 // want to scroll to the previous/next image.)
Chih-Chung Changec412542011-09-26 17:34:06 +0800409 if (Math.floor(mImageH * mToScale) <= mViewH) {
410 mToY = mImageH / 2;
411 }
412
413 mAnimationStartTime = SystemClock.uptimeMillis();
414 mAnimationKind = kind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800415 if (mAnimationKind != ANIM_KIND_FLING) {
416 mAnimationDuration = ANIM_TIME[mAnimationKind];
417 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800418 if (advanceAnimation()) mViewer.invalidate();
419 }
420
421 // Returns true if redraw is needed.
422 public boolean advanceAnimation() {
423 if (mAnimationStartTime == NO_ANIMATION) {
424 return false;
425 } else if (mAnimationStartTime == LAST_ANIMATION) {
426 mAnimationStartTime = NO_ANIMATION;
427 if (mViewer.isInTransition()) {
428 mViewer.notifyTransitionComplete();
429 return false;
430 } else {
431 return startSnapbackIfNeeded();
432 }
433 }
434
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800435 long now = SystemClock.uptimeMillis();
Chih-Chung Changec412542011-09-26 17:34:06 +0800436 float progress;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800437 if (mAnimationDuration == 0) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800438 progress = 1;
439 } else {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800440 progress = (now - mAnimationStartTime) / mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +0800441 }
442
443 if (progress >= 1) {
444 progress = 1;
445 mCurrentX = mToX;
446 mCurrentY = mToY;
447 mCurrentScale = mToScale;
448 mAnimationStartTime = LAST_ANIMATION;
449 } else {
450 float f = 1 - progress;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800451 switch (mAnimationKind) {
452 case ANIM_KIND_SCROLL:
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800453 case ANIM_KIND_FLING:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800454 progress = 1 - f; // linear
455 break;
456 case ANIM_KIND_SCALE:
457 progress = 1 - f * f; // quadratic
458 break;
459 case ANIM_KIND_SNAPBACK:
460 case ANIM_KIND_ZOOM:
461 case ANIM_KIND_SLIDE:
462 progress = 1 - f * f * f * f * f; // x^5
463 break;
Chih-Chung Changec412542011-09-26 17:34:06 +0800464 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800465 if (mAnimationKind == ANIM_KIND_FLING) {
466 flingInterpolate(progress);
467 } else {
468 linearInterpolate(progress);
469 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800470 }
471 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
472 return true;
473 }
474
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800475 private void flingInterpolate(float progress) {
476 mScroller.computeScrollOffset(progress);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800477 int oldX = mCurrentX;
478 int oldY = mCurrentY;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800479 mCurrentX = mScroller.getCurrX();
480 mCurrentY = mScroller.getCurrY();
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800481
482 // Check if we hit the edges; show edge effects if we do.
483 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
484 int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
485 mEdgeView.onAbsorb(v, EdgeView.LEFT);
486 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
487 int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
488 mEdgeView.onAbsorb(v, EdgeView.RIGHT);
489 }
490
491 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
492 int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
493 mEdgeView.onAbsorb(v, EdgeView.TOP);
494 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
495 int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
496 mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
497 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800498 }
499
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800500 // Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
Chih-Chung Changec412542011-09-26 17:34:06 +0800501 private void linearInterpolate(float progress) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800502 // To linearly interpolate the position on view coordinates, we do the
503 // following steps:
504 // (1) convert a bitmap position (x, y) to view coordinates:
505 // from: (x - mFromX) * mFromScale + mViewW / 2
506 // to: (x - mToX) * mToScale + mViewW / 2
507 // (2) interpolate between the "from" and "to" coordinates:
508 // (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
509 // + mViewW / 2
510 // should be equal to
511 // (x - mCurrentX) * mCurrentScale + mViewW / 2
512 // (3) The x-related terms in the above equation can be removed because
513 // mFromScale * (1 - p) + ToScale * p = mCurrentScale
514 // (4) Solve for mCurrentX, we have mCurrentX =
515 // (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
516 float fromX = mFromX * mFromScale;
517 float toX = mToX * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800518 float currentX = fromX + progress * (toX - fromX);
519
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800520 float fromY = mFromY * mFromScale;
521 float toY = mToY * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800522 float currentY = fromY + progress * (toY - fromY);
523
524 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800525 mCurrentX = Math.round(currentX / mCurrentScale);
526 mCurrentY = Math.round(currentY / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800527 }
528
529 // Returns true if redraw is needed.
530 private boolean startSnapbackIfNeeded() {
531 if (mAnimationStartTime != NO_ANIMATION) return false;
532 if (mInScale) return false;
533 if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
534 return false;
535 }
536 return startSnapback();
537 }
538
539 public boolean startSnapback() {
540 boolean needAnimation = false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800541 float scale = mCurrentScale;
542
543 if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) {
544 needAnimation = true;
545 scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
546 }
547
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800548 calculateStableBound(scale, sHorizontalSlack);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800549 int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
550 int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800551
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800552 if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800553 needAnimation = true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800554 }
555
556 if (needAnimation) {
557 startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
558 }
559
560 return needAnimation;
561 }
562
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800563 // Calculates the stable region of mCurrent{X/Y}, where "stable" means
564 //
565 // (1) If the dimension of scaled image >= view dimension, we will not
566 // see black region outside the image (at that dimension).
567 // (2) If the dimension of scaled image < view dimension, we will center
568 // the scaled image.
569 //
570 // We might temporarily go out of this stable during user interaction,
571 // but will "snap back" after user stops interaction.
572 //
573 // The results are stored in mBound{Left/Right/Top/Bottom}.
574 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800575 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
576 // is used to extend the stable region by some pixels on each side
577 // horizontally.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800578 private void calculateStableBound(float scale) {
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800579 calculateStableBound(scale, 0f);
580 }
581
582 private void calculateStableBound(float scale, float horizontalSlack) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800583 // The number of pixels between the center of the view
584 // and the edge when the edge is aligned.
Chih-Chung Chang8f568da2012-01-05 12:00:53 +0800585 mBoundLeft = (int) Math.ceil((mViewW - horizontalSlack) / (2 * scale));
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800586 mBoundRight = mImageW - mBoundLeft;
587 mBoundTop = (int) Math.ceil(mViewH / (2 * scale));
588 mBoundBottom = mImageH - mBoundTop;
589
590 // If the scaled height is smaller than the view height,
591 // force it to be in the center.
592 if (Math.floor(mImageH * scale) <= mViewH) {
593 mBoundTop = mBoundBottom = mImageH / 2;
594 }
595
596 // Same for width
597 if (Math.floor(mImageW * scale) <= mViewW) {
598 mBoundLeft = mBoundRight = mImageW / 2;
599 }
600 }
601
602 private boolean useCurrentValueAsTarget() {
603 return mAnimationStartTime == NO_ANIMATION ||
604 mAnimationKind == ANIM_KIND_SNAPBACK ||
605 mAnimationKind == ANIM_KIND_FLING;
606 }
607
Chih-Chung Changec412542011-09-26 17:34:06 +0800608 private float getTargetScale() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800609 return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800610 }
611
612 private int getTargetX() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800613 return useCurrentValueAsTarget() ? mCurrentX : mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +0800614 }
615
616 private int getTargetY() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800617 return useCurrentValueAsTarget() ? mCurrentY : mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800618 }
619
620 public RectF getImageBounds() {
621 float points[] = mTempPoints;
622
623 /*
624 * (p0,p1)----------(p2,p3)
625 * | |
626 * | |
627 * (p4,p5)----------(p6,p7)
628 */
629 points[0] = points[4] = -mCurrentX;
630 points[1] = points[3] = -mCurrentY;
631 points[2] = points[6] = mImageW - mCurrentX;
632 points[5] = points[7] = mImageH - mCurrentY;
633
634 RectF rect = mTempRect;
635 rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
636 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
637
638 float scale = mCurrentScale;
639 float offsetX = mViewW / 2;
640 float offsetY = mViewH / 2;
641 for (int i = 0; i < 4; ++i) {
642 float x = points[i + i] * scale + offsetX;
643 float y = points[i + i + 1] * scale + offsetY;
644 if (x < rect.left) rect.left = x;
645 if (x > rect.right) rect.right = x;
646 if (y < rect.top) rect.top = y;
647 if (y > rect.bottom) rect.bottom = y;
648 }
649 return rect;
650 }
651
652 public int getImageWidth() {
653 return mImageW;
654 }
655
656 public int getImageHeight() {
657 return mImageH;
658 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800659
660 public boolean isAtLeftEdge() {
661 calculateStableBound(mCurrentScale);
662 return mCurrentX <= mBoundLeft;
663 }
664
665 public boolean isAtRightEdge() {
666 calculateStableBound(mCurrentScale);
667 return mCurrentX >= mBoundRight;
668 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800669}