blob: db77b99bad0816068f6a9449438faf7b9291d198 [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 Changc4791b72012-05-18 19:10:36 -070037 public static final int CAPTURE_ANIMATION_TIME = 700;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080038
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080039 // Special values for animation time.
Chih-Chung Changec412542011-09-26 17:34:06 +080040 private static final long NO_ANIMATION = -1;
41 private static final long LAST_ANIMATION = -2;
42
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080043 private static final int ANIM_KIND_SCROLL = 0;
44 private static final int ANIM_KIND_SCALE = 1;
45 private static final int ANIM_KIND_SNAPBACK = 2;
46 private static final int ANIM_KIND_SLIDE = 3;
47 private static final int ANIM_KIND_ZOOM = 4;
48 private static final int ANIM_KIND_OPENING = 5;
49 private static final int ANIM_KIND_FLING = 6;
Chih-Chung Chang2c617382012-04-20 20:06:19 +080050 private static final int ANIM_KIND_CAPTURE = 7;
Chih-Chung Changec412542011-09-26 17:34:06 +080051
Chih-Chung Chang676170e2011-09-30 18:33:17 +080052 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080053 private static final int ANIM_TIME[] = {
Chih-Chung Chang676170e2011-09-30 18:33:17 +080054 0, // ANIM_KIND_SCROLL
55 50, // ANIM_KIND_SCALE
56 600, // ANIM_KIND_SNAPBACK
57 400, // ANIM_KIND_SLIDE
58 300, // ANIM_KIND_ZOOM
Yuli Huang5338d192012-05-17 11:32:15 +080059 400, // ANIM_KIND_OPENING
Chih-Chung Changb3aab902011-10-03 21:11:39 +080060 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080061 CAPTURE_ANIMATION_TIME, // ANIM_KIND_CAPTURE
Chih-Chung Chang676170e2011-09-30 18:33:17 +080062 };
63
Chih-Chung Changec412542011-09-26 17:34:06 +080064 // We try to scale up the image to fill the screen. But in order not to
65 // scale too much for small icons, we limit the max up-scaling factor here.
66 private static final float SCALE_LIMIT = 4;
67
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080068 // For user's gestures, we give a temporary extra scaling range which goes
69 // above or below the usual scaling limits.
70 private static final float SCALE_MIN_EXTRA = 0.7f;
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080071 private static final float SCALE_MAX_EXTRA = 1.4f;
72
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080073 // Setting this true makes the extra scaling range permanent (until this is
74 // set to false again).
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080075 private boolean mExtraScalingRange = false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +080076
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080077 // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
78 private boolean mFilmMode = false;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080079
Chih-Chung Chang642561d2012-04-16 16:29:13 +080080 // These are the limits for width / height of the picture in film mode.
81 private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
82 private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
83 private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
84 private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080085
86 // In addition to the focused box (index == 0). We also keep information
87 // about this many boxes on each side.
88 private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
89
Chih-Chung Changfb1a1552012-04-19 13:34:48 +080090 private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080091 private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
92
93 private Listener mListener;
94 private volatile Rect mOpenAnimationRect;
Chih-Chung Changba12eae2012-05-07 02:29:37 +080095
96 // Use a large enough value, so we won't see the gray shadown in the beginning.
97 private int mViewW = 1200;
98 private int mViewH = 1200;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080099
100 // A scaling guesture is in progress.
101 private boolean mInScale;
102 // The focus point of the scaling gesture, relative to the center of the
103 // picture in bitmap pixels.
104 private float mFocusX, mFocusY;
105
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800106 // whether there is a previous/next picture.
107 private boolean mHasPrev, mHasNext;
108
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800109 // This is used by the fling animation (page mode).
110 private FlingScroller mPageScroller;
111
112 // This is used by the fling animation (film mode).
Chih-Chung Changd8488622012-04-17 12:56:08 +0800113 private OverScroller mFilmScroller;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800114
115 // The bound of the stable region that the focused box can stay, see the
116 // comments above calculateStableBound() for details.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800117 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
118
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800119 // Constrained frame is a rectangle that the focused box should fit into if
120 // it is constrained. It has two effects:
121 //
122 // (1) In page mode, if the focused box is constrained, scaling for the
123 // focused box is adjusted to fit into the constrained frame, instead of the
124 // whole view.
125 //
126 // (2) In page mode, if the focused box is constrained, the mPlatform's
127 // default center (mDefaultX/Y) is moved to the center of the constrained
128 // frame, instead of the view center.
129 //
130 private Rect mConstrainedFrame = new Rect();
131
132 // Whether the focused box is constrained.
133 //
134 // Our current program's first call to moveBox() sets constrained = true, so
135 // we set the initial value of this variable to true, and we will not see
136 // see unwanted transition animation.
137 private boolean mConstrained = true;
138
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800139 //
140 // ___________________________________________________________
141 // | _____ _____ _____ _____ _____ |
142 // | | | | | | | | | | | |
143 // | | Box | | Box | | Box*| | Box | | Box | |
144 // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
145 // | Gap Gap Gap Gap |
146 // |___________________________________________________________|
147 //
148 // <-- Platform -->
149 //
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800150 // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
Chih-Chung Changec412542011-09-26 17:34:06 +0800151
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800152 private Platform mPlatform = new Platform();
153 private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
154 // The gap at the right of a Box i is at index i. The gap at the left of a
155 // Box i is at index i - 1.
156 private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800157 private FilmRatio mFilmRatio = new FilmRatio();
Chih-Chung Changec412542011-09-26 17:34:06 +0800158
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800159 // These are only used during moveBox().
160 private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800161 private RangeArray<Gap> mTempGaps =
162 new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800163
164 // The output of the PositionController. Available throught getPosition().
165 private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
166
167 public interface Listener {
168 void invalidate();
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800169 boolean isHolding();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800170
171 // EdgeView
172 void onPull(int offset, int direction);
173 void onRelease();
174 void onAbsorb(int velocity, int direction);
Chih-Chung Changec412542011-09-26 17:34:06 +0800175 }
176
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800177 public PositionController(Context context, Listener listener) {
178 mListener = listener;
179 mPageScroller = new FlingScroller();
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800180 mFilmScroller = new OverScroller(context,
181 null /* default interpolator */, false /* no flywheel */);
Chih-Chung Changec412542011-09-26 17:34:06 +0800182
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800183 // Initialize the areas.
184 initPlatform();
185 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
186 mBoxes.put(i, new Box());
187 initBox(i);
188 mRects.put(i, new Rect());
189 }
190 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
191 mGaps.put(i, new Gap());
192 initGap(i);
193 }
194 }
195
196 public void setOpenAnimationRect(Rect r) {
197 mOpenAnimationRect = r;
198 }
199
200 public void setViewSize(int viewW, int viewH) {
201 if (viewW == mViewW && viewH == mViewH) return;
202
203 mViewW = viewW;
204 mViewH = viewH;
205 initPlatform();
206
207 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
208 setBoxSize(i, viewW, viewH, true);
209 }
210
211 updateScaleAndGapLimit();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700212
213 // If we have the opening animation, do it. Otherwise go directly to the
214 // right position.
215 if (!startOpeningAnimationIfNeeded()) {
216 snapAndRedraw();
217 skipAnimation();
218 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800219 }
220
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800221 public void setConstrainedFrame(Rect f) {
222 if (mConstrainedFrame.equals(f)) return;
223 mConstrainedFrame.set(f);
224 mPlatform.updateDefaultXY();
225 updateScaleAndGapLimit();
226 snapAndRedraw();
227 }
228
229 public void setImageSize(int index, int width, int height, boolean force) {
230 if (force) {
231 Box b = mBoxes.get(index);
232 b.mImageW = width;
233 b.mImageH = height;
234 return;
235 }
236
Chih-Chung Changec412542011-09-26 17:34:06 +0800237 if (width == 0 || height == 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800238 initBox(index);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800239 } else if (!setBoxSize(index, width, height, false)) {
240 return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800241 }
242
243 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800244 snapAndRedraw();
245 }
246
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800247 // Returns false if the box size doesn't change.
248 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800249 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800250 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800251
Chih-Chung Changd8488622012-04-17 12:56:08 +0800252 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800253 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800254
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800255 b.mUseViewSize = isViewSize;
256
257 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800258 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800259 }
260
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800261 // The ratio of the old size and the new size.
Chih-Chung Changec412542011-09-26 17:34:06 +0800262 float ratio = Math.min(
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800263 (float) b.mImageW / width, (float) b.mImageH / height);
Chih-Chung Changec412542011-09-26 17:34:06 +0800264
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800265 b.mImageW = width;
266 b.mImageH = height;
267
Chih-Chung Changd8488622012-04-17 12:56:08 +0800268 // If this is the first time we receive an image size, we change the
269 // scale directly. Otherwise adjust the scales by a ratio, and snapback
270 // will animate the scale into the min/max bounds if necessary.
271 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800272 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800273 b.mAnimationStartTime = NO_ANIMATION;
274 } else {
275 b.mCurrentScale *= ratio;
276 b.mFromScale *= ratio;
277 b.mToScale *= ratio;
278 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800279
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800280 if (i == 0) {
281 mFocusX /= ratio;
282 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800283 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800284
285 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800286 }
287
Chih-Chung Change6251df2012-05-22 11:35:46 -0700288 private boolean startOpeningAnimationIfNeeded() {
289 if (mOpenAnimationRect == null) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800290 Box b = mBoxes.get(0);
Chih-Chung Change6251df2012-05-22 11:35:46 -0700291 if (b.mUseViewSize) return false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800292
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800293 // Start animation from the saved rectangle if we have one.
294 Rect r = mOpenAnimationRect;
295 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800296
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800297 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
298 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800299 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
300 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800301 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
302 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800303
304 // Animate from large gaps for neighbor boxes to avoid them
305 // shown on the screen during opening animation.
306 for (int i = -1; i < 1; i++) {
307 Gap g = mGaps.get(i);
308 g.mCurrentGap = mViewW;
309 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
310 }
Chih-Chung Change6251df2012-05-22 11:35:46 -0700311
312 return true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800313 }
314
315 public void setFilmMode(boolean enabled) {
316 if (enabled == mFilmMode) return;
317 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800318
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800319 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800320 updateScaleAndGapLimit();
321 stopAnimation();
322 snapAndRedraw();
323 }
324
325 public void setExtraScalingRange(boolean enabled) {
326 if (mExtraScalingRange == enabled) return;
327 mExtraScalingRange = enabled;
328 if (!enabled) {
329 snapAndRedraw();
330 }
331 }
332
333 // This should be called whenever the scale range of boxes or the default
334 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800335 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800336 private void updateScaleAndGapLimit() {
337 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
338 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800339 b.mScaleMin = getMinimalScale(b);
340 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800341 }
342
343 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
344 Gap g = mGaps.get(i);
345 g.mDefaultSize = getDefaultGapSize(i);
346 }
347 }
348
349 // Returns the default gap size according the the size of the boxes around
350 // the gap and the current mode.
351 private int getDefaultGapSize(int i) {
352 if (mFilmMode) return IMAGE_GAP;
353 Box a = mBoxes.get(i);
354 Box b = mBoxes.get(i + 1);
355 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
356 }
357
358 // Here is how we layout the boxes in the page mode.
359 //
360 // previous current next
361 // ___________ ________________ __________
362 // | _______ | | __________ | | ______ |
363 // | | | | | | right->| | | | | |
364 // | | |<-------->|<--left | | | | | |
365 // | |_______| | | | |__________| | | |______| |
366 // |___________| | |________________| |__________|
367 // | <--> gapToSide()
368 // |
369 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
370 private int gapToSide(Box b) {
371 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
372 }
373
374 // Stop all animations at where they are now.
375 public void stopAnimation() {
376 mPlatform.mAnimationStartTime = NO_ANIMATION;
377 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
378 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
379 }
380 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
381 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
382 }
383 }
384
385 public void skipAnimation() {
386 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
387 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800388 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800389 mPlatform.mAnimationStartTime = NO_ANIMATION;
390 }
391 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
392 Box b = mBoxes.get(i);
393 if (b.mAnimationStartTime == NO_ANIMATION) continue;
394 b.mCurrentY = b.mToY;
395 b.mCurrentScale = b.mToScale;
396 b.mAnimationStartTime = NO_ANIMATION;
397 }
398 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
399 Gap g = mGaps.get(i);
400 if (g.mAnimationStartTime == NO_ANIMATION) continue;
401 g.mCurrentGap = g.mToGap;
402 g.mAnimationStartTime = NO_ANIMATION;
403 }
404 redraw();
405 }
406
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800407 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800408 snapAndRedraw();
409 }
410
411 ////////////////////////////////////////////////////////////////////////////
412 // Start an animations for the focused box
413 ////////////////////////////////////////////////////////////////////////////
414
415 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800416 tapX -= mViewW / 2;
417 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800418 Box b = mBoxes.get(0);
419
420 // Convert the tap position to distance to center in bitmap coordinates
421 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
422 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
423
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800424 int x = (int) (-tempX * targetScale + 0.5f);
425 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800426
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800427 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800428 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
429 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
430 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800431
432 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
433 }
434
435 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800436 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800437 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800438 }
439
Chih-Chung Changec412542011-09-26 17:34:06 +0800440 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800441 focusX -= mViewW / 2;
442 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800443 Box b = mBoxes.get(0);
444 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800445 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800446 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
447 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800448 }
449
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800450 // Scales the image by the given factor.
451 // Returns an out-of-range indicator:
452 // 1 if the intended scale is too large for the stable range.
453 // 0 if the intended scale is in the stable range.
454 // -1 if the intended scale is too small for the stable range.
455 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800456 focusX -= mViewW / 2;
457 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800458 Box b = mBoxes.get(0);
459 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800460
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800461 // We want to keep the focus point (on the bitmap) the same as when we
462 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800463 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800464 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800465 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800466 s *= getTargetScale(b);
467 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
468 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800469 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800470 if (s < b.mScaleMin) return -1;
471 if (s > b.mScaleMax) return 1;
472 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800473 }
474
475 public void endScale() {
476 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800477 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800478 }
479
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800480 // Slide the focused box to the center of the view.
481 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800482 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800483 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800484 }
485
486 // Slide the focused box to the center of the view with the capture
487 // animation. In addition to the sliding, the animation will also scale the
488 // the focused box, the specified neighbor box, and the gap between the
489 // two. The specified offset should be 1 or -1.
490 public void startCaptureAnimationSlide(int offset) {
491 Box b = mBoxes.get(0);
492 Box n = mBoxes.get(offset); // the neighbor box
493 Gap g = mGaps.get(offset); // the gap between the two boxes
494
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800495 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
496 ANIM_KIND_CAPTURE);
497 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
498 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800499 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
500 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800501 }
502
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800503 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800504 Box b = mBoxes.get(0);
505 Platform p = mPlatform;
506
507 int x = getTargetX(p) + (int) (dx + 0.5f);
508 int y = getTargetY(b) + (int) (dy + 0.5f);
509
510 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800511 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800512 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800513 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800514 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800515 }
516
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800517 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800518 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800519
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800520 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800521
522 // Vertical direction: If we have space to move in the vertical
523 // direction, we show the edge effect when scrolling reaches the edge.
524 if (mBoundTop != mBoundBottom) {
525 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800526 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800527 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800528 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800529 }
530 }
531
532 y = Utils.clamp(y, mBoundTop, mBoundBottom);
533
534 // Horizontal direction: we show the edge effect when the scrolling
535 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800536 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800537 int pixels = x - mBoundRight;
538 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800539 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800540 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800541 int pixels = mBoundLeft - x;
542 mListener.onPull(pixels, EdgeView.RIGHT);
543 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800544 }
545
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800546 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
547 }
548
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800549 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800550 Box b = mBoxes.get(0);
551
552 // Horizontal direction: we show the edge effect when the scrolling
553 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800554 x -= mPlatform.mDefaultX;
555 if (!mHasPrev && x > 0) {
556 mListener.onPull(x, EdgeView.LEFT);
557 x = 0;
558 } else if (!mHasNext && x < 0) {
559 mListener.onPull(-x, EdgeView.RIGHT);
560 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800561 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800562 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800563 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800564 }
565
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800566 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800567 int vx = (int) (velocityX + 0.5f);
568 int vy = (int) (velocityY + 0.5f);
569 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
570 }
571
572 private boolean flingPage(int velocityX, int velocityY) {
573 Box b = mBoxes.get(0);
574 Platform p = mPlatform;
575
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800576 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800577 if (viewWiderThanScaledImage(b.mCurrentScale) &&
578 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800579 return false;
580 }
581
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800582 // We only allow flinging in the directions where it won't go over the
583 // picture.
584 int edges = getImageAtEdges();
585 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
586 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
587 velocityX = 0;
588 }
589 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
590 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
591 velocityY = 0;
592 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800593
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800594 if (velocityX == 0 && velocityY == 0) return false;
595
596 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800597 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800598 int targetX = mPageScroller.getFinalX();
599 int targetY = mPageScroller.getFinalY();
600 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
601 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800602 return true;
603 }
604
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800605 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800606 Box b = mBoxes.get(0);
607 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800608
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800609 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800610 int defaultX = p.mDefaultX;
611 if ((!mHasPrev && p.mCurrentX >= defaultX)
612 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800613 return false;
614 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800615
616 if (velocityX == 0) return false;
617
618 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
619 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
620 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800621 // This value doesn't matter because we use mFilmScroller.isFinished()
622 // to decide when to stop. We set this to 0 so it's faster for
623 // Animatable.advanceAnimation() to calculate the progress (always 1).
624 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800625 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
626 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800627 }
628
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800629 ////////////////////////////////////////////////////////////////////////////
630 // Redraw
631 //
632 // If a method changes box positions directly, redraw()
633 // should be called.
634 //
635 // If a method may also cause a snapback to happen, snapAndRedraw() should
636 // be called.
637 //
638 // If a method starts an animation to change the position of focused box,
639 // startAnimation() should be called.
640 //
641 // If time advances to change the box position, advanceAnimation() should
642 // be called.
643 ////////////////////////////////////////////////////////////////////////////
644 private void redraw() {
645 layoutAndSetPosition();
646 mListener.invalidate();
647 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800648
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800649 private void snapAndRedraw() {
650 mPlatform.startSnapback();
651 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
652 mBoxes.get(i).startSnapback();
653 }
654 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
655 mGaps.get(i).startSnapback();
656 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800657 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800658 redraw();
659 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800660
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800661 private void startAnimation(int targetX, int targetY, float targetScale,
662 int kind) {
663 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800664 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800665 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
666 if (changed) redraw();
667 }
668
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800669 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800670 boolean changed = false;
671 changed |= mPlatform.advanceAnimation();
672 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
673 changed |= mBoxes.get(i).advanceAnimation();
674 }
675 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
676 changed |= mGaps.get(i).advanceAnimation();
677 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800678 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800679 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800680 }
681
Yuli Huangf320b842012-05-16 01:38:06 +0800682 public boolean inOpeningAnimation() {
683 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
684 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
685 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
686 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
687 }
688
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800689 ////////////////////////////////////////////////////////////////////////////
690 // Layout
691 ////////////////////////////////////////////////////////////////////////////
692
693 // Returns the display width of this box.
694 private int widthOf(Box b) {
695 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
696 }
697
698 // Returns the display height of this box.
699 private int heightOf(Box b) {
700 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
701 }
702
703 // Returns the display width of this box, using the given scale.
704 private int widthOf(Box b, float scale) {
705 return (int) (b.mImageW * scale + 0.5f);
706 }
707
708 // Returns the display height of this box, using the given scale.
709 private int heightOf(Box b, float scale) {
710 return (int) (b.mImageH * scale + 0.5f);
711 }
712
713 // Convert the information in mPlatform and mBoxes to mRects, so the user
714 // can get the position of each box by getPosition().
715 //
716 // Note the loop index goes from inside-out because each box's X coordinate
717 // is relative to its anchor box (except the focused box).
718 private void layoutAndSetPosition() {
719 // layout box 0 (focused box)
720 convertBoxToRect(0);
721 for (int i = 1; i <= BOX_MAX; i++) {
722 // layout box i and -i
723 convertBoxToRect(i);
724 convertBoxToRect(-i);
725 }
726 //dumpState();
727 }
728
729 private void dumpState() {
730 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
731 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800732 }
733
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800734 dumpRect(0);
735 for (int i = 1; i <= BOX_MAX; i++) {
736 dumpRect(i);
737 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800738 }
739
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800740 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
741 for (int j = i + 1; j <= BOX_MAX; j++) {
742 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
743 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
744 }
745 }
746 }
747 }
748
749 private void dumpRect(int i) {
750 StringBuilder sb = new StringBuilder();
751 Rect r = mRects.get(i);
752 sb.append("Rect " + i + ":");
753 sb.append("(");
754 sb.append(r.centerX());
755 sb.append(",");
756 sb.append(r.centerY());
757 sb.append(") [");
758 sb.append(r.width());
759 sb.append("x");
760 sb.append(r.height());
761 sb.append("]");
762 Log.d(TAG, sb.toString());
763 }
764
765 private void convertBoxToRect(int i) {
766 Box b = mBoxes.get(i);
767 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800768 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800769 int w = widthOf(b);
770 int h = heightOf(b);
771 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800772 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800773 r.left = x - w / 2;
774 r.right = r.left + w;
775 } else if (i > 0) {
776 Rect a = mRects.get(i - 1);
777 Gap g = mGaps.get(i - 1);
778 r.left = a.right + g.mCurrentGap;
779 r.right = r.left + w;
780 } else { // i < 0
781 Rect a = mRects.get(i + 1);
782 Gap g = mGaps.get(i);
783 r.right = a.left - g.mCurrentGap;
784 r.left = r.right - w;
785 }
786 r.top = y - h / 2;
787 r.bottom = r.top + h;
788 }
789
790 // Returns the position of a box.
791 public Rect getPosition(int index) {
792 return mRects.get(index);
793 }
794
795 ////////////////////////////////////////////////////////////////////////////
796 // Box management
797 ////////////////////////////////////////////////////////////////////////////
798
799 // Initialize the platform to be at the view center.
800 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800801 mPlatform.updateDefaultXY();
802 mPlatform.mCurrentX = mPlatform.mDefaultX;
803 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800804 mPlatform.mAnimationStartTime = NO_ANIMATION;
805 }
806
807 // Initialize a box to have the size of the view.
808 private void initBox(int index) {
809 Box b = mBoxes.get(index);
810 b.mImageW = mViewW;
811 b.mImageH = mViewH;
812 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800813 b.mScaleMin = getMinimalScale(b);
814 b.mScaleMax = getMaximalScale(b);
815 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800816 b.mCurrentScale = b.mScaleMin;
817 b.mAnimationStartTime = NO_ANIMATION;
818 }
819
820 // Initialize a gap. This can only be called after the boxes around the gap
821 // has been initialized.
822 private void initGap(int index) {
823 Gap g = mGaps.get(index);
824 g.mDefaultSize = getDefaultGapSize(index);
825 g.mCurrentGap = g.mDefaultSize;
826 g.mAnimationStartTime = NO_ANIMATION;
827 }
828
829 private void initGap(int index, int size) {
830 Gap g = mGaps.get(index);
831 g.mDefaultSize = getDefaultGapSize(index);
832 g.mCurrentGap = size;
833 g.mAnimationStartTime = NO_ANIMATION;
834 }
835
836 private void debugMoveBox(int fromIndex[]) {
837 StringBuilder s = new StringBuilder("moveBox:");
838 for (int i = 0; i < fromIndex.length; i++) {
839 int j = fromIndex[i];
840 if (j == Integer.MAX_VALUE) {
841 s.append(" N");
842 } else {
843 s.append(" ");
844 s.append(fromIndex[i]);
845 }
846 }
847 Log.d(TAG, s.toString());
848 }
849
850 // Move the boxes: it may indicate focus change, box deleted, box appearing,
851 // box reordered, etc.
852 //
853 // Each element in the fromIndex array indicates where each box was in the
854 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
855 // means the box is new.
856 //
857 // For example:
858 // N N N N N N N -- all new boxes
859 // -3 -2 -1 0 1 2 3 -- nothing changed
860 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800861 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800862 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800863 //
864 // hasPrev/hasNext indicates if there are previous/next boxes for the
865 // focused box. constrained indicates whether the focused box should be put
866 // into the constrained frame.
867 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
868 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800869 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800870 mHasPrev = hasPrev;
871 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800872
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800873 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
874
875 // 1. Get the absolute X coordiates for the boxes.
876 layoutAndSetPosition();
877 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
878 Box b = mBoxes.get(i);
879 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800880 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800881 }
882
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800883 // 2. copy boxes and gaps to temporary storage.
884 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
885 mTempBoxes.put(i, mBoxes.get(i));
886 mBoxes.put(i, null);
887 }
888 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
889 mTempGaps.put(i, mGaps.get(i));
890 mGaps.put(i, null);
891 }
892
893 // 3. move back boxes that are used in the new array.
894 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
895 int j = from.get(i);
896 if (j == Integer.MAX_VALUE) continue;
897 mBoxes.put(i, mTempBoxes.get(j));
898 mTempBoxes.put(j, null);
899 }
900
901 // 4. move back gaps if both boxes around it are kept together.
902 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
903 int j = from.get(i);
904 if (j == Integer.MAX_VALUE) continue;
905 int k = from.get(i + 1);
906 if (k == Integer.MAX_VALUE) continue;
907 if (j + 1 == k) {
908 mGaps.put(i, mTempGaps.get(j));
909 mTempGaps.put(j, null);
910 }
911 }
912
913 // 5. recycle the boxes that are not used in the new array.
914 int k = -BOX_MAX;
915 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
916 if (mBoxes.get(i) != null) continue;
917 while (mTempBoxes.get(k) == null) {
918 k++;
919 }
920 mBoxes.put(i, mTempBoxes.get(k++));
921 initBox(i);
922 }
923
924 // 6. Now give the recycled box a reasonable absolute X position.
925 //
926 // First try to find the first and the last box which the absolute X
927 // position is known.
928 int first, last;
929 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
930 if (from.get(first) != Integer.MAX_VALUE) break;
931 }
932 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
933 if (from.get(last) != Integer.MAX_VALUE) break;
934 }
935 // If there is no box has known X position at all, make the focused one
936 // as known.
937 if (first > BOX_MAX) {
938 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
939 first = last = 0;
940 }
941 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800942 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800943 // rare). For the boxes before first or after last, we will use a new
944 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800945 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800946 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800947 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800948 }
949
950 // 7. recycle the gaps that are not used in the new array.
951 k = -BOX_MAX;
952 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
953 if (mGaps.get(i) != null) continue;
954 while (mTempGaps.get(k) == null) {
955 k++;
956 }
957 mGaps.put(i, mTempGaps.get(k++));
958 Box a = mBoxes.get(i);
959 Box b = mBoxes.get(i + 1);
960 int wa = widthOf(a);
961 int wb = widthOf(b);
962 if (i >= first && i < last) {
963 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
964 initGap(i, g);
965 } else {
966 initGap(i);
967 }
968 }
969
Chih-Chung Changc4791b72012-05-18 19:10:36 -0700970 // 8. calculate the new absolute X coordinates for those box before
971 // first or after last.
972 for (int i = first - 1; i >= -BOX_MAX; i--) {
973 Box a = mBoxes.get(i + 1);
974 Box b = mBoxes.get(i);
975 int wa = widthOf(a);
976 int wb = widthOf(b);
977 Gap g = mGaps.get(i);
978 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
979 }
980
981 for (int i = last + 1; i <= BOX_MAX; i++) {
982 Box a = mBoxes.get(i - 1);
983 Box b = mBoxes.get(i);
984 int wa = widthOf(a);
985 int wb = widthOf(b);
986 Gap g = mGaps.get(i - 1);
987 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
988 }
989
990 // 9. offset the Platform position
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800991 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
992 mPlatform.mCurrentX += dx;
993 mPlatform.mFromX += dx;
994 mPlatform.mToX += dx;
995 mPlatform.mFlingOffset += dx;
996
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800997 if (mConstrained != constrained) {
998 mConstrained = constrained;
999 mPlatform.updateDefaultXY();
1000 updateScaleAndGapLimit();
1001 }
1002
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001003 snapAndRedraw();
1004 }
1005
1006 ////////////////////////////////////////////////////////////////////////////
1007 // Public utilities
1008 ////////////////////////////////////////////////////////////////////////////
1009
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001010 public boolean isAtMinimalScale() {
1011 Box b = mBoxes.get(0);
1012 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1013 }
1014
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001015 public boolean isCenter() {
1016 Box b = mBoxes.get(0);
1017 return mPlatform.mCurrentX == mPlatform.mDefaultX
1018 && b.mCurrentY == 0;
1019 }
1020
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001021 public int getImageWidth() {
1022 Box b = mBoxes.get(0);
1023 return b.mImageW;
1024 }
1025
1026 public int getImageHeight() {
1027 Box b = mBoxes.get(0);
1028 return b.mImageH;
1029 }
1030
1031 public float getImageScale() {
1032 Box b = mBoxes.get(0);
1033 return b.mCurrentScale;
1034 }
1035
1036 public int getImageAtEdges() {
1037 Box b = mBoxes.get(0);
1038 Platform p = mPlatform;
1039 calculateStableBound(b.mCurrentScale);
1040 int edges = 0;
1041 if (p.mCurrentX <= mBoundLeft) {
1042 edges |= IMAGE_AT_RIGHT_EDGE;
1043 }
1044 if (p.mCurrentX >= mBoundRight) {
1045 edges |= IMAGE_AT_LEFT_EDGE;
1046 }
1047 if (b.mCurrentY <= mBoundTop) {
1048 edges |= IMAGE_AT_BOTTOM_EDGE;
1049 }
1050 if (b.mCurrentY >= mBoundBottom) {
1051 edges |= IMAGE_AT_TOP_EDGE;
1052 }
1053 return edges;
1054 }
1055
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001056 public boolean isScrolling() {
1057 return mPlatform.mAnimationStartTime != NO_ANIMATION
1058 && mPlatform.mCurrentX != mPlatform.mToX;
1059 }
1060
1061 public void stopScrolling() {
1062 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
Angus Kong95c2aea2012-05-24 16:20:57 -07001063 if (mFilmMode) mFilmScroller.forceFinished(true);
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001064 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1065 }
1066
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001067 public float getFilmRatio() {
1068 return mFilmRatio.mCurrentRatio;
1069 }
1070
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001071 ////////////////////////////////////////////////////////////////////////////
1072 // Private utilities
1073 ////////////////////////////////////////////////////////////////////////////
1074
1075 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001076 float wFactor = 1.0f;
1077 float hFactor = 1.0f;
1078 int viewW, viewH;
1079
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001080 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1081 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001082 viewW = mConstrainedFrame.width();
1083 viewH = mConstrainedFrame.height();
1084 } else {
1085 viewW = mViewW;
1086 viewH = mViewH;
1087 }
1088
1089 if (mFilmMode) {
1090 if (mViewH > mViewW) { // portrait
1091 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1092 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1093 } else { // landscape
1094 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1095 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1096 }
1097 }
1098
1099 float s = Math.min(wFactor * viewW / b.mImageW,
1100 hFactor * viewH / b.mImageH);
1101 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001102 }
1103
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001104 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001105 if (mFilmMode) return getMinimalScale(b);
1106 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1107 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001108 }
1109
1110 private static boolean isAlmostEqual(float a, float b) {
1111 float diff = a - b;
1112 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001113 }
1114
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001115 // Calculates the stable region of mPlatform.mCurrentX and
1116 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001117 //
1118 // (1) If the dimension of scaled image >= view dimension, we will not
1119 // see black region outside the image (at that dimension).
1120 // (2) If the dimension of scaled image < view dimension, we will center
1121 // the scaled image.
1122 //
1123 // We might temporarily go out of this stable during user interaction,
1124 // but will "snap back" after user stops interaction.
1125 //
1126 // The results are stored in mBound{Left/Right/Top/Bottom}.
1127 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001128 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1129 // is used to extend the stable region by some pixels on each side
1130 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001131 private void calculateStableBound(float scale, int horizontalSlack) {
1132 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001133
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001134 // The width and height of the box in number of view pixels
1135 int w = widthOf(b, scale);
1136 int h = heightOf(b, scale);
1137
1138 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001139 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1140 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1141 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1142 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001143
1144 // If the scaled height is smaller than the view height,
1145 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001146 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001147 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001148 }
1149
1150 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001151 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001152 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001153 }
1154 }
1155
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001156 private void calculateStableBound(float scale) {
1157 calculateStableBound(scale, 0);
1158 }
1159
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001160 private boolean viewTallerThanScaledImage(float scale) {
1161 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001162 }
1163
1164 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001165 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001166 }
1167
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001168 private float getTargetScale(Box b) {
1169 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001170 }
1171
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001172 private int getTargetX(Platform p) {
1173 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001174 }
1175
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001176 private int getTargetY(Box b) {
1177 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001178 }
1179
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001180 private boolean useCurrentValueAsTarget(Animatable a) {
1181 return a.mAnimationStartTime == NO_ANIMATION ||
1182 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1183 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001184 }
1185
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001186 ////////////////////////////////////////////////////////////////////////////
1187 // Animatable: an thing which can do animation.
1188 ////////////////////////////////////////////////////////////////////////////
1189 private abstract static class Animatable {
1190 public long mAnimationStartTime;
1191 public int mAnimationKind;
1192 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001193
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001194 // This should be overidden in subclass to change the animation values
1195 // give the progress value in [0, 1].
1196 protected abstract boolean interpolate(float progress);
1197 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001198
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001199 // Returns true if the animation values changes, so things need to be
1200 // redrawn.
1201 public boolean advanceAnimation() {
1202 if (mAnimationStartTime == NO_ANIMATION) {
1203 return false;
1204 }
1205 if (mAnimationStartTime == LAST_ANIMATION) {
1206 mAnimationStartTime = NO_ANIMATION;
1207 return startSnapback();
1208 }
1209
1210 float progress;
1211 if (mAnimationDuration == 0) {
1212 progress = 1;
1213 } else {
1214 long now = AnimationTime.get();
1215 progress =
1216 (float) (now - mAnimationStartTime) / mAnimationDuration;
1217 }
1218
1219 if (progress >= 1) {
1220 progress = 1;
1221 } else {
1222 progress = applyInterpolationCurve(mAnimationKind, progress);
1223 }
1224
1225 boolean done = interpolate(progress);
1226
1227 if (done) {
1228 mAnimationStartTime = LAST_ANIMATION;
1229 }
1230
1231 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001232 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001233
1234 private static float applyInterpolationCurve(int kind, float progress) {
1235 float f = 1 - progress;
1236 switch (kind) {
1237 case ANIM_KIND_SCROLL:
1238 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001239 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001240 progress = 1 - f; // linear
1241 break;
1242 case ANIM_KIND_SCALE:
1243 progress = 1 - f * f; // quadratic
1244 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001245 case ANIM_KIND_OPENING:
1246 progress = 1 - f * f * f; // x^3
1247 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001248 case ANIM_KIND_SNAPBACK:
1249 case ANIM_KIND_ZOOM:
1250 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001251 progress = 1 - f * f * f * f * f; // x^5
1252 break;
1253 }
1254 return progress;
1255 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001256 }
1257
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001258 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001259 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001260 ////////////////////////////////////////////////////////////////////////////
1261 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001262 public int mCurrentX, mFromX, mToX, mDefaultX;
1263 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001264 public int mFlingOffset;
1265
1266 @Override
1267 public boolean startSnapback() {
1268 if (mAnimationStartTime != NO_ANIMATION) return false;
1269 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001270 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001271
1272 Box b = mBoxes.get(0);
1273 float scaleMin = mExtraScalingRange ?
1274 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1275 float scaleMax = mExtraScalingRange ?
1276 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1277 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1278 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001279 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001280 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001281 int defaultX = mDefaultX;
1282 if (!mHasNext) x = Math.max(x, defaultX);
1283 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001284 } else {
1285 calculateStableBound(scale, HORIZONTAL_SLACK);
1286 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1287 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001288 if (mCurrentX != x || mCurrentY != y) {
1289 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001290 }
1291 return false;
1292 }
1293
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001294 // The updateDefaultXY() should be called whenever these variables
1295 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1296 // mFilmMode
1297 public void updateDefaultXY() {
1298 // We don't check mFilmMode and return 0 for mDefaultX. Because
1299 // otherwise if we decide to leave film mode because we are
1300 // centered, we will immediately back into film mode because we find
1301 // we are not centered.
1302 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1303 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1304 mDefaultY = mFilmMode ? 0 :
1305 mConstrainedFrame.centerY() - mViewH / 2;
1306 } else {
1307 mDefaultX = 0;
1308 mDefaultY = 0;
1309 }
1310 }
1311
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001312 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001313 private boolean doAnimation(int targetX, int targetY, int kind) {
1314 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001315 mAnimationKind = kind;
1316 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001317 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001318 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001319 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001320 mAnimationStartTime = AnimationTime.startTime();
1321 mAnimationDuration = ANIM_TIME[kind];
1322 mFlingOffset = 0;
1323 advanceAnimation();
1324 return true;
1325 }
1326
1327 @Override
1328 protected boolean interpolate(float progress) {
1329 if (mAnimationKind == ANIM_KIND_FLING) {
1330 return mFilmMode
1331 ? interpolateFlingFilm(progress)
1332 : interpolateFlingPage(progress);
1333 } else {
1334 return interpolateLinear(progress);
1335 }
1336 }
1337
1338 private boolean interpolateFlingFilm(float progress) {
1339 mFilmScroller.computeScrollOffset();
1340 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1341
1342 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001343 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001344 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001345 dir = EdgeView.RIGHT;
1346 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001347 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001348 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001349 dir = EdgeView.LEFT;
1350 }
1351 }
1352 if (dir != EdgeView.INVALID_DIRECTION) {
1353 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1354 mListener.onAbsorb(v, dir);
1355 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001356 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001357 }
1358 return mFilmScroller.isFinished();
1359 }
1360
1361 private boolean interpolateFlingPage(float progress) {
1362 mPageScroller.computeScrollOffset(progress);
1363 Box b = mBoxes.get(0);
1364 calculateStableBound(b.mCurrentScale);
1365
1366 int oldX = mCurrentX;
1367 mCurrentX = mPageScroller.getCurrX();
1368
1369 // Check if we hit the edges; show edge effects if we do.
1370 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1371 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1372 mListener.onAbsorb(v, EdgeView.RIGHT);
1373 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1374 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1375 mListener.onAbsorb(v, EdgeView.LEFT);
1376 }
1377
1378 return progress >= 1;
1379 }
1380
1381 private boolean interpolateLinear(float progress) {
1382 // Other animations
1383 if (progress >= 1) {
1384 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001385 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001386 return true;
1387 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001388 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1389 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001390 }
1391 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1392 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1393 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001394 return false;
1395 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001396 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001397 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001398 }
1399 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001400 }
1401
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001402 ////////////////////////////////////////////////////////////////////////////
1403 // Box: represents a rectangular area which shows a picture.
1404 ////////////////////////////////////////////////////////////////////////////
1405 private class Box extends Animatable {
1406 // Size of the bitmap
1407 public int mImageW, mImageH;
1408
1409 // This is true if we assume the image size is the same as view size
1410 // until we know the actual size of image. This is also used to
1411 // determine if there is an image ready to show.
1412 public boolean mUseViewSize;
1413
1414 // The minimum and maximum scale we allow for this box.
1415 public float mScaleMin, mScaleMax;
1416
1417 // The X/Y value indicates where the center of the box is on the view
1418 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1419 // actual values used currently. Note that the X values are implicitly
1420 // defined by Platform and Gaps.
1421 public int mCurrentY, mFromY, mToY;
1422 public float mCurrentScale, mFromScale, mToScale;
1423
1424 // The absolute X coordinate of the center of the box. This is only used
1425 // during moveBox().
1426 public int mAbsoluteX;
1427
1428 @Override
1429 public boolean startSnapback() {
1430 if (mAnimationStartTime != NO_ANIMATION) return false;
1431 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001432 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001433 if (mInScale && this == mBoxes.get(0)) return false;
1434
1435 int y;
1436 float scale;
1437
1438 if (this == mBoxes.get(0)) {
1439 float scaleMin = mExtraScalingRange ?
1440 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1441 float scaleMax = mExtraScalingRange ?
1442 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1443 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1444 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001445 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001446 } else {
1447 calculateStableBound(scale, HORIZONTAL_SLACK);
1448 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1449 }
1450 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001451 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001452 scale = mScaleMin;
1453 }
1454
1455 if (mCurrentY != y || mCurrentScale != scale) {
1456 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1457 }
1458 return false;
1459 }
1460
1461 private boolean doAnimation(int targetY, float targetScale, int kind) {
1462 targetScale = Utils.clamp(targetScale,
1463 SCALE_MIN_EXTRA * mScaleMin,
1464 SCALE_MAX_EXTRA * mScaleMax);
1465
1466 // If the scaled height is smaller than the view height, force it to be
1467 // in the center. (We do this for height only, not width, because the
1468 // user may want to scroll to the previous/next image.)
1469 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001470 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001471 }
1472
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001473 if (mCurrentY == targetY && mCurrentScale == targetScale
1474 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001475 return false;
1476 }
1477
1478 // Now starts an animation for the box.
1479 mAnimationKind = kind;
1480 mFromY = mCurrentY;
1481 mFromScale = mCurrentScale;
1482 mToY = targetY;
1483 mToScale = targetScale;
1484 mAnimationStartTime = AnimationTime.startTime();
1485 mAnimationDuration = ANIM_TIME[kind];
1486 advanceAnimation();
1487 return true;
1488 }
1489
1490 @Override
1491 protected boolean interpolate(float progress) {
1492 if (mAnimationKind == ANIM_KIND_FLING) {
1493 // Currently a Box can only be flung in page mode.
1494 return interpolateFlingPage(progress);
1495 } else {
1496 return interpolateLinear(progress);
1497 }
1498 }
1499
1500 private boolean interpolateFlingPage(float progress) {
1501 mPageScroller.computeScrollOffset(progress);
1502 calculateStableBound(mCurrentScale);
1503
1504 int oldY = mCurrentY;
1505 mCurrentY = mPageScroller.getCurrY();
1506
1507 // Check if we hit the edges; show edge effects if we do.
1508 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1509 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1510 mListener.onAbsorb(v, EdgeView.BOTTOM);
1511 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1512 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1513 mListener.onAbsorb(v, EdgeView.TOP);
1514 }
1515
1516 return progress >= 1;
1517 }
1518
1519 private boolean interpolateLinear(float progress) {
1520 if (progress >= 1) {
1521 mCurrentY = mToY;
1522 mCurrentScale = mToScale;
1523 return true;
1524 } else {
1525 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1526 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001527 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1528 float f = CaptureAnimation.calculateScale(progress);
1529 mCurrentScale *= f;
1530 return false;
1531 } else {
1532 return (mCurrentY == mToY && mCurrentScale == mToScale);
1533 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001534 }
1535 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001536 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001537
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001538 ////////////////////////////////////////////////////////////////////////////
1539 // Gap: represents a rectangular area which is between two boxes.
1540 ////////////////////////////////////////////////////////////////////////////
1541 private class Gap extends Animatable {
1542 // The default gap size between two boxes. The value may vary for
1543 // different image size of the boxes and for different modes (page or
1544 // film).
1545 public int mDefaultSize;
1546
1547 // The gap size between the two boxes.
1548 public int mCurrentGap, mFromGap, mToGap;
1549
1550 @Override
1551 public boolean startSnapback() {
1552 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001553 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001554 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001555
1556 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001557 public boolean doAnimation(int targetSize, int kind) {
1558 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1559 return false;
1560 }
1561 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001562 mFromGap = mCurrentGap;
1563 mToGap = targetSize;
1564 mAnimationStartTime = AnimationTime.startTime();
1565 mAnimationDuration = ANIM_TIME[mAnimationKind];
1566 advanceAnimation();
1567 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001568 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001569
1570 @Override
1571 protected boolean interpolate(float progress) {
1572 if (progress >= 1) {
1573 mCurrentGap = mToGap;
1574 return true;
1575 } else {
1576 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001577 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1578 float f = CaptureAnimation.calculateScale(progress);
1579 mCurrentGap = (int) (mCurrentGap * f);
1580 return false;
1581 } else {
1582 return (mCurrentGap == mToGap);
1583 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001584 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001585 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001586 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001587
1588 ////////////////////////////////////////////////////////////////////////////
1589 // FilmRatio: represents the progress of film mode change.
1590 ////////////////////////////////////////////////////////////////////////////
1591 private class FilmRatio extends Animatable {
1592 // The film ratio: 1 means switching to film mode is complete, 0 means
1593 // switching to page mode is complete.
1594 public float mCurrentRatio, mFromRatio, mToRatio;
1595
1596 @Override
1597 public boolean startSnapback() {
1598 float target = mFilmMode ? 1f : 0f;
1599 if (target == mToRatio) return false;
1600 return doAnimation(target, ANIM_KIND_SNAPBACK);
1601 }
1602
1603 // Starts an animation for the film ratio.
1604 private boolean doAnimation(float targetRatio, int kind) {
1605 mAnimationKind = kind;
1606 mFromRatio = mCurrentRatio;
1607 mToRatio = targetRatio;
1608 mAnimationStartTime = AnimationTime.startTime();
1609 mAnimationDuration = ANIM_TIME[mAnimationKind];
1610 advanceAnimation();
1611 return true;
1612 }
1613
1614 @Override
1615 protected boolean interpolate(float progress) {
1616 if (progress >= 1) {
1617 mCurrentRatio = mToRatio;
1618 return true;
1619 } else {
1620 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1621 return (mCurrentRatio == mToRatio);
1622 }
1623 }
1624 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001625}