blob: 9797ce903f2b594520abf7353679d7141a1eeab5 [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
Yuli Huang5338d192012-05-17 11:32:15 +080057 400, // 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;
Chih-Chung Changba12eae2012-05-07 02:29:37 +080093
94 // Use a large enough value, so we won't see the gray shadown in the beginning.
95 private int mViewW = 1200;
96 private int mViewH = 1200;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080097
98 // A scaling guesture is in progress.
99 private boolean mInScale;
100 // The focus point of the scaling gesture, relative to the center of the
101 // picture in bitmap pixels.
102 private float mFocusX, mFocusY;
103
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800104 // whether there is a previous/next picture.
105 private boolean mHasPrev, mHasNext;
106
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800107 // This is used by the fling animation (page mode).
108 private FlingScroller mPageScroller;
109
110 // This is used by the fling animation (film mode).
Chih-Chung Changd8488622012-04-17 12:56:08 +0800111 private OverScroller mFilmScroller;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800112
113 // The bound of the stable region that the focused box can stay, see the
114 // comments above calculateStableBound() for details.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800115 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
116
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800117 // Constrained frame is a rectangle that the focused box should fit into if
118 // it is constrained. It has two effects:
119 //
120 // (1) In page mode, if the focused box is constrained, scaling for the
121 // focused box is adjusted to fit into the constrained frame, instead of the
122 // whole view.
123 //
124 // (2) In page mode, if the focused box is constrained, the mPlatform's
125 // default center (mDefaultX/Y) is moved to the center of the constrained
126 // frame, instead of the view center.
127 //
128 private Rect mConstrainedFrame = new Rect();
129
130 // Whether the focused box is constrained.
131 //
132 // Our current program's first call to moveBox() sets constrained = true, so
133 // we set the initial value of this variable to true, and we will not see
134 // see unwanted transition animation.
135 private boolean mConstrained = true;
136
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800137 //
138 // ___________________________________________________________
139 // | _____ _____ _____ _____ _____ |
140 // | | | | | | | | | | | |
141 // | | Box | | Box | | Box*| | Box | | Box | |
142 // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
143 // | Gap Gap Gap Gap |
144 // |___________________________________________________________|
145 //
146 // <-- Platform -->
147 //
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800148 // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
Chih-Chung Changec412542011-09-26 17:34:06 +0800149
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800150 private Platform mPlatform = new Platform();
151 private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
152 // The gap at the right of a Box i is at index i. The gap at the left of a
153 // Box i is at index i - 1.
154 private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800155 private FilmRatio mFilmRatio = new FilmRatio();
Chih-Chung Changec412542011-09-26 17:34:06 +0800156
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800157 // These are only used during moveBox().
158 private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800159 private RangeArray<Gap> mTempGaps =
160 new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800161
162 // The output of the PositionController. Available throught getPosition().
163 private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
164
165 public interface Listener {
166 void invalidate();
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800167 boolean isHolding();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800168
169 // EdgeView
170 void onPull(int offset, int direction);
171 void onRelease();
172 void onAbsorb(int velocity, int direction);
Chih-Chung Changec412542011-09-26 17:34:06 +0800173 }
174
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800175 public PositionController(Context context, Listener listener) {
176 mListener = listener;
177 mPageScroller = new FlingScroller();
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800178 mFilmScroller = new OverScroller(context,
179 null /* default interpolator */, false /* no flywheel */);
Chih-Chung Changec412542011-09-26 17:34:06 +0800180
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800181 // Initialize the areas.
182 initPlatform();
183 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
184 mBoxes.put(i, new Box());
185 initBox(i);
186 mRects.put(i, new Rect());
187 }
188 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
189 mGaps.put(i, new Gap());
190 initGap(i);
191 }
192 }
193
194 public void setOpenAnimationRect(Rect r) {
195 mOpenAnimationRect = r;
196 }
197
198 public void setViewSize(int viewW, int viewH) {
199 if (viewW == mViewW && viewH == mViewH) return;
200
201 mViewW = viewW;
202 mViewH = viewH;
203 initPlatform();
204
205 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
206 setBoxSize(i, viewW, viewH, true);
207 }
208
209 updateScaleAndGapLimit();
Yuli Huangbd7c0162012-05-15 22:36:59 +0800210 startOpeningAnimationIfNeeded();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800211 snapAndRedraw();
212 }
213
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800214 public void setConstrainedFrame(Rect f) {
215 if (mConstrainedFrame.equals(f)) return;
216 mConstrainedFrame.set(f);
217 mPlatform.updateDefaultXY();
218 updateScaleAndGapLimit();
219 snapAndRedraw();
220 }
221
222 public void setImageSize(int index, int width, int height, boolean force) {
223 if (force) {
224 Box b = mBoxes.get(index);
225 b.mImageW = width;
226 b.mImageH = height;
227 return;
228 }
229
Chih-Chung Changec412542011-09-26 17:34:06 +0800230 if (width == 0 || height == 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800231 initBox(index);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800232 } else if (!setBoxSize(index, width, height, false)) {
233 return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800234 }
235
236 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800237 snapAndRedraw();
238 }
239
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800240 // Returns false if the box size doesn't change.
241 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800242 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800243 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800244
Chih-Chung Changd8488622012-04-17 12:56:08 +0800245 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800246 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800247
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800248 b.mUseViewSize = isViewSize;
249
250 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800251 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800252 }
253
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800254 // The ratio of the old size and the new size.
Chih-Chung Changec412542011-09-26 17:34:06 +0800255 float ratio = Math.min(
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800256 (float) b.mImageW / width, (float) b.mImageH / height);
Chih-Chung Changec412542011-09-26 17:34:06 +0800257
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800258 b.mImageW = width;
259 b.mImageH = height;
260
Chih-Chung Changd8488622012-04-17 12:56:08 +0800261 // If this is the first time we receive an image size, we change the
262 // scale directly. Otherwise adjust the scales by a ratio, and snapback
263 // will animate the scale into the min/max bounds if necessary.
264 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800265 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800266 b.mAnimationStartTime = NO_ANIMATION;
267 } else {
268 b.mCurrentScale *= ratio;
269 b.mFromScale *= ratio;
270 b.mToScale *= ratio;
271 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800272
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800273 if (i == 0) {
274 mFocusX /= ratio;
275 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800276 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800277
278 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800279 }
280
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800281 private void startOpeningAnimationIfNeeded() {
282 if (mOpenAnimationRect == null) return;
283 Box b = mBoxes.get(0);
284 if (b.mUseViewSize) return;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800285
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800286 // Start animation from the saved rectangle if we have one.
287 Rect r = mOpenAnimationRect;
288 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800289
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800290 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
291 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800292 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
293 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800294 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
295 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800296
297 // Animate from large gaps for neighbor boxes to avoid them
298 // shown on the screen during opening animation.
299 for (int i = -1; i < 1; i++) {
300 Gap g = mGaps.get(i);
301 g.mCurrentGap = mViewW;
302 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
303 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800304 }
305
306 public void setFilmMode(boolean enabled) {
307 if (enabled == mFilmMode) return;
308 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800309
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800310 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800311 updateScaleAndGapLimit();
312 stopAnimation();
313 snapAndRedraw();
314 }
315
316 public void setExtraScalingRange(boolean enabled) {
317 if (mExtraScalingRange == enabled) return;
318 mExtraScalingRange = enabled;
319 if (!enabled) {
320 snapAndRedraw();
321 }
322 }
323
324 // This should be called whenever the scale range of boxes or the default
325 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800326 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800327 private void updateScaleAndGapLimit() {
328 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
329 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800330 b.mScaleMin = getMinimalScale(b);
331 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800332 }
333
334 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
335 Gap g = mGaps.get(i);
336 g.mDefaultSize = getDefaultGapSize(i);
337 }
338 }
339
340 // Returns the default gap size according the the size of the boxes around
341 // the gap and the current mode.
342 private int getDefaultGapSize(int i) {
343 if (mFilmMode) return IMAGE_GAP;
344 Box a = mBoxes.get(i);
345 Box b = mBoxes.get(i + 1);
346 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
347 }
348
349 // Here is how we layout the boxes in the page mode.
350 //
351 // previous current next
352 // ___________ ________________ __________
353 // | _______ | | __________ | | ______ |
354 // | | | | | | right->| | | | | |
355 // | | |<-------->|<--left | | | | | |
356 // | |_______| | | | |__________| | | |______| |
357 // |___________| | |________________| |__________|
358 // | <--> gapToSide()
359 // |
360 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
361 private int gapToSide(Box b) {
362 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
363 }
364
365 // Stop all animations at where they are now.
366 public void stopAnimation() {
367 mPlatform.mAnimationStartTime = NO_ANIMATION;
368 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
369 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
370 }
371 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
372 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
373 }
374 }
375
376 public void skipAnimation() {
377 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
378 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800379 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800380 mPlatform.mAnimationStartTime = NO_ANIMATION;
381 }
382 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
383 Box b = mBoxes.get(i);
384 if (b.mAnimationStartTime == NO_ANIMATION) continue;
385 b.mCurrentY = b.mToY;
386 b.mCurrentScale = b.mToScale;
387 b.mAnimationStartTime = NO_ANIMATION;
388 }
389 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
390 Gap g = mGaps.get(i);
391 if (g.mAnimationStartTime == NO_ANIMATION) continue;
392 g.mCurrentGap = g.mToGap;
393 g.mAnimationStartTime = NO_ANIMATION;
394 }
395 redraw();
396 }
397
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800398 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800399 snapAndRedraw();
400 }
401
402 ////////////////////////////////////////////////////////////////////////////
403 // Start an animations for the focused box
404 ////////////////////////////////////////////////////////////////////////////
405
406 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800407 tapX -= mViewW / 2;
408 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800409 Box b = mBoxes.get(0);
410
411 // Convert the tap position to distance to center in bitmap coordinates
412 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
413 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
414
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800415 int x = (int) (-tempX * targetScale + 0.5f);
416 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800417
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800418 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800419 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
420 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
421 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800422
423 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
424 }
425
426 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800427 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800428 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800429 }
430
Chih-Chung Changec412542011-09-26 17:34:06 +0800431 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800432 focusX -= mViewW / 2;
433 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800434 Box b = mBoxes.get(0);
435 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800436 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800437 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
438 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800439 }
440
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800441 // Scales the image by the given factor.
442 // Returns an out-of-range indicator:
443 // 1 if the intended scale is too large for the stable range.
444 // 0 if the intended scale is in the stable range.
445 // -1 if the intended scale is too small for the stable range.
446 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800447 focusX -= mViewW / 2;
448 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800449 Box b = mBoxes.get(0);
450 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800451
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800452 // We want to keep the focus point (on the bitmap) the same as when we
453 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800454 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800455 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800456 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800457 s *= getTargetScale(b);
458 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
459 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800460 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800461 if (s < b.mScaleMin) return -1;
462 if (s > b.mScaleMax) return 1;
463 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800464 }
465
466 public void endScale() {
467 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800468 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800469 }
470
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800471 // Slide the focused box to the center of the view.
472 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800473 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800474 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800475 }
476
477 // Slide the focused box to the center of the view with the capture
478 // animation. In addition to the sliding, the animation will also scale the
479 // the focused box, the specified neighbor box, and the gap between the
480 // two. The specified offset should be 1 or -1.
481 public void startCaptureAnimationSlide(int offset) {
482 Box b = mBoxes.get(0);
483 Box n = mBoxes.get(offset); // the neighbor box
484 Gap g = mGaps.get(offset); // the gap between the two boxes
485
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800486 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
487 ANIM_KIND_CAPTURE);
488 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
489 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800490 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
491 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800492 }
493
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800494 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800495 Box b = mBoxes.get(0);
496 Platform p = mPlatform;
497
498 int x = getTargetX(p) + (int) (dx + 0.5f);
499 int y = getTargetY(b) + (int) (dy + 0.5f);
500
501 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800502 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800503 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800504 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800505 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800506 }
507
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800508 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800509 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800510
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800511 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800512
513 // Vertical direction: If we have space to move in the vertical
514 // direction, we show the edge effect when scrolling reaches the edge.
515 if (mBoundTop != mBoundBottom) {
516 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800517 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800518 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800519 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800520 }
521 }
522
523 y = Utils.clamp(y, mBoundTop, mBoundBottom);
524
525 // Horizontal direction: we show the edge effect when the scrolling
526 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800527 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800528 int pixels = x - mBoundRight;
529 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800530 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800531 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800532 int pixels = mBoundLeft - x;
533 mListener.onPull(pixels, EdgeView.RIGHT);
534 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800535 }
536
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800537 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
538 }
539
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800540 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800541 Box b = mBoxes.get(0);
542
543 // Horizontal direction: we show the edge effect when the scrolling
544 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800545 x -= mPlatform.mDefaultX;
546 if (!mHasPrev && x > 0) {
547 mListener.onPull(x, EdgeView.LEFT);
548 x = 0;
549 } else if (!mHasNext && x < 0) {
550 mListener.onPull(-x, EdgeView.RIGHT);
551 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800552 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800553 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800554 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800555 }
556
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800557 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800558 int vx = (int) (velocityX + 0.5f);
559 int vy = (int) (velocityY + 0.5f);
560 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
561 }
562
563 private boolean flingPage(int velocityX, int velocityY) {
564 Box b = mBoxes.get(0);
565 Platform p = mPlatform;
566
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800567 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800568 if (viewWiderThanScaledImage(b.mCurrentScale) &&
569 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800570 return false;
571 }
572
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800573 // We only allow flinging in the directions where it won't go over the
574 // picture.
575 int edges = getImageAtEdges();
576 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
577 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
578 velocityX = 0;
579 }
580 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
581 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
582 velocityY = 0;
583 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800584
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800585 if (velocityX == 0 && velocityY == 0) return false;
586
587 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800588 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800589 int targetX = mPageScroller.getFinalX();
590 int targetY = mPageScroller.getFinalY();
591 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
592 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800593 return true;
594 }
595
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800596 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800597 Box b = mBoxes.get(0);
598 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800599
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800600 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800601 int defaultX = p.mDefaultX;
602 if ((!mHasPrev && p.mCurrentX >= defaultX)
603 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800604 return false;
605 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800606
607 if (velocityX == 0) return false;
608
609 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
610 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
611 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800612 // This value doesn't matter because we use mFilmScroller.isFinished()
613 // to decide when to stop. We set this to 0 so it's faster for
614 // Animatable.advanceAnimation() to calculate the progress (always 1).
615 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800616 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
617 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800618 }
619
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800620 ////////////////////////////////////////////////////////////////////////////
621 // Redraw
622 //
623 // If a method changes box positions directly, redraw()
624 // should be called.
625 //
626 // If a method may also cause a snapback to happen, snapAndRedraw() should
627 // be called.
628 //
629 // If a method starts an animation to change the position of focused box,
630 // startAnimation() should be called.
631 //
632 // If time advances to change the box position, advanceAnimation() should
633 // be called.
634 ////////////////////////////////////////////////////////////////////////////
635 private void redraw() {
636 layoutAndSetPosition();
637 mListener.invalidate();
638 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800639
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800640 private void snapAndRedraw() {
641 mPlatform.startSnapback();
642 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
643 mBoxes.get(i).startSnapback();
644 }
645 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
646 mGaps.get(i).startSnapback();
647 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800648 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800649 redraw();
650 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800651
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800652 private void startAnimation(int targetX, int targetY, float targetScale,
653 int kind) {
654 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800655 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800656 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
657 if (changed) redraw();
658 }
659
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800660 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800661 boolean changed = false;
662 changed |= mPlatform.advanceAnimation();
663 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
664 changed |= mBoxes.get(i).advanceAnimation();
665 }
666 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
667 changed |= mGaps.get(i).advanceAnimation();
668 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800669 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800670 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800671 }
672
Yuli Huangf320b842012-05-16 01:38:06 +0800673 public boolean inOpeningAnimation() {
674 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
675 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
676 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
677 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
678 }
679
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800680 ////////////////////////////////////////////////////////////////////////////
681 // Layout
682 ////////////////////////////////////////////////////////////////////////////
683
684 // Returns the display width of this box.
685 private int widthOf(Box b) {
686 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
687 }
688
689 // Returns the display height of this box.
690 private int heightOf(Box b) {
691 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
692 }
693
694 // Returns the display width of this box, using the given scale.
695 private int widthOf(Box b, float scale) {
696 return (int) (b.mImageW * scale + 0.5f);
697 }
698
699 // Returns the display height of this box, using the given scale.
700 private int heightOf(Box b, float scale) {
701 return (int) (b.mImageH * scale + 0.5f);
702 }
703
704 // Convert the information in mPlatform and mBoxes to mRects, so the user
705 // can get the position of each box by getPosition().
706 //
707 // Note the loop index goes from inside-out because each box's X coordinate
708 // is relative to its anchor box (except the focused box).
709 private void layoutAndSetPosition() {
710 // layout box 0 (focused box)
711 convertBoxToRect(0);
712 for (int i = 1; i <= BOX_MAX; i++) {
713 // layout box i and -i
714 convertBoxToRect(i);
715 convertBoxToRect(-i);
716 }
717 //dumpState();
718 }
719
720 private void dumpState() {
721 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
722 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800723 }
724
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800725 dumpRect(0);
726 for (int i = 1; i <= BOX_MAX; i++) {
727 dumpRect(i);
728 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800729 }
730
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800731 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
732 for (int j = i + 1; j <= BOX_MAX; j++) {
733 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
734 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
735 }
736 }
737 }
738 }
739
740 private void dumpRect(int i) {
741 StringBuilder sb = new StringBuilder();
742 Rect r = mRects.get(i);
743 sb.append("Rect " + i + ":");
744 sb.append("(");
745 sb.append(r.centerX());
746 sb.append(",");
747 sb.append(r.centerY());
748 sb.append(") [");
749 sb.append(r.width());
750 sb.append("x");
751 sb.append(r.height());
752 sb.append("]");
753 Log.d(TAG, sb.toString());
754 }
755
756 private void convertBoxToRect(int i) {
757 Box b = mBoxes.get(i);
758 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800759 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800760 int w = widthOf(b);
761 int h = heightOf(b);
762 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800763 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800764 r.left = x - w / 2;
765 r.right = r.left + w;
766 } else if (i > 0) {
767 Rect a = mRects.get(i - 1);
768 Gap g = mGaps.get(i - 1);
769 r.left = a.right + g.mCurrentGap;
770 r.right = r.left + w;
771 } else { // i < 0
772 Rect a = mRects.get(i + 1);
773 Gap g = mGaps.get(i);
774 r.right = a.left - g.mCurrentGap;
775 r.left = r.right - w;
776 }
777 r.top = y - h / 2;
778 r.bottom = r.top + h;
779 }
780
781 // Returns the position of a box.
782 public Rect getPosition(int index) {
783 return mRects.get(index);
784 }
785
786 ////////////////////////////////////////////////////////////////////////////
787 // Box management
788 ////////////////////////////////////////////////////////////////////////////
789
790 // Initialize the platform to be at the view center.
791 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800792 mPlatform.updateDefaultXY();
793 mPlatform.mCurrentX = mPlatform.mDefaultX;
794 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800795 mPlatform.mAnimationStartTime = NO_ANIMATION;
796 }
797
798 // Initialize a box to have the size of the view.
799 private void initBox(int index) {
800 Box b = mBoxes.get(index);
801 b.mImageW = mViewW;
802 b.mImageH = mViewH;
803 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800804 b.mScaleMin = getMinimalScale(b);
805 b.mScaleMax = getMaximalScale(b);
806 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800807 b.mCurrentScale = b.mScaleMin;
808 b.mAnimationStartTime = NO_ANIMATION;
809 }
810
811 // Initialize a gap. This can only be called after the boxes around the gap
812 // has been initialized.
813 private void initGap(int index) {
814 Gap g = mGaps.get(index);
815 g.mDefaultSize = getDefaultGapSize(index);
816 g.mCurrentGap = g.mDefaultSize;
817 g.mAnimationStartTime = NO_ANIMATION;
818 }
819
820 private void initGap(int index, int size) {
821 Gap g = mGaps.get(index);
822 g.mDefaultSize = getDefaultGapSize(index);
823 g.mCurrentGap = size;
824 g.mAnimationStartTime = NO_ANIMATION;
825 }
826
827 private void debugMoveBox(int fromIndex[]) {
828 StringBuilder s = new StringBuilder("moveBox:");
829 for (int i = 0; i < fromIndex.length; i++) {
830 int j = fromIndex[i];
831 if (j == Integer.MAX_VALUE) {
832 s.append(" N");
833 } else {
834 s.append(" ");
835 s.append(fromIndex[i]);
836 }
837 }
838 Log.d(TAG, s.toString());
839 }
840
841 // Move the boxes: it may indicate focus change, box deleted, box appearing,
842 // box reordered, etc.
843 //
844 // Each element in the fromIndex array indicates where each box was in the
845 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
846 // means the box is new.
847 //
848 // For example:
849 // N N N N N N N -- all new boxes
850 // -3 -2 -1 0 1 2 3 -- nothing changed
851 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800852 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800853 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800854 //
855 // hasPrev/hasNext indicates if there are previous/next boxes for the
856 // focused box. constrained indicates whether the focused box should be put
857 // into the constrained frame.
858 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
859 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800860 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800861 mHasPrev = hasPrev;
862 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800863
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800864 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
865
866 // 1. Get the absolute X coordiates for the boxes.
867 layoutAndSetPosition();
868 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
869 Box b = mBoxes.get(i);
870 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800871 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800872 }
873
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800874 // 2. copy boxes and gaps to temporary storage.
875 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
876 mTempBoxes.put(i, mBoxes.get(i));
877 mBoxes.put(i, null);
878 }
879 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
880 mTempGaps.put(i, mGaps.get(i));
881 mGaps.put(i, null);
882 }
883
884 // 3. move back boxes that are used in the new array.
885 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
886 int j = from.get(i);
887 if (j == Integer.MAX_VALUE) continue;
888 mBoxes.put(i, mTempBoxes.get(j));
889 mTempBoxes.put(j, null);
890 }
891
892 // 4. move back gaps if both boxes around it are kept together.
893 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
894 int j = from.get(i);
895 if (j == Integer.MAX_VALUE) continue;
896 int k = from.get(i + 1);
897 if (k == Integer.MAX_VALUE) continue;
898 if (j + 1 == k) {
899 mGaps.put(i, mTempGaps.get(j));
900 mTempGaps.put(j, null);
901 }
902 }
903
904 // 5. recycle the boxes that are not used in the new array.
905 int k = -BOX_MAX;
906 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
907 if (mBoxes.get(i) != null) continue;
908 while (mTempBoxes.get(k) == null) {
909 k++;
910 }
911 mBoxes.put(i, mTempBoxes.get(k++));
912 initBox(i);
913 }
914
915 // 6. Now give the recycled box a reasonable absolute X position.
916 //
917 // First try to find the first and the last box which the absolute X
918 // position is known.
919 int first, last;
920 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
921 if (from.get(first) != Integer.MAX_VALUE) break;
922 }
923 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
924 if (from.get(last) != Integer.MAX_VALUE) break;
925 }
926 // If there is no box has known X position at all, make the focused one
927 // as known.
928 if (first > BOX_MAX) {
929 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
930 first = last = 0;
931 }
932 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800933 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800934 // rare). For the boxes before first or after last, we will use a new
935 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800936 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800937 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800938 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800939 }
940
941 // 7. recycle the gaps that are not used in the new array.
942 k = -BOX_MAX;
943 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
944 if (mGaps.get(i) != null) continue;
945 while (mTempGaps.get(k) == null) {
946 k++;
947 }
948 mGaps.put(i, mTempGaps.get(k++));
949 Box a = mBoxes.get(i);
950 Box b = mBoxes.get(i + 1);
951 int wa = widthOf(a);
952 int wb = widthOf(b);
953 if (i >= first && i < last) {
954 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
955 initGap(i, g);
956 } else {
957 initGap(i);
958 }
959 }
960
961 // 8. offset the Platform position
962 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
963 mPlatform.mCurrentX += dx;
964 mPlatform.mFromX += dx;
965 mPlatform.mToX += dx;
966 mPlatform.mFlingOffset += dx;
967
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800968 if (mConstrained != constrained) {
969 mConstrained = constrained;
970 mPlatform.updateDefaultXY();
971 updateScaleAndGapLimit();
972 }
973
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800974 snapAndRedraw();
975 }
976
977 ////////////////////////////////////////////////////////////////////////////
978 // Public utilities
979 ////////////////////////////////////////////////////////////////////////////
980
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800981 public boolean isAtMinimalScale() {
982 Box b = mBoxes.get(0);
983 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
984 }
985
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800986 public boolean isCenter() {
987 Box b = mBoxes.get(0);
988 return mPlatform.mCurrentX == mPlatform.mDefaultX
989 && b.mCurrentY == 0;
990 }
991
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800992 public int getImageWidth() {
993 Box b = mBoxes.get(0);
994 return b.mImageW;
995 }
996
997 public int getImageHeight() {
998 Box b = mBoxes.get(0);
999 return b.mImageH;
1000 }
1001
1002 public float getImageScale() {
1003 Box b = mBoxes.get(0);
1004 return b.mCurrentScale;
1005 }
1006
1007 public int getImageAtEdges() {
1008 Box b = mBoxes.get(0);
1009 Platform p = mPlatform;
1010 calculateStableBound(b.mCurrentScale);
1011 int edges = 0;
1012 if (p.mCurrentX <= mBoundLeft) {
1013 edges |= IMAGE_AT_RIGHT_EDGE;
1014 }
1015 if (p.mCurrentX >= mBoundRight) {
1016 edges |= IMAGE_AT_LEFT_EDGE;
1017 }
1018 if (b.mCurrentY <= mBoundTop) {
1019 edges |= IMAGE_AT_BOTTOM_EDGE;
1020 }
1021 if (b.mCurrentY >= mBoundBottom) {
1022 edges |= IMAGE_AT_TOP_EDGE;
1023 }
1024 return edges;
1025 }
1026
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001027 public boolean isScrolling() {
1028 return mPlatform.mAnimationStartTime != NO_ANIMATION
1029 && mPlatform.mCurrentX != mPlatform.mToX;
1030 }
1031
1032 public void stopScrolling() {
1033 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
1034 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1035 }
1036
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001037 public float getFilmRatio() {
1038 return mFilmRatio.mCurrentRatio;
1039 }
1040
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001041 ////////////////////////////////////////////////////////////////////////////
1042 // Private utilities
1043 ////////////////////////////////////////////////////////////////////////////
1044
1045 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001046 float wFactor = 1.0f;
1047 float hFactor = 1.0f;
1048 int viewW, viewH;
1049
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001050 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1051 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001052 viewW = mConstrainedFrame.width();
1053 viewH = mConstrainedFrame.height();
1054 } else {
1055 viewW = mViewW;
1056 viewH = mViewH;
1057 }
1058
1059 if (mFilmMode) {
1060 if (mViewH > mViewW) { // portrait
1061 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1062 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1063 } else { // landscape
1064 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1065 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1066 }
1067 }
1068
1069 float s = Math.min(wFactor * viewW / b.mImageW,
1070 hFactor * viewH / b.mImageH);
1071 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001072 }
1073
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001074 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001075 if (mFilmMode) return getMinimalScale(b);
1076 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1077 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001078 }
1079
1080 private static boolean isAlmostEqual(float a, float b) {
1081 float diff = a - b;
1082 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001083 }
1084
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001085 // Calculates the stable region of mPlatform.mCurrentX and
1086 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001087 //
1088 // (1) If the dimension of scaled image >= view dimension, we will not
1089 // see black region outside the image (at that dimension).
1090 // (2) If the dimension of scaled image < view dimension, we will center
1091 // the scaled image.
1092 //
1093 // We might temporarily go out of this stable during user interaction,
1094 // but will "snap back" after user stops interaction.
1095 //
1096 // The results are stored in mBound{Left/Right/Top/Bottom}.
1097 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001098 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1099 // is used to extend the stable region by some pixels on each side
1100 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001101 private void calculateStableBound(float scale, int horizontalSlack) {
1102 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001103
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001104 // The width and height of the box in number of view pixels
1105 int w = widthOf(b, scale);
1106 int h = heightOf(b, scale);
1107
1108 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001109 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1110 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1111 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1112 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001113
1114 // If the scaled height is smaller than the view height,
1115 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001116 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001117 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001118 }
1119
1120 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001121 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001122 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001123 }
1124 }
1125
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001126 private void calculateStableBound(float scale) {
1127 calculateStableBound(scale, 0);
1128 }
1129
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001130 private boolean viewTallerThanScaledImage(float scale) {
1131 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001132 }
1133
1134 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001135 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001136 }
1137
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001138 private float getTargetScale(Box b) {
1139 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001140 }
1141
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001142 private int getTargetX(Platform p) {
1143 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001144 }
1145
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001146 private int getTargetY(Box b) {
1147 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001148 }
1149
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001150 private boolean useCurrentValueAsTarget(Animatable a) {
1151 return a.mAnimationStartTime == NO_ANIMATION ||
1152 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1153 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001154 }
1155
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001156 ////////////////////////////////////////////////////////////////////////////
1157 // Animatable: an thing which can do animation.
1158 ////////////////////////////////////////////////////////////////////////////
1159 private abstract static class Animatable {
1160 public long mAnimationStartTime;
1161 public int mAnimationKind;
1162 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001163
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001164 // This should be overidden in subclass to change the animation values
1165 // give the progress value in [0, 1].
1166 protected abstract boolean interpolate(float progress);
1167 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001168
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001169 // Returns true if the animation values changes, so things need to be
1170 // redrawn.
1171 public boolean advanceAnimation() {
1172 if (mAnimationStartTime == NO_ANIMATION) {
1173 return false;
1174 }
1175 if (mAnimationStartTime == LAST_ANIMATION) {
1176 mAnimationStartTime = NO_ANIMATION;
1177 return startSnapback();
1178 }
1179
1180 float progress;
1181 if (mAnimationDuration == 0) {
1182 progress = 1;
1183 } else {
1184 long now = AnimationTime.get();
1185 progress =
1186 (float) (now - mAnimationStartTime) / mAnimationDuration;
1187 }
1188
1189 if (progress >= 1) {
1190 progress = 1;
1191 } else {
1192 progress = applyInterpolationCurve(mAnimationKind, progress);
1193 }
1194
1195 boolean done = interpolate(progress);
1196
1197 if (done) {
1198 mAnimationStartTime = LAST_ANIMATION;
1199 }
1200
1201 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001202 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001203
1204 private static float applyInterpolationCurve(int kind, float progress) {
1205 float f = 1 - progress;
1206 switch (kind) {
1207 case ANIM_KIND_SCROLL:
1208 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001209 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001210 progress = 1 - f; // linear
1211 break;
1212 case ANIM_KIND_SCALE:
1213 progress = 1 - f * f; // quadratic
1214 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001215 case ANIM_KIND_OPENING:
1216 progress = 1 - f * f * f; // x^3
1217 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001218 case ANIM_KIND_SNAPBACK:
1219 case ANIM_KIND_ZOOM:
1220 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001221 progress = 1 - f * f * f * f * f; // x^5
1222 break;
1223 }
1224 return progress;
1225 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001226 }
1227
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001228 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001229 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001230 ////////////////////////////////////////////////////////////////////////////
1231 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001232 public int mCurrentX, mFromX, mToX, mDefaultX;
1233 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001234 public int mFlingOffset;
1235
1236 @Override
1237 public boolean startSnapback() {
1238 if (mAnimationStartTime != NO_ANIMATION) return false;
1239 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001240 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001241
1242 Box b = mBoxes.get(0);
1243 float scaleMin = mExtraScalingRange ?
1244 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1245 float scaleMax = mExtraScalingRange ?
1246 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1247 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1248 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001249 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001250 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001251 int defaultX = mDefaultX;
1252 if (!mHasNext) x = Math.max(x, defaultX);
1253 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001254 } else {
1255 calculateStableBound(scale, HORIZONTAL_SLACK);
1256 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1257 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001258 if (mCurrentX != x || mCurrentY != y) {
1259 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001260 }
1261 return false;
1262 }
1263
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001264 // The updateDefaultXY() should be called whenever these variables
1265 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1266 // mFilmMode
1267 public void updateDefaultXY() {
1268 // We don't check mFilmMode and return 0 for mDefaultX. Because
1269 // otherwise if we decide to leave film mode because we are
1270 // centered, we will immediately back into film mode because we find
1271 // we are not centered.
1272 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1273 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1274 mDefaultY = mFilmMode ? 0 :
1275 mConstrainedFrame.centerY() - mViewH / 2;
1276 } else {
1277 mDefaultX = 0;
1278 mDefaultY = 0;
1279 }
1280 }
1281
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001282 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001283 private boolean doAnimation(int targetX, int targetY, int kind) {
1284 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001285 mAnimationKind = kind;
1286 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001287 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001288 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001289 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001290 mAnimationStartTime = AnimationTime.startTime();
1291 mAnimationDuration = ANIM_TIME[kind];
1292 mFlingOffset = 0;
1293 advanceAnimation();
1294 return true;
1295 }
1296
1297 @Override
1298 protected boolean interpolate(float progress) {
1299 if (mAnimationKind == ANIM_KIND_FLING) {
1300 return mFilmMode
1301 ? interpolateFlingFilm(progress)
1302 : interpolateFlingPage(progress);
1303 } else {
1304 return interpolateLinear(progress);
1305 }
1306 }
1307
1308 private boolean interpolateFlingFilm(float progress) {
1309 mFilmScroller.computeScrollOffset();
1310 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1311
1312 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001313 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001314 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001315 dir = EdgeView.RIGHT;
1316 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001317 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001318 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001319 dir = EdgeView.LEFT;
1320 }
1321 }
1322 if (dir != EdgeView.INVALID_DIRECTION) {
1323 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1324 mListener.onAbsorb(v, dir);
1325 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001326 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001327 }
1328 return mFilmScroller.isFinished();
1329 }
1330
1331 private boolean interpolateFlingPage(float progress) {
1332 mPageScroller.computeScrollOffset(progress);
1333 Box b = mBoxes.get(0);
1334 calculateStableBound(b.mCurrentScale);
1335
1336 int oldX = mCurrentX;
1337 mCurrentX = mPageScroller.getCurrX();
1338
1339 // Check if we hit the edges; show edge effects if we do.
1340 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1341 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1342 mListener.onAbsorb(v, EdgeView.RIGHT);
1343 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1344 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1345 mListener.onAbsorb(v, EdgeView.LEFT);
1346 }
1347
1348 return progress >= 1;
1349 }
1350
1351 private boolean interpolateLinear(float progress) {
1352 // Other animations
1353 if (progress >= 1) {
1354 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001355 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001356 return true;
1357 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001358 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1359 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001360 }
1361 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1362 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1363 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001364 return false;
1365 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001366 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001367 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001368 }
1369 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001370 }
1371
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001372 ////////////////////////////////////////////////////////////////////////////
1373 // Box: represents a rectangular area which shows a picture.
1374 ////////////////////////////////////////////////////////////////////////////
1375 private class Box extends Animatable {
1376 // Size of the bitmap
1377 public int mImageW, mImageH;
1378
1379 // This is true if we assume the image size is the same as view size
1380 // until we know the actual size of image. This is also used to
1381 // determine if there is an image ready to show.
1382 public boolean mUseViewSize;
1383
1384 // The minimum and maximum scale we allow for this box.
1385 public float mScaleMin, mScaleMax;
1386
1387 // The X/Y value indicates where the center of the box is on the view
1388 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1389 // actual values used currently. Note that the X values are implicitly
1390 // defined by Platform and Gaps.
1391 public int mCurrentY, mFromY, mToY;
1392 public float mCurrentScale, mFromScale, mToScale;
1393
1394 // The absolute X coordinate of the center of the box. This is only used
1395 // during moveBox().
1396 public int mAbsoluteX;
1397
1398 @Override
1399 public boolean startSnapback() {
1400 if (mAnimationStartTime != NO_ANIMATION) return false;
1401 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001402 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001403 if (mInScale && this == mBoxes.get(0)) return false;
1404
1405 int y;
1406 float scale;
1407
1408 if (this == mBoxes.get(0)) {
1409 float scaleMin = mExtraScalingRange ?
1410 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1411 float scaleMax = mExtraScalingRange ?
1412 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1413 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1414 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001415 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001416 } else {
1417 calculateStableBound(scale, HORIZONTAL_SLACK);
1418 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1419 }
1420 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001421 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001422 scale = mScaleMin;
1423 }
1424
1425 if (mCurrentY != y || mCurrentScale != scale) {
1426 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1427 }
1428 return false;
1429 }
1430
1431 private boolean doAnimation(int targetY, float targetScale, int kind) {
1432 targetScale = Utils.clamp(targetScale,
1433 SCALE_MIN_EXTRA * mScaleMin,
1434 SCALE_MAX_EXTRA * mScaleMax);
1435
1436 // If the scaled height is smaller than the view height, force it to be
1437 // in the center. (We do this for height only, not width, because the
1438 // user may want to scroll to the previous/next image.)
1439 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001440 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001441 }
1442
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001443 if (mCurrentY == targetY && mCurrentScale == targetScale
1444 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001445 return false;
1446 }
1447
1448 // Now starts an animation for the box.
1449 mAnimationKind = kind;
1450 mFromY = mCurrentY;
1451 mFromScale = mCurrentScale;
1452 mToY = targetY;
1453 mToScale = targetScale;
1454 mAnimationStartTime = AnimationTime.startTime();
1455 mAnimationDuration = ANIM_TIME[kind];
1456 advanceAnimation();
1457 return true;
1458 }
1459
1460 @Override
1461 protected boolean interpolate(float progress) {
1462 if (mAnimationKind == ANIM_KIND_FLING) {
1463 // Currently a Box can only be flung in page mode.
1464 return interpolateFlingPage(progress);
1465 } else {
1466 return interpolateLinear(progress);
1467 }
1468 }
1469
1470 private boolean interpolateFlingPage(float progress) {
1471 mPageScroller.computeScrollOffset(progress);
1472 calculateStableBound(mCurrentScale);
1473
1474 int oldY = mCurrentY;
1475 mCurrentY = mPageScroller.getCurrY();
1476
1477 // Check if we hit the edges; show edge effects if we do.
1478 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1479 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1480 mListener.onAbsorb(v, EdgeView.BOTTOM);
1481 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1482 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1483 mListener.onAbsorb(v, EdgeView.TOP);
1484 }
1485
1486 return progress >= 1;
1487 }
1488
1489 private boolean interpolateLinear(float progress) {
1490 if (progress >= 1) {
1491 mCurrentY = mToY;
1492 mCurrentScale = mToScale;
1493 return true;
1494 } else {
1495 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1496 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001497 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1498 float f = CaptureAnimation.calculateScale(progress);
1499 mCurrentScale *= f;
1500 return false;
1501 } else {
1502 return (mCurrentY == mToY && mCurrentScale == mToScale);
1503 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001504 }
1505 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001506 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001507
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001508 ////////////////////////////////////////////////////////////////////////////
1509 // Gap: represents a rectangular area which is between two boxes.
1510 ////////////////////////////////////////////////////////////////////////////
1511 private class Gap extends Animatable {
1512 // The default gap size between two boxes. The value may vary for
1513 // different image size of the boxes and for different modes (page or
1514 // film).
1515 public int mDefaultSize;
1516
1517 // The gap size between the two boxes.
1518 public int mCurrentGap, mFromGap, mToGap;
1519
1520 @Override
1521 public boolean startSnapback() {
1522 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001523 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001524 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001525
1526 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001527 public boolean doAnimation(int targetSize, int kind) {
1528 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1529 return false;
1530 }
1531 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001532 mFromGap = mCurrentGap;
1533 mToGap = targetSize;
1534 mAnimationStartTime = AnimationTime.startTime();
1535 mAnimationDuration = ANIM_TIME[mAnimationKind];
1536 advanceAnimation();
1537 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001538 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001539
1540 @Override
1541 protected boolean interpolate(float progress) {
1542 if (progress >= 1) {
1543 mCurrentGap = mToGap;
1544 return true;
1545 } else {
1546 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001547 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1548 float f = CaptureAnimation.calculateScale(progress);
1549 mCurrentGap = (int) (mCurrentGap * f);
1550 return false;
1551 } else {
1552 return (mCurrentGap == mToGap);
1553 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001554 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001555 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001556 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001557
1558 ////////////////////////////////////////////////////////////////////////////
1559 // FilmRatio: represents the progress of film mode change.
1560 ////////////////////////////////////////////////////////////////////////////
1561 private class FilmRatio extends Animatable {
1562 // The film ratio: 1 means switching to film mode is complete, 0 means
1563 // switching to page mode is complete.
1564 public float mCurrentRatio, mFromRatio, mToRatio;
1565
1566 @Override
1567 public boolean startSnapback() {
1568 float target = mFilmMode ? 1f : 0f;
1569 if (target == mToRatio) return false;
1570 return doAnimation(target, ANIM_KIND_SNAPBACK);
1571 }
1572
1573 // Starts an animation for the film ratio.
1574 private boolean doAnimation(float targetRatio, int kind) {
1575 mAnimationKind = kind;
1576 mFromRatio = mCurrentRatio;
1577 mToRatio = targetRatio;
1578 mAnimationStartTime = AnimationTime.startTime();
1579 mAnimationDuration = ANIM_TIME[mAnimationKind];
1580 advanceAnimation();
1581 return true;
1582 }
1583
1584 @Override
1585 protected boolean interpolate(float progress) {
1586 if (progress >= 1) {
1587 mCurrentRatio = mToRatio;
1588 return true;
1589 } else {
1590 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1591 return (mCurrentRatio == mToRatio);
1592 }
1593 }
1594 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001595}