blob: c09ffea0cbdffeed3a3a2d1221210e8202bb69b3 [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
Owen Lin2b3ee0e2012-03-14 17:27:24 +080019import android.content.Context;
Yuli Huang04ac0452012-03-20 16:37:05 +080020import android.graphics.Rect;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080021import android.util.Log;
Chih-Chung Changd8488622012-04-17 12:56:08 +080022import android.widget.OverScroller;
Owen Lin2b3ee0e2012-03-14 17:27:24 +080023
Chih-Chung Changec412542011-09-26 17:34:06 +080024import com.android.gallery3d.common.Utils;
Chih-Chung Chang8f568da2012-01-05 12:00:53 +080025import com.android.gallery3d.util.GalleryUtils;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080026import com.android.gallery3d.util.RangeArray;
27import com.android.gallery3d.util.RangeIntArray;
Chih-Chung Changec412542011-09-26 17:34:06 +080028
Chih-Chung Changec412542011-09-26 17:34:06 +080029class PositionController {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080030 private static final String TAG = "PositionController";
31
Yuli Huang2ce3c3b2012-02-23 22:26:12 +080032 public static final int IMAGE_AT_LEFT_EDGE = 1;
33 public static final int IMAGE_AT_RIGHT_EDGE = 2;
34 public static final int IMAGE_AT_TOP_EDGE = 4;
35 public static final int IMAGE_AT_BOTTOM_EDGE = 8;
36
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080037 // Special values for animation time.
Chih-Chung Changec412542011-09-26 17:34:06 +080038 private static final long NO_ANIMATION = -1;
39 private static final long LAST_ANIMATION = -2;
40
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080041 private static final int ANIM_KIND_SCROLL = 0;
42 private static final int ANIM_KIND_SCALE = 1;
43 private static final int ANIM_KIND_SNAPBACK = 2;
44 private static final int ANIM_KIND_SLIDE = 3;
45 private static final int ANIM_KIND_ZOOM = 4;
46 private static final int ANIM_KIND_OPENING = 5;
47 private static final int ANIM_KIND_FLING = 6;
Chih-Chung Chang2c617382012-04-20 20:06:19 +080048 private static final int ANIM_KIND_CAPTURE = 7;
Chih-Chung Changec412542011-09-26 17:34:06 +080049
Chih-Chung Chang676170e2011-09-30 18:33:17 +080050 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080051 private static final int ANIM_TIME[] = {
Chih-Chung Chang676170e2011-09-30 18:33:17 +080052 0, // ANIM_KIND_SCROLL
53 50, // ANIM_KIND_SCALE
54 600, // ANIM_KIND_SNAPBACK
55 400, // ANIM_KIND_SLIDE
56 300, // ANIM_KIND_ZOOM
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080057 600, // ANIM_KIND_OPENING
Chih-Chung Changb3aab902011-10-03 21:11:39 +080058 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Chang2c617382012-04-20 20:06:19 +080059 800, // ANIM_KIND_CAPTURE
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;
65
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080066 // For user's gestures, we give a temporary extra scaling range which goes
67 // above or below the usual scaling limits.
68 private static final float SCALE_MIN_EXTRA = 0.7f;
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080069 private static final float SCALE_MAX_EXTRA = 1.4f;
70
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080071 // Setting this true makes the extra scaling range permanent (until this is
72 // set to false again).
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080073 private boolean mExtraScalingRange = false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +080074
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080075 // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
76 private boolean mFilmMode = false;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080077
Chih-Chung Chang642561d2012-04-16 16:29:13 +080078 // These are the limits for width / height of the picture in film mode.
79 private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
80 private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
81 private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
82 private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080083
84 // In addition to the focused box (index == 0). We also keep information
85 // about this many boxes on each side.
86 private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
87
Chih-Chung Changfb1a1552012-04-19 13:34:48 +080088 private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080089 private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
90
91 private Listener mListener;
92 private volatile Rect mOpenAnimationRect;
93 private int mViewW = 640;
94 private int mViewH = 480;;
95
96 // A scaling guesture is in progress.
97 private boolean mInScale;
98 // The focus point of the scaling gesture, relative to the center of the
99 // picture in bitmap pixels.
100 private float mFocusX, mFocusY;
101
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800102 // whether there is a previous/next picture.
103 private boolean mHasPrev, mHasNext;
104
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800105 // This is used by the fling animation (page mode).
106 private FlingScroller mPageScroller;
107
108 // This is used by the fling animation (film mode).
Chih-Chung Changd8488622012-04-17 12:56:08 +0800109 private OverScroller mFilmScroller;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800110
111 // The bound of the stable region that the focused box can stay, see the
112 // comments above calculateStableBound() for details.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800113 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
114
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800115 // Constrained frame is a rectangle that the focused box should fit into if
116 // it is constrained. It has two effects:
117 //
118 // (1) In page mode, if the focused box is constrained, scaling for the
119 // focused box is adjusted to fit into the constrained frame, instead of the
120 // whole view.
121 //
122 // (2) In page mode, if the focused box is constrained, the mPlatform's
123 // default center (mDefaultX/Y) is moved to the center of the constrained
124 // frame, instead of the view center.
125 //
126 private Rect mConstrainedFrame = new Rect();
127
128 // Whether the focused box is constrained.
129 //
130 // Our current program's first call to moveBox() sets constrained = true, so
131 // we set the initial value of this variable to true, and we will not see
132 // see unwanted transition animation.
133 private boolean mConstrained = true;
134
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800135 //
136 // ___________________________________________________________
137 // | _____ _____ _____ _____ _____ |
138 // | | | | | | | | | | | |
139 // | | Box | | Box | | Box*| | Box | | Box | |
140 // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
141 // | Gap Gap Gap Gap |
142 // |___________________________________________________________|
143 //
144 // <-- Platform -->
145 //
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800146 // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
Chih-Chung Changec412542011-09-26 17:34:06 +0800147
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800148 private Platform mPlatform = new Platform();
149 private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
150 // The gap at the right of a Box i is at index i. The gap at the left of a
151 // Box i is at index i - 1.
152 private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changec412542011-09-26 17:34:06 +0800153
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800154 // These are only used during moveBox().
155 private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800156 private RangeArray<Gap> mTempGaps =
157 new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800158
159 // The output of the PositionController. Available throught getPosition().
160 private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
161
162 public interface Listener {
163 void invalidate();
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800164 boolean isHolding();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800165
166 // EdgeView
167 void onPull(int offset, int direction);
168 void onRelease();
169 void onAbsorb(int velocity, int direction);
Chih-Chung Changec412542011-09-26 17:34:06 +0800170 }
171
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800172 public PositionController(Context context, Listener listener) {
173 mListener = listener;
174 mPageScroller = new FlingScroller();
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800175 mFilmScroller = new OverScroller(context,
176 null /* default interpolator */, false /* no flywheel */);
Chih-Chung Changec412542011-09-26 17:34:06 +0800177
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800178 // Initialize the areas.
179 initPlatform();
180 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
181 mBoxes.put(i, new Box());
182 initBox(i);
183 mRects.put(i, new Rect());
184 }
185 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
186 mGaps.put(i, new Gap());
187 initGap(i);
188 }
189 }
190
191 public void setOpenAnimationRect(Rect r) {
192 mOpenAnimationRect = r;
193 }
194
195 public void setViewSize(int viewW, int viewH) {
196 if (viewW == mViewW && viewH == mViewH) return;
197
198 mViewW = viewW;
199 mViewH = viewH;
200 initPlatform();
201
202 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
203 setBoxSize(i, viewW, viewH, true);
204 }
205
206 updateScaleAndGapLimit();
207 snapAndRedraw();
208 }
209
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800210 public void setConstrainedFrame(Rect f) {
211 if (mConstrainedFrame.equals(f)) return;
212 mConstrainedFrame.set(f);
213 mPlatform.updateDefaultXY();
214 updateScaleAndGapLimit();
215 snapAndRedraw();
216 }
217
218 public void setImageSize(int index, int width, int height, boolean force) {
219 if (force) {
220 Box b = mBoxes.get(index);
221 b.mImageW = width;
222 b.mImageH = height;
223 return;
224 }
225
Chih-Chung Changec412542011-09-26 17:34:06 +0800226 if (width == 0 || height == 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800227 initBox(index);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800228 } else if (!setBoxSize(index, width, height, false)) {
229 return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800230 }
231
232 updateScaleAndGapLimit();
233 startOpeningAnimationIfNeeded();
234 snapAndRedraw();
235 }
236
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800237 // Returns false if the box size doesn't change.
238 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800239 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800240 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800241
Chih-Chung Changd8488622012-04-17 12:56:08 +0800242 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800243 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800244
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800245 b.mUseViewSize = isViewSize;
246
247 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800248 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800249 }
250
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800251 // The ratio of the old size and the new size.
Chih-Chung Changec412542011-09-26 17:34:06 +0800252 float ratio = Math.min(
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800253 (float) b.mImageW / width, (float) b.mImageH / height);
Chih-Chung Changec412542011-09-26 17:34:06 +0800254
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800255 b.mImageW = width;
256 b.mImageH = height;
257
Chih-Chung Changd8488622012-04-17 12:56:08 +0800258 // If this is the first time we receive an image size, we change the
259 // scale directly. Otherwise adjust the scales by a ratio, and snapback
260 // will animate the scale into the min/max bounds if necessary.
261 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800262 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800263 b.mAnimationStartTime = NO_ANIMATION;
264 } else {
265 b.mCurrentScale *= ratio;
266 b.mFromScale *= ratio;
267 b.mToScale *= ratio;
268 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800269
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800270 if (i == 0) {
271 mFocusX /= ratio;
272 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800273 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800274
275 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800276 }
277
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800278 private void startOpeningAnimationIfNeeded() {
279 if (mOpenAnimationRect == null) return;
280 Box b = mBoxes.get(0);
281 if (b.mUseViewSize) return;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800282
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800283 // Start animation from the saved rectangle if we have one.
284 Rect r = mOpenAnimationRect;
285 mOpenAnimationRect = null;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800286 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
287 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800288 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
289 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800290 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
291 ANIM_KIND_OPENING);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800292 }
293
294 public void setFilmMode(boolean enabled) {
295 if (enabled == mFilmMode) return;
296 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800297
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800298 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800299 updateScaleAndGapLimit();
300 stopAnimation();
301 snapAndRedraw();
302 }
303
304 public void setExtraScalingRange(boolean enabled) {
305 if (mExtraScalingRange == enabled) return;
306 mExtraScalingRange = enabled;
307 if (!enabled) {
308 snapAndRedraw();
309 }
310 }
311
312 // This should be called whenever the scale range of boxes or the default
313 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800314 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800315 private void updateScaleAndGapLimit() {
316 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
317 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800318 b.mScaleMin = getMinimalScale(b);
319 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800320 }
321
322 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
323 Gap g = mGaps.get(i);
324 g.mDefaultSize = getDefaultGapSize(i);
325 }
326 }
327
328 // Returns the default gap size according the the size of the boxes around
329 // the gap and the current mode.
330 private int getDefaultGapSize(int i) {
331 if (mFilmMode) return IMAGE_GAP;
332 Box a = mBoxes.get(i);
333 Box b = mBoxes.get(i + 1);
334 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
335 }
336
337 // Here is how we layout the boxes in the page mode.
338 //
339 // previous current next
340 // ___________ ________________ __________
341 // | _______ | | __________ | | ______ |
342 // | | | | | | right->| | | | | |
343 // | | |<-------->|<--left | | | | | |
344 // | |_______| | | | |__________| | | |______| |
345 // |___________| | |________________| |__________|
346 // | <--> gapToSide()
347 // |
348 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
349 private int gapToSide(Box b) {
350 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
351 }
352
353 // Stop all animations at where they are now.
354 public void stopAnimation() {
355 mPlatform.mAnimationStartTime = NO_ANIMATION;
356 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
357 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
358 }
359 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
360 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
361 }
362 }
363
364 public void skipAnimation() {
365 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
366 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800367 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800368 mPlatform.mAnimationStartTime = NO_ANIMATION;
369 }
370 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
371 Box b = mBoxes.get(i);
372 if (b.mAnimationStartTime == NO_ANIMATION) continue;
373 b.mCurrentY = b.mToY;
374 b.mCurrentScale = b.mToScale;
375 b.mAnimationStartTime = NO_ANIMATION;
376 }
377 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
378 Gap g = mGaps.get(i);
379 if (g.mAnimationStartTime == NO_ANIMATION) continue;
380 g.mCurrentGap = g.mToGap;
381 g.mAnimationStartTime = NO_ANIMATION;
382 }
383 redraw();
384 }
385
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800386 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800387 snapAndRedraw();
388 }
389
390 ////////////////////////////////////////////////////////////////////////////
391 // Start an animations for the focused box
392 ////////////////////////////////////////////////////////////////////////////
393
394 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800395 tapX -= mViewW / 2;
396 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800397 Box b = mBoxes.get(0);
398
399 // Convert the tap position to distance to center in bitmap coordinates
400 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
401 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
402
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800403 int x = (int) (-tempX * targetScale + 0.5f);
404 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800405
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800406 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800407 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
408 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
409 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800410
411 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
412 }
413
414 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800415 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800416 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800417 }
418
Chih-Chung Changec412542011-09-26 17:34:06 +0800419 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800420 focusX -= mViewW / 2;
421 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800422 Box b = mBoxes.get(0);
423 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800424 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800425 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
426 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800427 }
428
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800429 // Scales the image by the given factor.
430 // Returns an out-of-range indicator:
431 // 1 if the intended scale is too large for the stable range.
432 // 0 if the intended scale is in the stable range.
433 // -1 if the intended scale is too small for the stable range.
434 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800435 focusX -= mViewW / 2;
436 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800437 Box b = mBoxes.get(0);
438 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800439
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800440 // We want to keep the focus point (on the bitmap) the same as when we
441 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800442 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800443 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800444 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800445 s *= getTargetScale(b);
446 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
447 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800448 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800449 if (s < b.mScaleMin) return -1;
450 if (s > b.mScaleMax) return 1;
451 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800452 }
453
454 public void endScale() {
455 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800456 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800457 }
458
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800459 // Slide the focused box to the center of the view.
460 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800461 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800462 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800463 }
464
465 // Slide the focused box to the center of the view with the capture
466 // animation. In addition to the sliding, the animation will also scale the
467 // the focused box, the specified neighbor box, and the gap between the
468 // two. The specified offset should be 1 or -1.
469 public void startCaptureAnimationSlide(int offset) {
470 Box b = mBoxes.get(0);
471 Box n = mBoxes.get(offset); // the neighbor box
472 Gap g = mGaps.get(offset); // the gap between the two boxes
473
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800474 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
475 ANIM_KIND_CAPTURE);
476 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
477 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800478 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
479 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800480 }
481
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800482 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800483 Box b = mBoxes.get(0);
484 Platform p = mPlatform;
485
486 int x = getTargetX(p) + (int) (dx + 0.5f);
487 int y = getTargetY(b) + (int) (dy + 0.5f);
488
489 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800490 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800491 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800492 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800493 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800494 }
495
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800496 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800497 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800498
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800499 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800500
501 // Vertical direction: If we have space to move in the vertical
502 // direction, we show the edge effect when scrolling reaches the edge.
503 if (mBoundTop != mBoundBottom) {
504 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800505 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800506 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800507 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800508 }
509 }
510
511 y = Utils.clamp(y, mBoundTop, mBoundBottom);
512
513 // Horizontal direction: we show the edge effect when the scrolling
514 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800515 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800516 int pixels = x - mBoundRight;
517 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800518 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800519 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800520 int pixels = mBoundLeft - x;
521 mListener.onPull(pixels, EdgeView.RIGHT);
522 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800523 }
524
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800525 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
526 }
527
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800528 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800529 Box b = mBoxes.get(0);
530
531 // Horizontal direction: we show the edge effect when the scrolling
532 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800533 x -= mPlatform.mDefaultX;
534 if (!mHasPrev && x > 0) {
535 mListener.onPull(x, EdgeView.LEFT);
536 x = 0;
537 } else if (!mHasNext && x < 0) {
538 mListener.onPull(-x, EdgeView.RIGHT);
539 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800540 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800541 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800542 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800543 }
544
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800545 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800546 int vx = (int) (velocityX + 0.5f);
547 int vy = (int) (velocityY + 0.5f);
548 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
549 }
550
551 private boolean flingPage(int velocityX, int velocityY) {
552 Box b = mBoxes.get(0);
553 Platform p = mPlatform;
554
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800555 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800556 if (viewWiderThanScaledImage(b.mCurrentScale) &&
557 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800558 return false;
559 }
560
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800561 // We only allow flinging in the directions where it won't go over the
562 // picture.
563 int edges = getImageAtEdges();
564 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
565 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
566 velocityX = 0;
567 }
568 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
569 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
570 velocityY = 0;
571 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800572
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800573 if (velocityX == 0 && velocityY == 0) return false;
574
575 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800576 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800577 int targetX = mPageScroller.getFinalX();
578 int targetY = mPageScroller.getFinalY();
579 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
580 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800581 return true;
582 }
583
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800584 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800585 Box b = mBoxes.get(0);
586 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800587
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800588 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800589 int defaultX = p.mDefaultX;
590 if ((!mHasPrev && p.mCurrentX >= defaultX)
591 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800592 return false;
593 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800594
595 if (velocityX == 0) return false;
596
597 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
598 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
599 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800600 // This value doesn't matter because we use mFilmScroller.isFinished()
601 // to decide when to stop. We set this to 0 so it's faster for
602 // Animatable.advanceAnimation() to calculate the progress (always 1).
603 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800604 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
605 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800606 }
607
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800608 ////////////////////////////////////////////////////////////////////////////
609 // Redraw
610 //
611 // If a method changes box positions directly, redraw()
612 // should be called.
613 //
614 // If a method may also cause a snapback to happen, snapAndRedraw() should
615 // be called.
616 //
617 // If a method starts an animation to change the position of focused box,
618 // startAnimation() should be called.
619 //
620 // If time advances to change the box position, advanceAnimation() should
621 // be called.
622 ////////////////////////////////////////////////////////////////////////////
623 private void redraw() {
624 layoutAndSetPosition();
625 mListener.invalidate();
626 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800627
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800628 private void snapAndRedraw() {
629 mPlatform.startSnapback();
630 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
631 mBoxes.get(i).startSnapback();
632 }
633 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
634 mGaps.get(i).startSnapback();
635 }
636 redraw();
637 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800638
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800639 private void startAnimation(int targetX, int targetY, float targetScale,
640 int kind) {
641 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800642 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800643 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
644 if (changed) redraw();
645 }
646
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800647 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800648 boolean changed = false;
649 changed |= mPlatform.advanceAnimation();
650 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
651 changed |= mBoxes.get(i).advanceAnimation();
652 }
653 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
654 changed |= mGaps.get(i).advanceAnimation();
655 }
656 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800657 }
658
659 ////////////////////////////////////////////////////////////////////////////
660 // Layout
661 ////////////////////////////////////////////////////////////////////////////
662
663 // Returns the display width of this box.
664 private int widthOf(Box b) {
665 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
666 }
667
668 // Returns the display height of this box.
669 private int heightOf(Box b) {
670 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
671 }
672
673 // Returns the display width of this box, using the given scale.
674 private int widthOf(Box b, float scale) {
675 return (int) (b.mImageW * scale + 0.5f);
676 }
677
678 // Returns the display height of this box, using the given scale.
679 private int heightOf(Box b, float scale) {
680 return (int) (b.mImageH * scale + 0.5f);
681 }
682
683 // Convert the information in mPlatform and mBoxes to mRects, so the user
684 // can get the position of each box by getPosition().
685 //
686 // Note the loop index goes from inside-out because each box's X coordinate
687 // is relative to its anchor box (except the focused box).
688 private void layoutAndSetPosition() {
689 // layout box 0 (focused box)
690 convertBoxToRect(0);
691 for (int i = 1; i <= BOX_MAX; i++) {
692 // layout box i and -i
693 convertBoxToRect(i);
694 convertBoxToRect(-i);
695 }
696 //dumpState();
697 }
698
699 private void dumpState() {
700 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
701 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800702 }
703
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800704 dumpRect(0);
705 for (int i = 1; i <= BOX_MAX; i++) {
706 dumpRect(i);
707 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800708 }
709
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800710 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
711 for (int j = i + 1; j <= BOX_MAX; j++) {
712 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
713 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
714 }
715 }
716 }
717 }
718
719 private void dumpRect(int i) {
720 StringBuilder sb = new StringBuilder();
721 Rect r = mRects.get(i);
722 sb.append("Rect " + i + ":");
723 sb.append("(");
724 sb.append(r.centerX());
725 sb.append(",");
726 sb.append(r.centerY());
727 sb.append(") [");
728 sb.append(r.width());
729 sb.append("x");
730 sb.append(r.height());
731 sb.append("]");
732 Log.d(TAG, sb.toString());
733 }
734
735 private void convertBoxToRect(int i) {
736 Box b = mBoxes.get(i);
737 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800738 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800739 int w = widthOf(b);
740 int h = heightOf(b);
741 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800742 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800743 r.left = x - w / 2;
744 r.right = r.left + w;
745 } else if (i > 0) {
746 Rect a = mRects.get(i - 1);
747 Gap g = mGaps.get(i - 1);
748 r.left = a.right + g.mCurrentGap;
749 r.right = r.left + w;
750 } else { // i < 0
751 Rect a = mRects.get(i + 1);
752 Gap g = mGaps.get(i);
753 r.right = a.left - g.mCurrentGap;
754 r.left = r.right - w;
755 }
756 r.top = y - h / 2;
757 r.bottom = r.top + h;
758 }
759
760 // Returns the position of a box.
761 public Rect getPosition(int index) {
762 return mRects.get(index);
763 }
764
765 ////////////////////////////////////////////////////////////////////////////
766 // Box management
767 ////////////////////////////////////////////////////////////////////////////
768
769 // Initialize the platform to be at the view center.
770 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800771 mPlatform.updateDefaultXY();
772 mPlatform.mCurrentX = mPlatform.mDefaultX;
773 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800774 mPlatform.mAnimationStartTime = NO_ANIMATION;
775 }
776
777 // Initialize a box to have the size of the view.
778 private void initBox(int index) {
779 Box b = mBoxes.get(index);
780 b.mImageW = mViewW;
781 b.mImageH = mViewH;
782 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800783 b.mScaleMin = getMinimalScale(b);
784 b.mScaleMax = getMaximalScale(b);
785 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800786 b.mCurrentScale = b.mScaleMin;
787 b.mAnimationStartTime = NO_ANIMATION;
788 }
789
790 // Initialize a gap. This can only be called after the boxes around the gap
791 // has been initialized.
792 private void initGap(int index) {
793 Gap g = mGaps.get(index);
794 g.mDefaultSize = getDefaultGapSize(index);
795 g.mCurrentGap = g.mDefaultSize;
796 g.mAnimationStartTime = NO_ANIMATION;
797 }
798
799 private void initGap(int index, int size) {
800 Gap g = mGaps.get(index);
801 g.mDefaultSize = getDefaultGapSize(index);
802 g.mCurrentGap = size;
803 g.mAnimationStartTime = NO_ANIMATION;
804 }
805
806 private void debugMoveBox(int fromIndex[]) {
807 StringBuilder s = new StringBuilder("moveBox:");
808 for (int i = 0; i < fromIndex.length; i++) {
809 int j = fromIndex[i];
810 if (j == Integer.MAX_VALUE) {
811 s.append(" N");
812 } else {
813 s.append(" ");
814 s.append(fromIndex[i]);
815 }
816 }
817 Log.d(TAG, s.toString());
818 }
819
820 // Move the boxes: it may indicate focus change, box deleted, box appearing,
821 // box reordered, etc.
822 //
823 // Each element in the fromIndex array indicates where each box was in the
824 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
825 // means the box is new.
826 //
827 // For example:
828 // N N N N N N N -- all new boxes
829 // -3 -2 -1 0 1 2 3 -- nothing changed
830 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800831 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800832 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800833 //
834 // hasPrev/hasNext indicates if there are previous/next boxes for the
835 // focused box. constrained indicates whether the focused box should be put
836 // into the constrained frame.
837 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
838 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800839 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800840 mHasPrev = hasPrev;
841 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800842
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800843 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
844
845 // 1. Get the absolute X coordiates for the boxes.
846 layoutAndSetPosition();
847 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
848 Box b = mBoxes.get(i);
849 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800850 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800851 }
852
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800853 // 2. copy boxes and gaps to temporary storage.
854 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
855 mTempBoxes.put(i, mBoxes.get(i));
856 mBoxes.put(i, null);
857 }
858 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
859 mTempGaps.put(i, mGaps.get(i));
860 mGaps.put(i, null);
861 }
862
863 // 3. move back boxes that are used in the new array.
864 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
865 int j = from.get(i);
866 if (j == Integer.MAX_VALUE) continue;
867 mBoxes.put(i, mTempBoxes.get(j));
868 mTempBoxes.put(j, null);
869 }
870
871 // 4. move back gaps if both boxes around it are kept together.
872 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
873 int j = from.get(i);
874 if (j == Integer.MAX_VALUE) continue;
875 int k = from.get(i + 1);
876 if (k == Integer.MAX_VALUE) continue;
877 if (j + 1 == k) {
878 mGaps.put(i, mTempGaps.get(j));
879 mTempGaps.put(j, null);
880 }
881 }
882
883 // 5. recycle the boxes that are not used in the new array.
884 int k = -BOX_MAX;
885 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
886 if (mBoxes.get(i) != null) continue;
887 while (mTempBoxes.get(k) == null) {
888 k++;
889 }
890 mBoxes.put(i, mTempBoxes.get(k++));
891 initBox(i);
892 }
893
894 // 6. Now give the recycled box a reasonable absolute X position.
895 //
896 // First try to find the first and the last box which the absolute X
897 // position is known.
898 int first, last;
899 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
900 if (from.get(first) != Integer.MAX_VALUE) break;
901 }
902 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
903 if (from.get(last) != Integer.MAX_VALUE) break;
904 }
905 // If there is no box has known X position at all, make the focused one
906 // as known.
907 if (first > BOX_MAX) {
908 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
909 first = last = 0;
910 }
911 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800912 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800913 // rare). For the boxes before first or after last, we will use a new
914 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800915 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800916 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800917 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800918 }
919
920 // 7. recycle the gaps that are not used in the new array.
921 k = -BOX_MAX;
922 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
923 if (mGaps.get(i) != null) continue;
924 while (mTempGaps.get(k) == null) {
925 k++;
926 }
927 mGaps.put(i, mTempGaps.get(k++));
928 Box a = mBoxes.get(i);
929 Box b = mBoxes.get(i + 1);
930 int wa = widthOf(a);
931 int wb = widthOf(b);
932 if (i >= first && i < last) {
933 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
934 initGap(i, g);
935 } else {
936 initGap(i);
937 }
938 }
939
940 // 8. offset the Platform position
941 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
942 mPlatform.mCurrentX += dx;
943 mPlatform.mFromX += dx;
944 mPlatform.mToX += dx;
945 mPlatform.mFlingOffset += dx;
946
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800947 if (mConstrained != constrained) {
948 mConstrained = constrained;
949 mPlatform.updateDefaultXY();
950 updateScaleAndGapLimit();
951 }
952
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800953 snapAndRedraw();
954 }
955
956 ////////////////////////////////////////////////////////////////////////////
957 // Public utilities
958 ////////////////////////////////////////////////////////////////////////////
959
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800960 public boolean isAtMinimalScale() {
961 Box b = mBoxes.get(0);
962 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
963 }
964
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800965 public boolean isCenter() {
966 Box b = mBoxes.get(0);
967 return mPlatform.mCurrentX == mPlatform.mDefaultX
968 && b.mCurrentY == 0;
969 }
970
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800971 public int getImageWidth() {
972 Box b = mBoxes.get(0);
973 return b.mImageW;
974 }
975
976 public int getImageHeight() {
977 Box b = mBoxes.get(0);
978 return b.mImageH;
979 }
980
981 public float getImageScale() {
982 Box b = mBoxes.get(0);
983 return b.mCurrentScale;
984 }
985
986 public int getImageAtEdges() {
987 Box b = mBoxes.get(0);
988 Platform p = mPlatform;
989 calculateStableBound(b.mCurrentScale);
990 int edges = 0;
991 if (p.mCurrentX <= mBoundLeft) {
992 edges |= IMAGE_AT_RIGHT_EDGE;
993 }
994 if (p.mCurrentX >= mBoundRight) {
995 edges |= IMAGE_AT_LEFT_EDGE;
996 }
997 if (b.mCurrentY <= mBoundTop) {
998 edges |= IMAGE_AT_BOTTOM_EDGE;
999 }
1000 if (b.mCurrentY >= mBoundBottom) {
1001 edges |= IMAGE_AT_TOP_EDGE;
1002 }
1003 return edges;
1004 }
1005
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001006 public boolean isScrolling() {
1007 return mPlatform.mAnimationStartTime != NO_ANIMATION
1008 && mPlatform.mCurrentX != mPlatform.mToX;
1009 }
1010
1011 public void stopScrolling() {
1012 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
1013 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1014 }
1015
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001016 ////////////////////////////////////////////////////////////////////////////
1017 // Private utilities
1018 ////////////////////////////////////////////////////////////////////////////
1019
1020 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001021 float wFactor = 1.0f;
1022 float hFactor = 1.0f;
1023 int viewW, viewH;
1024
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001025 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1026 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001027 viewW = mConstrainedFrame.width();
1028 viewH = mConstrainedFrame.height();
1029 } else {
1030 viewW = mViewW;
1031 viewH = mViewH;
1032 }
1033
1034 if (mFilmMode) {
1035 if (mViewH > mViewW) { // portrait
1036 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1037 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1038 } else { // landscape
1039 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1040 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1041 }
1042 }
1043
1044 float s = Math.min(wFactor * viewW / b.mImageW,
1045 hFactor * viewH / b.mImageH);
1046 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001047 }
1048
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001049 private float getMaximalScale(Box b) {
1050 return mFilmMode ? getMinimalScale(b) : SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001051 }
1052
1053 private static boolean isAlmostEqual(float a, float b) {
1054 float diff = a - b;
1055 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001056 }
1057
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001058 // Calculates the stable region of mPlatform.mCurrentX and
1059 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001060 //
1061 // (1) If the dimension of scaled image >= view dimension, we will not
1062 // see black region outside the image (at that dimension).
1063 // (2) If the dimension of scaled image < view dimension, we will center
1064 // the scaled image.
1065 //
1066 // We might temporarily go out of this stable during user interaction,
1067 // but will "snap back" after user stops interaction.
1068 //
1069 // The results are stored in mBound{Left/Right/Top/Bottom}.
1070 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001071 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1072 // is used to extend the stable region by some pixels on each side
1073 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001074 private void calculateStableBound(float scale, int horizontalSlack) {
1075 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001076
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001077 // The width and height of the box in number of view pixels
1078 int w = widthOf(b, scale);
1079 int h = heightOf(b, scale);
1080
1081 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001082 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1083 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1084 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1085 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001086
1087 // If the scaled height is smaller than the view height,
1088 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001089 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001090 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001091 }
1092
1093 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001094 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001095 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001096 }
1097 }
1098
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001099 private void calculateStableBound(float scale) {
1100 calculateStableBound(scale, 0);
1101 }
1102
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001103 private boolean viewTallerThanScaledImage(float scale) {
1104 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001105 }
1106
1107 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001108 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001109 }
1110
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001111 private float getTargetScale(Box b) {
1112 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001113 }
1114
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001115 private int getTargetX(Platform p) {
1116 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001117 }
1118
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001119 private int getTargetY(Box b) {
1120 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001121 }
1122
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001123 private boolean useCurrentValueAsTarget(Animatable a) {
1124 return a.mAnimationStartTime == NO_ANIMATION ||
1125 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1126 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001127 }
1128
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001129 ////////////////////////////////////////////////////////////////////////////
1130 // Animatable: an thing which can do animation.
1131 ////////////////////////////////////////////////////////////////////////////
1132 private abstract static class Animatable {
1133 public long mAnimationStartTime;
1134 public int mAnimationKind;
1135 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001136
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001137 // This should be overidden in subclass to change the animation values
1138 // give the progress value in [0, 1].
1139 protected abstract boolean interpolate(float progress);
1140 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001141
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001142 // Returns true if the animation values changes, so things need to be
1143 // redrawn.
1144 public boolean advanceAnimation() {
1145 if (mAnimationStartTime == NO_ANIMATION) {
1146 return false;
1147 }
1148 if (mAnimationStartTime == LAST_ANIMATION) {
1149 mAnimationStartTime = NO_ANIMATION;
1150 return startSnapback();
1151 }
1152
1153 float progress;
1154 if (mAnimationDuration == 0) {
1155 progress = 1;
1156 } else {
1157 long now = AnimationTime.get();
1158 progress =
1159 (float) (now - mAnimationStartTime) / mAnimationDuration;
1160 }
1161
1162 if (progress >= 1) {
1163 progress = 1;
1164 } else {
1165 progress = applyInterpolationCurve(mAnimationKind, progress);
1166 }
1167
1168 boolean done = interpolate(progress);
1169
1170 if (done) {
1171 mAnimationStartTime = LAST_ANIMATION;
1172 }
1173
1174 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001175 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001176
1177 private static float applyInterpolationCurve(int kind, float progress) {
1178 float f = 1 - progress;
1179 switch (kind) {
1180 case ANIM_KIND_SCROLL:
1181 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001182 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001183 progress = 1 - f; // linear
1184 break;
1185 case ANIM_KIND_SCALE:
1186 progress = 1 - f * f; // quadratic
1187 break;
1188 case ANIM_KIND_SNAPBACK:
1189 case ANIM_KIND_ZOOM:
1190 case ANIM_KIND_SLIDE:
1191 case ANIM_KIND_OPENING:
1192 progress = 1 - f * f * f * f * f; // x^5
1193 break;
1194 }
1195 return progress;
1196 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001197 }
1198
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001199 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001200 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001201 ////////////////////////////////////////////////////////////////////////////
1202 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001203 public int mCurrentX, mFromX, mToX, mDefaultX;
1204 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001205 public int mFlingOffset;
1206
1207 @Override
1208 public boolean startSnapback() {
1209 if (mAnimationStartTime != NO_ANIMATION) return false;
1210 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001211 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001212
1213 Box b = mBoxes.get(0);
1214 float scaleMin = mExtraScalingRange ?
1215 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1216 float scaleMax = mExtraScalingRange ?
1217 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1218 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1219 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001220 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001221 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001222 int defaultX = mDefaultX;
1223 if (!mHasNext) x = Math.max(x, defaultX);
1224 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001225 } else {
1226 calculateStableBound(scale, HORIZONTAL_SLACK);
1227 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1228 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001229 if (mCurrentX != x || mCurrentY != y) {
1230 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001231 }
1232 return false;
1233 }
1234
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001235 // The updateDefaultXY() should be called whenever these variables
1236 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1237 // mFilmMode
1238 public void updateDefaultXY() {
1239 // We don't check mFilmMode and return 0 for mDefaultX. Because
1240 // otherwise if we decide to leave film mode because we are
1241 // centered, we will immediately back into film mode because we find
1242 // we are not centered.
1243 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1244 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1245 mDefaultY = mFilmMode ? 0 :
1246 mConstrainedFrame.centerY() - mViewH / 2;
1247 } else {
1248 mDefaultX = 0;
1249 mDefaultY = 0;
1250 }
1251 }
1252
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001253 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001254 private boolean doAnimation(int targetX, int targetY, int kind) {
1255 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001256 mAnimationKind = kind;
1257 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001258 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001259 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001260 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001261 mAnimationStartTime = AnimationTime.startTime();
1262 mAnimationDuration = ANIM_TIME[kind];
1263 mFlingOffset = 0;
1264 advanceAnimation();
1265 return true;
1266 }
1267
1268 @Override
1269 protected boolean interpolate(float progress) {
1270 if (mAnimationKind == ANIM_KIND_FLING) {
1271 return mFilmMode
1272 ? interpolateFlingFilm(progress)
1273 : interpolateFlingPage(progress);
1274 } else {
1275 return interpolateLinear(progress);
1276 }
1277 }
1278
1279 private boolean interpolateFlingFilm(float progress) {
1280 mFilmScroller.computeScrollOffset();
1281 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1282
1283 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001284 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001285 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001286 dir = EdgeView.RIGHT;
1287 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001288 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001289 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001290 dir = EdgeView.LEFT;
1291 }
1292 }
1293 if (dir != EdgeView.INVALID_DIRECTION) {
1294 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1295 mListener.onAbsorb(v, dir);
1296 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001297 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001298 }
1299 return mFilmScroller.isFinished();
1300 }
1301
1302 private boolean interpolateFlingPage(float progress) {
1303 mPageScroller.computeScrollOffset(progress);
1304 Box b = mBoxes.get(0);
1305 calculateStableBound(b.mCurrentScale);
1306
1307 int oldX = mCurrentX;
1308 mCurrentX = mPageScroller.getCurrX();
1309
1310 // Check if we hit the edges; show edge effects if we do.
1311 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1312 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1313 mListener.onAbsorb(v, EdgeView.RIGHT);
1314 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1315 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1316 mListener.onAbsorb(v, EdgeView.LEFT);
1317 }
1318
1319 return progress >= 1;
1320 }
1321
1322 private boolean interpolateLinear(float progress) {
1323 // Other animations
1324 if (progress >= 1) {
1325 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001326 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001327 return true;
1328 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001329 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1330 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001331 }
1332 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1333 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1334 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001335 return false;
1336 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001337 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001338 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001339 }
1340 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001341 }
1342
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001343 ////////////////////////////////////////////////////////////////////////////
1344 // Box: represents a rectangular area which shows a picture.
1345 ////////////////////////////////////////////////////////////////////////////
1346 private class Box extends Animatable {
1347 // Size of the bitmap
1348 public int mImageW, mImageH;
1349
1350 // This is true if we assume the image size is the same as view size
1351 // until we know the actual size of image. This is also used to
1352 // determine if there is an image ready to show.
1353 public boolean mUseViewSize;
1354
1355 // The minimum and maximum scale we allow for this box.
1356 public float mScaleMin, mScaleMax;
1357
1358 // The X/Y value indicates where the center of the box is on the view
1359 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1360 // actual values used currently. Note that the X values are implicitly
1361 // defined by Platform and Gaps.
1362 public int mCurrentY, mFromY, mToY;
1363 public float mCurrentScale, mFromScale, mToScale;
1364
1365 // The absolute X coordinate of the center of the box. This is only used
1366 // during moveBox().
1367 public int mAbsoluteX;
1368
1369 @Override
1370 public boolean startSnapback() {
1371 if (mAnimationStartTime != NO_ANIMATION) return false;
1372 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001373 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001374 if (mInScale && this == mBoxes.get(0)) return false;
1375
1376 int y;
1377 float scale;
1378
1379 if (this == mBoxes.get(0)) {
1380 float scaleMin = mExtraScalingRange ?
1381 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1382 float scaleMax = mExtraScalingRange ?
1383 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1384 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1385 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001386 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001387 } else {
1388 calculateStableBound(scale, HORIZONTAL_SLACK);
1389 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1390 }
1391 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001392 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001393 scale = mScaleMin;
1394 }
1395
1396 if (mCurrentY != y || mCurrentScale != scale) {
1397 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1398 }
1399 return false;
1400 }
1401
1402 private boolean doAnimation(int targetY, float targetScale, int kind) {
1403 targetScale = Utils.clamp(targetScale,
1404 SCALE_MIN_EXTRA * mScaleMin,
1405 SCALE_MAX_EXTRA * mScaleMax);
1406
1407 // If the scaled height is smaller than the view height, force it to be
1408 // in the center. (We do this for height only, not width, because the
1409 // user may want to scroll to the previous/next image.)
1410 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001411 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001412 }
1413
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001414 if (mCurrentY == targetY && mCurrentScale == targetScale
1415 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001416 return false;
1417 }
1418
1419 // Now starts an animation for the box.
1420 mAnimationKind = kind;
1421 mFromY = mCurrentY;
1422 mFromScale = mCurrentScale;
1423 mToY = targetY;
1424 mToScale = targetScale;
1425 mAnimationStartTime = AnimationTime.startTime();
1426 mAnimationDuration = ANIM_TIME[kind];
1427 advanceAnimation();
1428 return true;
1429 }
1430
1431 @Override
1432 protected boolean interpolate(float progress) {
1433 if (mAnimationKind == ANIM_KIND_FLING) {
1434 // Currently a Box can only be flung in page mode.
1435 return interpolateFlingPage(progress);
1436 } else {
1437 return interpolateLinear(progress);
1438 }
1439 }
1440
1441 private boolean interpolateFlingPage(float progress) {
1442 mPageScroller.computeScrollOffset(progress);
1443 calculateStableBound(mCurrentScale);
1444
1445 int oldY = mCurrentY;
1446 mCurrentY = mPageScroller.getCurrY();
1447
1448 // Check if we hit the edges; show edge effects if we do.
1449 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1450 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1451 mListener.onAbsorb(v, EdgeView.BOTTOM);
1452 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1453 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1454 mListener.onAbsorb(v, EdgeView.TOP);
1455 }
1456
1457 return progress >= 1;
1458 }
1459
1460 private boolean interpolateLinear(float progress) {
1461 if (progress >= 1) {
1462 mCurrentY = mToY;
1463 mCurrentScale = mToScale;
1464 return true;
1465 } else {
1466 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1467 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001468 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1469 float f = CaptureAnimation.calculateScale(progress);
1470 mCurrentScale *= f;
1471 return false;
1472 } else {
1473 return (mCurrentY == mToY && mCurrentScale == mToScale);
1474 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001475 }
1476 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001477 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001478
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001479 ////////////////////////////////////////////////////////////////////////////
1480 // Gap: represents a rectangular area which is between two boxes.
1481 ////////////////////////////////////////////////////////////////////////////
1482 private class Gap extends Animatable {
1483 // The default gap size between two boxes. The value may vary for
1484 // different image size of the boxes and for different modes (page or
1485 // film).
1486 public int mDefaultSize;
1487
1488 // The gap size between the two boxes.
1489 public int mCurrentGap, mFromGap, mToGap;
1490
1491 @Override
1492 public boolean startSnapback() {
1493 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001494 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001495 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001496
1497 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001498 public boolean doAnimation(int targetSize, int kind) {
1499 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1500 return false;
1501 }
1502 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001503 mFromGap = mCurrentGap;
1504 mToGap = targetSize;
1505 mAnimationStartTime = AnimationTime.startTime();
1506 mAnimationDuration = ANIM_TIME[mAnimationKind];
1507 advanceAnimation();
1508 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001509 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001510
1511 @Override
1512 protected boolean interpolate(float progress) {
1513 if (progress >= 1) {
1514 mCurrentGap = mToGap;
1515 return true;
1516 } else {
1517 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001518 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1519 float f = CaptureAnimation.calculateScale(progress);
1520 mCurrentGap = (int) (mCurrentGap * f);
1521 return false;
1522 } else {
1523 return (mCurrentGap == mToGap);
1524 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001525 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001526 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001527 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001528}