blob: abffbc58fcf259eff257ea95135baea8f97af45c [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;
24
25import android.content.Context;
26import android.graphics.Bitmap;
27import android.graphics.Color;
28import android.graphics.RectF;
29import android.os.Message;
30import android.os.SystemClock;
31import android.view.GestureDetector;
32import android.view.MotionEvent;
33import android.view.ScaleGestureDetector;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080034import android.widget.Scroller;
Chih-Chung Changec412542011-09-26 17:34:06 +080035
36class PositionController {
Chih-Chung Changb3aab902011-10-03 21:11:39 +080037 private static final String TAG = "PositionController";
Chih-Chung Changec412542011-09-26 17:34:06 +080038 private long mAnimationStartTime = NO_ANIMATION;
39 private static final long NO_ANIMATION = -1;
40 private static final long LAST_ANIMATION = -2;
41
Chih-Chung Changec412542011-09-26 17:34:06 +080042 private int mAnimationKind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080043 private float mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +080044 private final static int ANIM_KIND_SCROLL = 0;
45 private final static int ANIM_KIND_SCALE = 1;
46 private final static int ANIM_KIND_SNAPBACK = 2;
47 private final static int ANIM_KIND_SLIDE = 3;
48 private final static int ANIM_KIND_ZOOM = 4;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080049 private final static int ANIM_KIND_FLING = 5;
Chih-Chung Changec412542011-09-26 17:34:06 +080050
Chih-Chung Chang676170e2011-09-30 18:33:17 +080051 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
52 private final static int ANIM_TIME[] = {
53 0, // ANIM_KIND_SCROLL
54 50, // ANIM_KIND_SCALE
55 600, // ANIM_KIND_SNAPBACK
56 400, // ANIM_KIND_SLIDE
57 300, // ANIM_KIND_ZOOM
Chih-Chung Changb3aab902011-10-03 21:11:39 +080058 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Chang676170e2011-09-30 18:33:17 +080059 };
60
Chih-Chung Changec412542011-09-26 17:34:06 +080061 // We try to scale up the image to fill the screen. But in order not to
62 // scale too much for small icons, we limit the max up-scaling factor here.
63 private static final float SCALE_LIMIT = 4;
64
65 private PhotoView mViewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080066 private EdgeView mEdgeView;
Chih-Chung Changec412542011-09-26 17:34:06 +080067 private int mImageW, mImageH;
68 private int mViewW, mViewH;
69
70 // The X, Y are the coordinate on bitmap which shows on the center of
Chih-Chung Chang676170e2011-09-30 18:33:17 +080071 // the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
Chih-Chung Changec412542011-09-26 17:34:06 +080072 // values used currently.
73 private int mCurrentX, mFromX, mToX;
74 private int mCurrentY, mFromY, mToY;
75 private float mCurrentScale, mFromScale, mToScale;
76
Chih-Chung Chang676170e2011-09-30 18:33:17 +080077 // The focus point of the scaling gesture (in bitmap coordinates).
Chih-Chung Changb3aab902011-10-03 21:11:39 +080078 private int mFocusBitmapX;
79 private int mFocusBitmapY;
Chih-Chung Changec412542011-09-26 17:34:06 +080080 private boolean mInScale;
Chih-Chung Changec412542011-09-26 17:34:06 +080081
Chih-Chung Chang676170e2011-09-30 18:33:17 +080082 // The minimum and maximum scale we allow.
83 private float mScaleMin, mScaleMax = SCALE_LIMIT;
84
Chih-Chung Changb3aab902011-10-03 21:11:39 +080085 // This is used by the fling animation
86 private FlingScroller mScroller;
87
88 // The bound of the stable region, see the comments above
89 // calculateStableBound() for details.
90 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
91
Chih-Chung Chang676170e2011-09-30 18:33:17 +080092 // Assume the image size is the same as view size before we know the actual
93 // size of image.
94 private boolean mUseViewSize = true;
Chih-Chung Changec412542011-09-26 17:34:06 +080095
96 private RectF mTempRect = new RectF();
97 private float[] mTempPoints = new float[8];
98
Chih-Chung Chang532d93c2011-10-12 17:10:33 +080099 public PositionController(PhotoView viewer, Context context,
100 EdgeView edgeView) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800101 mViewer = viewer;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800102 mEdgeView = edgeView;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800103 mScroller = new FlingScroller();
Chih-Chung Changec412542011-09-26 17:34:06 +0800104 }
105
106 public void setImageSize(int width, int height) {
107
108 // If no image available, use view size.
109 if (width == 0 || height == 0) {
110 mUseViewSize = true;
111 mImageW = mViewW;
112 mImageH = mViewH;
113 mCurrentX = mImageW / 2;
114 mCurrentY = mImageH / 2;
115 mCurrentScale = 1;
116 mScaleMin = 1;
117 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
118 return;
119 }
120
121 mUseViewSize = false;
122
123 float ratio = Math.min(
124 (float) mImageW / width, (float) mImageH / height);
125
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800126 // See the comment above translate() for details.
Chih-Chung Changec412542011-09-26 17:34:06 +0800127 mCurrentX = translate(mCurrentX, mImageW, width, ratio);
128 mCurrentY = translate(mCurrentY, mImageH, height, ratio);
129 mCurrentScale = mCurrentScale * ratio;
130
131 mFromX = translate(mFromX, mImageW, width, ratio);
132 mFromY = translate(mFromY, mImageH, height, ratio);
133 mFromScale = mFromScale * ratio;
134
135 mToX = translate(mToX, mImageW, width, ratio);
136 mToY = translate(mToY, mImageH, height, ratio);
137 mToScale = mToScale * ratio;
138
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800139 mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
140 mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
141
Chih-Chung Changec412542011-09-26 17:34:06 +0800142 mImageW = width;
143 mImageH = height;
144
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800145 mScaleMin = getMinimalScale(mImageW, mImageH);
Chih-Chung Changec412542011-09-26 17:34:06 +0800146
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800147 // Start animation from the saved position if we have one.
148 Position position = mViewer.retrieveSavedPosition();
Chih-Chung Changec412542011-09-26 17:34:06 +0800149 if (position != null) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800150 // The animation starts from 240 pixels and centers at the image
151 // at the saved position.
Chih-Chung Changec412542011-09-26 17:34:06 +0800152 float scale = 240f / Math.min(width, height);
153 mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2;
154 mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2;
155 mCurrentScale = scale;
156 mViewer.openAnimationStarted();
157 startSnapback();
158 } else if (mAnimationStartTime == NO_ANIMATION) {
159 mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
160 }
161 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
162 }
163
164 public void zoomIn(float tapX, float tapY, float targetScale) {
165 if (targetScale > mScaleMax) targetScale = mScaleMax;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800166
167 // Convert the tap position to image coordinate
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800168 int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
169 int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
Chih-Chung Changec412542011-09-26 17:34:06 +0800170
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800171 calculateStableBound(targetScale);
172 int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
173 int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800174
175 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
176 }
177
178 public void resetToFullView() {
179 startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
180 }
181
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800182 public float getMinimalScale(int w, int h) {
183 return Math.min(SCALE_LIMIT,
184 Math.min((float) mViewW / w, (float) mViewH / h));
Chih-Chung Changec412542011-09-26 17:34:06 +0800185 }
186
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800187 // Translate a coordinate on bitmap if the bitmap size changes.
188 // If the aspect ratio doesn't change, it's easy:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800189 //
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800190 // r = w / w' (= h / h')
191 // x' = x / r
192 // y' = y / r
193 //
194 // However the aspect ratio may change. That happens when the user slides
195 // a image before it's loaded, we don't know the actual aspect ratio, so
196 // we will assume one. When we receive the actual bitmap size, we need to
197 // translate the coordinate from the old bitmap into the new bitmap.
198 //
199 // What we want to do is center the bitmap at the original position.
200 //
201 // ...+--+...
202 // . | | .
203 // . | | .
204 // ...+--+...
205 //
206 // First we scale down the new bitmap by a factor r = min(w/w', h/h').
207 // Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
208 // to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
209 // the old bitmap maps to (x', y') in the new bitmap, where
210 // x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
211 // y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800212 private static int translate(int value, int size, int newSize, float ratio) {
213 return Math.round(newSize / 2f + (value - size / 2f) / ratio);
Chih-Chung Changec412542011-09-26 17:34:06 +0800214 }
215
216 public void setViewSize(int viewW, int viewH) {
217 boolean needLayout = mViewW == 0 || mViewH == 0;
218
219 mViewW = viewW;
220 mViewH = viewH;
221
222 if (mUseViewSize) {
223 mImageW = viewW;
224 mImageH = viewH;
225 mCurrentX = mImageW / 2;
226 mCurrentY = mImageH / 2;
227 mCurrentScale = 1;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800228 mScaleMin = 1;
Chih-Chung Changec412542011-09-26 17:34:06 +0800229 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800230 return;
231 }
232
233 // In most cases we want to keep the scaling factor intact when the
234 // view size changes. The cases we want to reset the scaling factor
235 // (to fit the view if possible) are (1) the scaling factor is too
236 // small for the new view size (2) the scaling factor has not been
237 // changed by the user.
238 boolean wasMinScale = (mCurrentScale == mScaleMin);
239 mScaleMin = getMinimalScale(mImageW, mImageH);
240
241 if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
242 mCurrentX = mImageW / 2;
243 mCurrentY = mImageH / 2;
244 mCurrentScale = mScaleMin;
245 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800246 }
247 }
248
249 public void stopAnimation() {
250 mAnimationStartTime = NO_ANIMATION;
251 }
252
253 public void skipAnimation() {
254 if (mAnimationStartTime == NO_ANIMATION) return;
255 mAnimationStartTime = NO_ANIMATION;
256 mCurrentX = mToX;
257 mCurrentY = mToY;
258 mCurrentScale = mToScale;
259 }
260
Chih-Chung Changec412542011-09-26 17:34:06 +0800261 public void beginScale(float focusX, float focusY) {
262 mInScale = true;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800263 mFocusBitmapX = Math.round(mCurrentX +
264 (focusX - mViewW / 2f) / mCurrentScale);
265 mFocusBitmapY = Math.round(mCurrentY +
266 (focusY - mViewH / 2f) / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800267 }
268
269 public void scaleBy(float s, float focusX, float focusY) {
270
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800271 // We want to keep the focus point (on the bitmap) the same as when
272 // we begin the scale guesture, that is,
273 //
274 // mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
275 //
276 s *= getTargetScale();
277 int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
278 int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
Chih-Chung Changec412542011-09-26 17:34:06 +0800279
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800280 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800281 }
282
283 public void endScale() {
284 mInScale = false;
285 startSnapbackIfNeeded();
286 }
287
288 public float getCurrentScale() {
289 return mCurrentScale;
290 }
291
292 public boolean isAtMinimalScale() {
293 return isAlmostEquals(mCurrentScale, mScaleMin);
294 }
295
296 private static boolean isAlmostEquals(float a, float b) {
297 float diff = a - b;
298 return (diff < 0 ? -diff : diff) < 0.02f;
299 }
300
301 public void up() {
302 startSnapback();
303 }
304
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800305 // |<--| (1/2) * mImageW
306 // +-------+-------+-------+
307 // | | | |
308 // | | o | |
309 // | | | |
310 // +-------+-------+-------+
311 // |<----------| (3/2) * mImageW
312 // Slide in the image from left or right.
313 // Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
314 // Sliding from left: mCurrentX = (1/2) * mImageW
315 // right: mCurrentX = (3/2) * mImageW
Chih-Chung Changec412542011-09-26 17:34:06 +0800316 public void startSlideInAnimation(int direction) {
317 int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800318 mImageW / 2 : 3 * mImageW / 2;
319 mFromX = Math.round(fromX);
Chih-Chung Changec412542011-09-26 17:34:06 +0800320 mFromY = Math.round(mImageH / 2f);
321 mCurrentX = mFromX;
322 mCurrentY = mFromY;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800323 startAnimation(
324 mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
Chih-Chung Changec412542011-09-26 17:34:06 +0800325 }
326
327 public void startHorizontalSlide(int distance) {
328 scrollBy(distance, 0, ANIM_KIND_SLIDE);
329 }
330
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800331 private void scrollBy(float dx, float dy, int type) {
332 startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
333 getTargetY() + Math.round(dy / mCurrentScale),
334 mCurrentScale, type);
335 }
336
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800337 public void startScroll(float dx, float dy, boolean hasNext,
338 boolean hasPrev) {
339 int x = getTargetX() + Math.round(dx / mCurrentScale);
340 int y = getTargetY() + Math.round(dy / mCurrentScale);
341
342 calculateStableBound(mCurrentScale);
343
344 // Vertical direction: If we have space to move in the vertical
345 // direction, we show the edge effect when scrolling reaches the edge.
346 if (mBoundTop != mBoundBottom) {
347 if (y < mBoundTop) {
348 mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
349 } else if (y > mBoundBottom) {
350 mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
351 }
352 }
353
354 y = Utils.clamp(y, mBoundTop, mBoundBottom);
355
356 // Horizontal direction: we show the edge effect when the scrolling
357 // tries to go left of the first image or go right of the last image.
358 if (!hasPrev && x < mBoundLeft) {
359 int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
360 mEdgeView.onPull(pixels, EdgeView.LEFT);
361 x = mBoundLeft;
362 } else if (!hasNext && x > mBoundRight) {
363 int pixels = Math.round((x - mBoundRight) * mCurrentScale);
364 mEdgeView.onPull(pixels, EdgeView.RIGHT);
365 x = mBoundRight;
366 }
367
368 startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
369 }
370
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800371 public boolean fling(float velocityX, float velocityY) {
372 // We only want to do fling when the picture is zoomed-in.
373 if (mImageW * mCurrentScale <= mViewW &&
374 mImageH * mCurrentScale <= mViewH) {
375 return false;
376 }
377
378 calculateStableBound(mCurrentScale);
379 mScroller.fling(mCurrentX, mCurrentY,
380 Math.round(-velocityX / mCurrentScale),
381 Math.round(-velocityY / mCurrentScale),
382 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
383 int targetX = mScroller.getFinalX();
384 int targetY = mScroller.getFinalY();
385 mAnimationDuration = mScroller.getDuration();
386 startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
387 return true;
388 }
389
Chih-Chung Changec412542011-09-26 17:34:06 +0800390 private void startAnimation(
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800391 int targetX, int targetY, float scale, int kind) {
392 if (targetX == mCurrentX && targetY == mCurrentY
Chih-Chung Changec412542011-09-26 17:34:06 +0800393 && scale == mCurrentScale) return;
394
395 mFromX = mCurrentX;
396 mFromY = mCurrentY;
397 mFromScale = mCurrentScale;
398
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800399 mToX = targetX;
400 mToY = targetY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800401 mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax);
402
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800403 // If the scaled height is smaller than the view height,
Chih-Chung Changec412542011-09-26 17:34:06 +0800404 // force it to be in the center.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800405 // (We do for height only, not width, because the user may
406 // want to scroll to the previous/next image.)
Chih-Chung Changec412542011-09-26 17:34:06 +0800407 if (Math.floor(mImageH * mToScale) <= mViewH) {
408 mToY = mImageH / 2;
409 }
410
411 mAnimationStartTime = SystemClock.uptimeMillis();
412 mAnimationKind = kind;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800413 if (mAnimationKind != ANIM_KIND_FLING) {
414 mAnimationDuration = ANIM_TIME[mAnimationKind];
415 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800416 if (advanceAnimation()) mViewer.invalidate();
417 }
418
419 // Returns true if redraw is needed.
420 public boolean advanceAnimation() {
421 if (mAnimationStartTime == NO_ANIMATION) {
422 return false;
423 } else if (mAnimationStartTime == LAST_ANIMATION) {
424 mAnimationStartTime = NO_ANIMATION;
425 if (mViewer.isInTransition()) {
426 mViewer.notifyTransitionComplete();
427 return false;
428 } else {
429 return startSnapbackIfNeeded();
430 }
431 }
432
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800433 long now = SystemClock.uptimeMillis();
Chih-Chung Changec412542011-09-26 17:34:06 +0800434 float progress;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800435 if (mAnimationDuration == 0) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800436 progress = 1;
437 } else {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800438 progress = (now - mAnimationStartTime) / mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +0800439 }
440
441 if (progress >= 1) {
442 progress = 1;
443 mCurrentX = mToX;
444 mCurrentY = mToY;
445 mCurrentScale = mToScale;
446 mAnimationStartTime = LAST_ANIMATION;
447 } else {
448 float f = 1 - progress;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800449 switch (mAnimationKind) {
450 case ANIM_KIND_SCROLL:
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800451 case ANIM_KIND_FLING:
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800452 progress = 1 - f; // linear
453 break;
454 case ANIM_KIND_SCALE:
455 progress = 1 - f * f; // quadratic
456 break;
457 case ANIM_KIND_SNAPBACK:
458 case ANIM_KIND_ZOOM:
459 case ANIM_KIND_SLIDE:
460 progress = 1 - f * f * f * f * f; // x^5
461 break;
Chih-Chung Changec412542011-09-26 17:34:06 +0800462 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800463 if (mAnimationKind == ANIM_KIND_FLING) {
464 flingInterpolate(progress);
465 } else {
466 linearInterpolate(progress);
467 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800468 }
469 mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
470 return true;
471 }
472
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800473 private void flingInterpolate(float progress) {
474 mScroller.computeScrollOffset(progress);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800475 int oldX = mCurrentX;
476 int oldY = mCurrentY;
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800477 mCurrentX = mScroller.getCurrX();
478 mCurrentY = mScroller.getCurrY();
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800479
480 // Check if we hit the edges; show edge effects if we do.
481 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
482 int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
483 mEdgeView.onAbsorb(v, EdgeView.LEFT);
484 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
485 int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
486 mEdgeView.onAbsorb(v, EdgeView.RIGHT);
487 }
488
489 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
490 int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
491 mEdgeView.onAbsorb(v, EdgeView.TOP);
492 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
493 int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
494 mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
495 }
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800496 }
497
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800498 // Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
Chih-Chung Changec412542011-09-26 17:34:06 +0800499 private void linearInterpolate(float progress) {
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800500 // To linearly interpolate the position on view coordinates, we do the
501 // following steps:
502 // (1) convert a bitmap position (x, y) to view coordinates:
503 // from: (x - mFromX) * mFromScale + mViewW / 2
504 // to: (x - mToX) * mToScale + mViewW / 2
505 // (2) interpolate between the "from" and "to" coordinates:
506 // (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
507 // + mViewW / 2
508 // should be equal to
509 // (x - mCurrentX) * mCurrentScale + mViewW / 2
510 // (3) The x-related terms in the above equation can be removed because
511 // mFromScale * (1 - p) + ToScale * p = mCurrentScale
512 // (4) Solve for mCurrentX, we have mCurrentX =
513 // (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
514 float fromX = mFromX * mFromScale;
515 float toX = mToX * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800516 float currentX = fromX + progress * (toX - fromX);
517
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800518 float fromY = mFromY * mFromScale;
519 float toY = mToY * mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800520 float currentY = fromY + progress * (toY - fromY);
521
522 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800523 mCurrentX = Math.round(currentX / mCurrentScale);
524 mCurrentY = Math.round(currentY / mCurrentScale);
Chih-Chung Changec412542011-09-26 17:34:06 +0800525 }
526
527 // Returns true if redraw is needed.
528 private boolean startSnapbackIfNeeded() {
529 if (mAnimationStartTime != NO_ANIMATION) return false;
530 if (mInScale) return false;
531 if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
532 return false;
533 }
534 return startSnapback();
535 }
536
537 public boolean startSnapback() {
538 boolean needAnimation = false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800539 float scale = mCurrentScale;
540
541 if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) {
542 needAnimation = true;
543 scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
544 }
545
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800546 calculateStableBound(scale);
547 int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
548 int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
Chih-Chung Changec412542011-09-26 17:34:06 +0800549
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800550 if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800551 needAnimation = true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800552 }
553
554 if (needAnimation) {
555 startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
556 }
557
558 return needAnimation;
559 }
560
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800561 // Calculates the stable region of mCurrent{X/Y}, where "stable" means
562 //
563 // (1) If the dimension of scaled image >= view dimension, we will not
564 // see black region outside the image (at that dimension).
565 // (2) If the dimension of scaled image < view dimension, we will center
566 // the scaled image.
567 //
568 // We might temporarily go out of this stable during user interaction,
569 // but will "snap back" after user stops interaction.
570 //
571 // The results are stored in mBound{Left/Right/Top/Bottom}.
572 //
573 private void calculateStableBound(float scale) {
574 // The number of pixels between the center of the view
575 // and the edge when the edge is aligned.
576 mBoundLeft = (int) Math.ceil(mViewW / (2 * scale));
577 mBoundRight = mImageW - mBoundLeft;
578 mBoundTop = (int) Math.ceil(mViewH / (2 * scale));
579 mBoundBottom = mImageH - mBoundTop;
580
581 // If the scaled height is smaller than the view height,
582 // force it to be in the center.
583 if (Math.floor(mImageH * scale) <= mViewH) {
584 mBoundTop = mBoundBottom = mImageH / 2;
585 }
586
587 // Same for width
588 if (Math.floor(mImageW * scale) <= mViewW) {
589 mBoundLeft = mBoundRight = mImageW / 2;
590 }
591 }
592
593 private boolean useCurrentValueAsTarget() {
594 return mAnimationStartTime == NO_ANIMATION ||
595 mAnimationKind == ANIM_KIND_SNAPBACK ||
596 mAnimationKind == ANIM_KIND_FLING;
597 }
598
Chih-Chung Changec412542011-09-26 17:34:06 +0800599 private float getTargetScale() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800600 return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +0800601 }
602
603 private int getTargetX() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800604 return useCurrentValueAsTarget() ? mCurrentX : mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +0800605 }
606
607 private int getTargetY() {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800608 return useCurrentValueAsTarget() ? mCurrentY : mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +0800609 }
610
611 public RectF getImageBounds() {
612 float points[] = mTempPoints;
613
614 /*
615 * (p0,p1)----------(p2,p3)
616 * | |
617 * | |
618 * (p4,p5)----------(p6,p7)
619 */
620 points[0] = points[4] = -mCurrentX;
621 points[1] = points[3] = -mCurrentY;
622 points[2] = points[6] = mImageW - mCurrentX;
623 points[5] = points[7] = mImageH - mCurrentY;
624
625 RectF rect = mTempRect;
626 rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
627 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
628
629 float scale = mCurrentScale;
630 float offsetX = mViewW / 2;
631 float offsetY = mViewH / 2;
632 for (int i = 0; i < 4; ++i) {
633 float x = points[i + i] * scale + offsetX;
634 float y = points[i + i + 1] * scale + offsetY;
635 if (x < rect.left) rect.left = x;
636 if (x > rect.right) rect.right = x;
637 if (y < rect.top) rect.top = y;
638 if (y > rect.bottom) rect.bottom = y;
639 }
640 return rect;
641 }
642
643 public int getImageWidth() {
644 return mImageW;
645 }
646
647 public int getImageHeight() {
648 return mImageH;
649 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800650
651 public boolean isAtLeftEdge() {
652 calculateStableBound(mCurrentScale);
653 return mCurrentX <= mBoundLeft;
654 }
655
656 public boolean isAtRightEdge() {
657 calculateStableBound(mCurrentScale);
658 return mCurrentX >= mBoundRight;
659 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800660}