blob: ae0df34b52eee9a200652ce4bdba55532c53b897 [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()) {
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700216 skipToFinalPosition();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700217 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800218 }
219
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700220 public void setConstrainedFrame(Rect cFrame) {
221 if (mConstrainedFrame.equals(cFrame)) return;
222 mConstrainedFrame.set(cFrame);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800223 mPlatform.updateDefaultXY();
224 updateScaleAndGapLimit();
225 snapAndRedraw();
226 }
227
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700228 public void forceImageSize(int index, int width, int height) {
229 if (width == 0 || height == 0) return;
230 Box b = mBoxes.get(index);
231 b.mImageW = width;
232 b.mImageH = height;
233 return;
234 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800235
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700236 public void setImageSize(int index, int width, int height, Rect cFrame) {
237 if (width == 0 || height == 0) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800238
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700239 boolean needUpdate = false;
240 if (cFrame != null && !mConstrainedFrame.equals(cFrame)) {
241 mConstrainedFrame.set(cFrame);
242 mPlatform.updateDefaultXY();
243 needUpdate = true;
244 }
245 needUpdate |= setBoxSize(index, width, height, false);
246
247 if (!needUpdate) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800248 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800249 snapAndRedraw();
250 }
251
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800252 // Returns false if the box size doesn't change.
253 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800254 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800255 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800256
Chih-Chung Changd8488622012-04-17 12:56:08 +0800257 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800258 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800259
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800260 b.mUseViewSize = isViewSize;
261
262 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800263 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800264 }
265
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800266 // The ratio of the old size and the new size.
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700267 //
268 // If the aspect ratio changes, we don't know if it is because one side
269 // grows or the other side shrinks. Currently we just assume the view
270 // angle of the longer side doesn't change (so the aspect ratio change
271 // is because the view angle of the shorter side changes). This matches
272 // what camera preview does.
273 float ratio = (width > height)
274 ? (float) b.mImageW / width
275 : (float) b.mImageH / height;
Chih-Chung Changec412542011-09-26 17:34:06 +0800276
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800277 b.mImageW = width;
278 b.mImageH = height;
279
Chih-Chung Changd8488622012-04-17 12:56:08 +0800280 // If this is the first time we receive an image size, we change the
281 // scale directly. Otherwise adjust the scales by a ratio, and snapback
282 // will animate the scale into the min/max bounds if necessary.
283 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800284 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800285 b.mAnimationStartTime = NO_ANIMATION;
286 } else {
287 b.mCurrentScale *= ratio;
288 b.mFromScale *= ratio;
289 b.mToScale *= ratio;
290 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800291
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800292 if (i == 0) {
293 mFocusX /= ratio;
294 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800295 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800296
297 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800298 }
299
Chih-Chung Change6251df2012-05-22 11:35:46 -0700300 private boolean startOpeningAnimationIfNeeded() {
301 if (mOpenAnimationRect == null) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800302 Box b = mBoxes.get(0);
Chih-Chung Change6251df2012-05-22 11:35:46 -0700303 if (b.mUseViewSize) return false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800304
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800305 // Start animation from the saved rectangle if we have one.
306 Rect r = mOpenAnimationRect;
307 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800308
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800309 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
310 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800311 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
312 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800313 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
314 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800315
316 // Animate from large gaps for neighbor boxes to avoid them
317 // shown on the screen during opening animation.
318 for (int i = -1; i < 1; i++) {
319 Gap g = mGaps.get(i);
320 g.mCurrentGap = mViewW;
321 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
322 }
Chih-Chung Change6251df2012-05-22 11:35:46 -0700323
324 return true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800325 }
326
327 public void setFilmMode(boolean enabled) {
328 if (enabled == mFilmMode) return;
329 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800330
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800331 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800332 updateScaleAndGapLimit();
333 stopAnimation();
334 snapAndRedraw();
335 }
336
337 public void setExtraScalingRange(boolean enabled) {
338 if (mExtraScalingRange == enabled) return;
339 mExtraScalingRange = enabled;
340 if (!enabled) {
341 snapAndRedraw();
342 }
343 }
344
345 // This should be called whenever the scale range of boxes or the default
346 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800347 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800348 private void updateScaleAndGapLimit() {
349 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
350 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800351 b.mScaleMin = getMinimalScale(b);
352 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800353 }
354
355 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
356 Gap g = mGaps.get(i);
357 g.mDefaultSize = getDefaultGapSize(i);
358 }
359 }
360
361 // Returns the default gap size according the the size of the boxes around
362 // the gap and the current mode.
363 private int getDefaultGapSize(int i) {
364 if (mFilmMode) return IMAGE_GAP;
365 Box a = mBoxes.get(i);
366 Box b = mBoxes.get(i + 1);
367 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
368 }
369
370 // Here is how we layout the boxes in the page mode.
371 //
372 // previous current next
373 // ___________ ________________ __________
374 // | _______ | | __________ | | ______ |
375 // | | | | | | right->| | | | | |
376 // | | |<-------->|<--left | | | | | |
377 // | |_______| | | | |__________| | | |______| |
378 // |___________| | |________________| |__________|
379 // | <--> gapToSide()
380 // |
381 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
382 private int gapToSide(Box b) {
383 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
384 }
385
386 // Stop all animations at where they are now.
387 public void stopAnimation() {
388 mPlatform.mAnimationStartTime = NO_ANIMATION;
389 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
390 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
391 }
392 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
393 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
394 }
395 }
396
397 public void skipAnimation() {
398 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
399 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800400 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800401 mPlatform.mAnimationStartTime = NO_ANIMATION;
402 }
403 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
404 Box b = mBoxes.get(i);
405 if (b.mAnimationStartTime == NO_ANIMATION) continue;
406 b.mCurrentY = b.mToY;
407 b.mCurrentScale = b.mToScale;
408 b.mAnimationStartTime = NO_ANIMATION;
409 }
410 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
411 Gap g = mGaps.get(i);
412 if (g.mAnimationStartTime == NO_ANIMATION) continue;
413 g.mCurrentGap = g.mToGap;
414 g.mAnimationStartTime = NO_ANIMATION;
415 }
416 redraw();
417 }
418
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800419 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800420 snapAndRedraw();
421 }
422
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700423 public void skipToFinalPosition() {
424 stopAnimation();
425 snapAndRedraw();
426 skipAnimation();
427 }
428
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800429 ////////////////////////////////////////////////////////////////////////////
430 // Start an animations for the focused box
431 ////////////////////////////////////////////////////////////////////////////
432
433 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800434 tapX -= mViewW / 2;
435 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800436 Box b = mBoxes.get(0);
437
438 // Convert the tap position to distance to center in bitmap coordinates
439 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
440 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
441
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800442 int x = (int) (-tempX * targetScale + 0.5f);
443 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800444
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800445 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800446 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
447 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
448 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800449
450 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
451 }
452
453 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800454 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800455 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800456 }
457
Chih-Chung Changec412542011-09-26 17:34:06 +0800458 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800459 focusX -= mViewW / 2;
460 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800461 Box b = mBoxes.get(0);
462 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800463 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800464 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
465 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800466 }
467
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800468 // Scales the image by the given factor.
469 // Returns an out-of-range indicator:
470 // 1 if the intended scale is too large for the stable range.
471 // 0 if the intended scale is in the stable range.
472 // -1 if the intended scale is too small for the stable range.
473 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800474 focusX -= mViewW / 2;
475 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800476 Box b = mBoxes.get(0);
477 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800478
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800479 // We want to keep the focus point (on the bitmap) the same as when we
480 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800481 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800482 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800483 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800484 s *= getTargetScale(b);
485 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
486 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800487 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800488 if (s < b.mScaleMin) return -1;
489 if (s > b.mScaleMax) return 1;
490 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800491 }
492
493 public void endScale() {
494 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800495 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800496 }
497
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800498 // Slide the focused box to the center of the view.
499 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800500 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800501 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800502 }
503
504 // Slide the focused box to the center of the view with the capture
505 // animation. In addition to the sliding, the animation will also scale the
506 // the focused box, the specified neighbor box, and the gap between the
507 // two. The specified offset should be 1 or -1.
508 public void startCaptureAnimationSlide(int offset) {
509 Box b = mBoxes.get(0);
510 Box n = mBoxes.get(offset); // the neighbor box
511 Gap g = mGaps.get(offset); // the gap between the two boxes
512
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800513 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
514 ANIM_KIND_CAPTURE);
515 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
516 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800517 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
518 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800519 }
520
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800521 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800522 Box b = mBoxes.get(0);
523 Platform p = mPlatform;
524
525 int x = getTargetX(p) + (int) (dx + 0.5f);
526 int y = getTargetY(b) + (int) (dy + 0.5f);
527
528 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800529 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800530 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800531 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800532 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800533 }
534
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800535 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800536 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800537
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800538 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800539
540 // Vertical direction: If we have space to move in the vertical
541 // direction, we show the edge effect when scrolling reaches the edge.
542 if (mBoundTop != mBoundBottom) {
543 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800544 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800545 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800546 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800547 }
548 }
549
550 y = Utils.clamp(y, mBoundTop, mBoundBottom);
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 Changfb1a1552012-04-19 13:34:48 +0800554 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800555 int pixels = x - mBoundRight;
556 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800557 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800558 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800559 int pixels = mBoundLeft - x;
560 mListener.onPull(pixels, EdgeView.RIGHT);
561 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800562 }
563
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800564 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
565 }
566
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800567 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800568 Box b = mBoxes.get(0);
569
570 // Horizontal direction: we show the edge effect when the scrolling
571 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800572 x -= mPlatform.mDefaultX;
573 if (!mHasPrev && x > 0) {
574 mListener.onPull(x, EdgeView.LEFT);
575 x = 0;
576 } else if (!mHasNext && x < 0) {
577 mListener.onPull(-x, EdgeView.RIGHT);
578 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800579 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800580 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800581 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800582 }
583
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800584 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800585 int vx = (int) (velocityX + 0.5f);
586 int vy = (int) (velocityY + 0.5f);
587 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
588 }
589
590 private boolean flingPage(int velocityX, int velocityY) {
591 Box b = mBoxes.get(0);
592 Platform p = mPlatform;
593
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800594 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800595 if (viewWiderThanScaledImage(b.mCurrentScale) &&
596 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800597 return false;
598 }
599
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800600 // We only allow flinging in the directions where it won't go over the
601 // picture.
602 int edges = getImageAtEdges();
603 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
604 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
605 velocityX = 0;
606 }
607 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
608 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
609 velocityY = 0;
610 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800611
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800612 if (velocityX == 0 && velocityY == 0) return false;
613
614 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800615 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800616 int targetX = mPageScroller.getFinalX();
617 int targetY = mPageScroller.getFinalY();
618 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
619 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800620 return true;
621 }
622
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800623 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800624 Box b = mBoxes.get(0);
625 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800626
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800627 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800628 int defaultX = p.mDefaultX;
629 if ((!mHasPrev && p.mCurrentX >= defaultX)
630 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800631 return false;
632 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800633
634 if (velocityX == 0) return false;
635
636 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
637 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
638 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800639 // This value doesn't matter because we use mFilmScroller.isFinished()
640 // to decide when to stop. We set this to 0 so it's faster for
641 // Animatable.advanceAnimation() to calculate the progress (always 1).
642 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800643 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
644 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800645 }
646
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800647 ////////////////////////////////////////////////////////////////////////////
648 // Redraw
649 //
650 // If a method changes box positions directly, redraw()
651 // should be called.
652 //
653 // If a method may also cause a snapback to happen, snapAndRedraw() should
654 // be called.
655 //
656 // If a method starts an animation to change the position of focused box,
657 // startAnimation() should be called.
658 //
659 // If time advances to change the box position, advanceAnimation() should
660 // be called.
661 ////////////////////////////////////////////////////////////////////////////
662 private void redraw() {
663 layoutAndSetPosition();
664 mListener.invalidate();
665 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800666
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800667 private void snapAndRedraw() {
668 mPlatform.startSnapback();
669 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
670 mBoxes.get(i).startSnapback();
671 }
672 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
673 mGaps.get(i).startSnapback();
674 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800675 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800676 redraw();
677 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800678
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800679 private void startAnimation(int targetX, int targetY, float targetScale,
680 int kind) {
681 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800682 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800683 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
684 if (changed) redraw();
685 }
686
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800687 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800688 boolean changed = false;
689 changed |= mPlatform.advanceAnimation();
690 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
691 changed |= mBoxes.get(i).advanceAnimation();
692 }
693 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
694 changed |= mGaps.get(i).advanceAnimation();
695 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800696 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800697 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800698 }
699
Yuli Huangf320b842012-05-16 01:38:06 +0800700 public boolean inOpeningAnimation() {
701 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
702 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
703 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
704 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
705 }
706
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800707 ////////////////////////////////////////////////////////////////////////////
708 // Layout
709 ////////////////////////////////////////////////////////////////////////////
710
711 // Returns the display width of this box.
712 private int widthOf(Box b) {
713 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
714 }
715
716 // Returns the display height of this box.
717 private int heightOf(Box b) {
718 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
719 }
720
721 // Returns the display width of this box, using the given scale.
722 private int widthOf(Box b, float scale) {
723 return (int) (b.mImageW * scale + 0.5f);
724 }
725
726 // Returns the display height of this box, using the given scale.
727 private int heightOf(Box b, float scale) {
728 return (int) (b.mImageH * scale + 0.5f);
729 }
730
731 // Convert the information in mPlatform and mBoxes to mRects, so the user
732 // can get the position of each box by getPosition().
733 //
734 // Note the loop index goes from inside-out because each box's X coordinate
735 // is relative to its anchor box (except the focused box).
736 private void layoutAndSetPosition() {
737 // layout box 0 (focused box)
738 convertBoxToRect(0);
739 for (int i = 1; i <= BOX_MAX; i++) {
740 // layout box i and -i
741 convertBoxToRect(i);
742 convertBoxToRect(-i);
743 }
744 //dumpState();
745 }
746
747 private void dumpState() {
748 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
749 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800750 }
751
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800752 dumpRect(0);
753 for (int i = 1; i <= BOX_MAX; i++) {
754 dumpRect(i);
755 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800756 }
757
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800758 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
759 for (int j = i + 1; j <= BOX_MAX; j++) {
760 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
761 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
762 }
763 }
764 }
765 }
766
767 private void dumpRect(int i) {
768 StringBuilder sb = new StringBuilder();
769 Rect r = mRects.get(i);
770 sb.append("Rect " + i + ":");
771 sb.append("(");
772 sb.append(r.centerX());
773 sb.append(",");
774 sb.append(r.centerY());
775 sb.append(") [");
776 sb.append(r.width());
777 sb.append("x");
778 sb.append(r.height());
779 sb.append("]");
780 Log.d(TAG, sb.toString());
781 }
782
783 private void convertBoxToRect(int i) {
784 Box b = mBoxes.get(i);
785 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800786 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800787 int w = widthOf(b);
788 int h = heightOf(b);
789 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800790 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800791 r.left = x - w / 2;
792 r.right = r.left + w;
793 } else if (i > 0) {
794 Rect a = mRects.get(i - 1);
795 Gap g = mGaps.get(i - 1);
796 r.left = a.right + g.mCurrentGap;
797 r.right = r.left + w;
798 } else { // i < 0
799 Rect a = mRects.get(i + 1);
800 Gap g = mGaps.get(i);
801 r.right = a.left - g.mCurrentGap;
802 r.left = r.right - w;
803 }
804 r.top = y - h / 2;
805 r.bottom = r.top + h;
806 }
807
808 // Returns the position of a box.
809 public Rect getPosition(int index) {
810 return mRects.get(index);
811 }
812
813 ////////////////////////////////////////////////////////////////////////////
814 // Box management
815 ////////////////////////////////////////////////////////////////////////////
816
817 // Initialize the platform to be at the view center.
818 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800819 mPlatform.updateDefaultXY();
820 mPlatform.mCurrentX = mPlatform.mDefaultX;
821 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800822 mPlatform.mAnimationStartTime = NO_ANIMATION;
823 }
824
825 // Initialize a box to have the size of the view.
826 private void initBox(int index) {
827 Box b = mBoxes.get(index);
828 b.mImageW = mViewW;
829 b.mImageH = mViewH;
830 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800831 b.mScaleMin = getMinimalScale(b);
832 b.mScaleMax = getMaximalScale(b);
833 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800834 b.mCurrentScale = b.mScaleMin;
835 b.mAnimationStartTime = NO_ANIMATION;
836 }
837
838 // Initialize a gap. This can only be called after the boxes around the gap
839 // has been initialized.
840 private void initGap(int index) {
841 Gap g = mGaps.get(index);
842 g.mDefaultSize = getDefaultGapSize(index);
843 g.mCurrentGap = g.mDefaultSize;
844 g.mAnimationStartTime = NO_ANIMATION;
845 }
846
847 private void initGap(int index, int size) {
848 Gap g = mGaps.get(index);
849 g.mDefaultSize = getDefaultGapSize(index);
850 g.mCurrentGap = size;
851 g.mAnimationStartTime = NO_ANIMATION;
852 }
853
854 private void debugMoveBox(int fromIndex[]) {
855 StringBuilder s = new StringBuilder("moveBox:");
856 for (int i = 0; i < fromIndex.length; i++) {
857 int j = fromIndex[i];
858 if (j == Integer.MAX_VALUE) {
859 s.append(" N");
860 } else {
861 s.append(" ");
862 s.append(fromIndex[i]);
863 }
864 }
865 Log.d(TAG, s.toString());
866 }
867
868 // Move the boxes: it may indicate focus change, box deleted, box appearing,
869 // box reordered, etc.
870 //
871 // Each element in the fromIndex array indicates where each box was in the
872 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
873 // means the box is new.
874 //
875 // For example:
876 // N N N N N N N -- all new boxes
877 // -3 -2 -1 0 1 2 3 -- nothing changed
878 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800879 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800880 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800881 //
882 // hasPrev/hasNext indicates if there are previous/next boxes for the
883 // focused box. constrained indicates whether the focused box should be put
884 // into the constrained frame.
885 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
886 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800887 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800888 mHasPrev = hasPrev;
889 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800890
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800891 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
892
893 // 1. Get the absolute X coordiates for the boxes.
894 layoutAndSetPosition();
895 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
896 Box b = mBoxes.get(i);
897 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800898 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800899 }
900
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800901 // 2. copy boxes and gaps to temporary storage.
902 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
903 mTempBoxes.put(i, mBoxes.get(i));
904 mBoxes.put(i, null);
905 }
906 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
907 mTempGaps.put(i, mGaps.get(i));
908 mGaps.put(i, null);
909 }
910
911 // 3. move back boxes that are used in the new array.
912 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
913 int j = from.get(i);
914 if (j == Integer.MAX_VALUE) continue;
915 mBoxes.put(i, mTempBoxes.get(j));
916 mTempBoxes.put(j, null);
917 }
918
919 // 4. move back gaps if both boxes around it are kept together.
920 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
921 int j = from.get(i);
922 if (j == Integer.MAX_VALUE) continue;
923 int k = from.get(i + 1);
924 if (k == Integer.MAX_VALUE) continue;
925 if (j + 1 == k) {
926 mGaps.put(i, mTempGaps.get(j));
927 mTempGaps.put(j, null);
928 }
929 }
930
931 // 5. recycle the boxes that are not used in the new array.
932 int k = -BOX_MAX;
933 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
934 if (mBoxes.get(i) != null) continue;
935 while (mTempBoxes.get(k) == null) {
936 k++;
937 }
938 mBoxes.put(i, mTempBoxes.get(k++));
939 initBox(i);
940 }
941
942 // 6. Now give the recycled box a reasonable absolute X position.
943 //
944 // First try to find the first and the last box which the absolute X
945 // position is known.
946 int first, last;
947 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
948 if (from.get(first) != Integer.MAX_VALUE) break;
949 }
950 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
951 if (from.get(last) != Integer.MAX_VALUE) break;
952 }
953 // If there is no box has known X position at all, make the focused one
954 // as known.
955 if (first > BOX_MAX) {
956 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
957 first = last = 0;
958 }
959 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800960 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800961 // rare). For the boxes before first or after last, we will use a new
962 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800963 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800964 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800965 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800966 }
967
968 // 7. recycle the gaps that are not used in the new array.
969 k = -BOX_MAX;
970 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
971 if (mGaps.get(i) != null) continue;
972 while (mTempGaps.get(k) == null) {
973 k++;
974 }
975 mGaps.put(i, mTempGaps.get(k++));
976 Box a = mBoxes.get(i);
977 Box b = mBoxes.get(i + 1);
978 int wa = widthOf(a);
979 int wb = widthOf(b);
980 if (i >= first && i < last) {
981 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
982 initGap(i, g);
983 } else {
984 initGap(i);
985 }
986 }
987
Chih-Chung Changc4791b72012-05-18 19:10:36 -0700988 // 8. calculate the new absolute X coordinates for those box before
989 // first or after last.
990 for (int i = first - 1; i >= -BOX_MAX; i--) {
991 Box a = mBoxes.get(i + 1);
992 Box b = mBoxes.get(i);
993 int wa = widthOf(a);
994 int wb = widthOf(b);
995 Gap g = mGaps.get(i);
996 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
997 }
998
999 for (int i = last + 1; i <= BOX_MAX; i++) {
1000 Box a = mBoxes.get(i - 1);
1001 Box b = mBoxes.get(i);
1002 int wa = widthOf(a);
1003 int wb = widthOf(b);
1004 Gap g = mGaps.get(i - 1);
1005 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
1006 }
1007
1008 // 9. offset the Platform position
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001009 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
1010 mPlatform.mCurrentX += dx;
1011 mPlatform.mFromX += dx;
1012 mPlatform.mToX += dx;
1013 mPlatform.mFlingOffset += dx;
1014
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001015 if (mConstrained != constrained) {
1016 mConstrained = constrained;
1017 mPlatform.updateDefaultXY();
1018 updateScaleAndGapLimit();
1019 }
1020
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001021 snapAndRedraw();
1022 }
1023
1024 ////////////////////////////////////////////////////////////////////////////
1025 // Public utilities
1026 ////////////////////////////////////////////////////////////////////////////
1027
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001028 public boolean isAtMinimalScale() {
1029 Box b = mBoxes.get(0);
1030 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1031 }
1032
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001033 public boolean isCenter() {
1034 Box b = mBoxes.get(0);
1035 return mPlatform.mCurrentX == mPlatform.mDefaultX
1036 && b.mCurrentY == 0;
1037 }
1038
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001039 public int getImageWidth() {
1040 Box b = mBoxes.get(0);
1041 return b.mImageW;
1042 }
1043
1044 public int getImageHeight() {
1045 Box b = mBoxes.get(0);
1046 return b.mImageH;
1047 }
1048
1049 public float getImageScale() {
1050 Box b = mBoxes.get(0);
1051 return b.mCurrentScale;
1052 }
1053
1054 public int getImageAtEdges() {
1055 Box b = mBoxes.get(0);
1056 Platform p = mPlatform;
1057 calculateStableBound(b.mCurrentScale);
1058 int edges = 0;
1059 if (p.mCurrentX <= mBoundLeft) {
1060 edges |= IMAGE_AT_RIGHT_EDGE;
1061 }
1062 if (p.mCurrentX >= mBoundRight) {
1063 edges |= IMAGE_AT_LEFT_EDGE;
1064 }
1065 if (b.mCurrentY <= mBoundTop) {
1066 edges |= IMAGE_AT_BOTTOM_EDGE;
1067 }
1068 if (b.mCurrentY >= mBoundBottom) {
1069 edges |= IMAGE_AT_TOP_EDGE;
1070 }
1071 return edges;
1072 }
1073
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001074 public boolean isScrolling() {
1075 return mPlatform.mAnimationStartTime != NO_ANIMATION
1076 && mPlatform.mCurrentX != mPlatform.mToX;
1077 }
1078
1079 public void stopScrolling() {
1080 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
Angus Kong95c2aea2012-05-24 16:20:57 -07001081 if (mFilmMode) mFilmScroller.forceFinished(true);
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001082 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1083 }
1084
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001085 public float getFilmRatio() {
1086 return mFilmRatio.mCurrentRatio;
1087 }
1088
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001089 ////////////////////////////////////////////////////////////////////////////
1090 // Private utilities
1091 ////////////////////////////////////////////////////////////////////////////
1092
1093 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001094 float wFactor = 1.0f;
1095 float hFactor = 1.0f;
1096 int viewW, viewH;
1097
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001098 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1099 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001100 viewW = mConstrainedFrame.width();
1101 viewH = mConstrainedFrame.height();
1102 } else {
1103 viewW = mViewW;
1104 viewH = mViewH;
1105 }
1106
1107 if (mFilmMode) {
1108 if (mViewH > mViewW) { // portrait
1109 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1110 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1111 } else { // landscape
1112 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1113 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1114 }
1115 }
1116
1117 float s = Math.min(wFactor * viewW / b.mImageW,
1118 hFactor * viewH / b.mImageH);
1119 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001120 }
1121
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001122 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001123 if (mFilmMode) return getMinimalScale(b);
1124 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1125 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001126 }
1127
1128 private static boolean isAlmostEqual(float a, float b) {
1129 float diff = a - b;
1130 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001131 }
1132
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001133 // Calculates the stable region of mPlatform.mCurrentX and
1134 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001135 //
1136 // (1) If the dimension of scaled image >= view dimension, we will not
1137 // see black region outside the image (at that dimension).
1138 // (2) If the dimension of scaled image < view dimension, we will center
1139 // the scaled image.
1140 //
1141 // We might temporarily go out of this stable during user interaction,
1142 // but will "snap back" after user stops interaction.
1143 //
1144 // The results are stored in mBound{Left/Right/Top/Bottom}.
1145 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001146 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1147 // is used to extend the stable region by some pixels on each side
1148 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001149 private void calculateStableBound(float scale, int horizontalSlack) {
1150 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001151
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001152 // The width and height of the box in number of view pixels
1153 int w = widthOf(b, scale);
1154 int h = heightOf(b, scale);
1155
1156 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001157 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1158 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1159 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1160 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001161
1162 // If the scaled height is smaller than the view height,
1163 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001164 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001165 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001166 }
1167
1168 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001169 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001170 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001171 }
1172 }
1173
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001174 private void calculateStableBound(float scale) {
1175 calculateStableBound(scale, 0);
1176 }
1177
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001178 private boolean viewTallerThanScaledImage(float scale) {
1179 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001180 }
1181
1182 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001183 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001184 }
1185
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001186 private float getTargetScale(Box b) {
1187 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001188 }
1189
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001190 private int getTargetX(Platform p) {
1191 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001192 }
1193
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001194 private int getTargetY(Box b) {
1195 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001196 }
1197
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001198 private boolean useCurrentValueAsTarget(Animatable a) {
1199 return a.mAnimationStartTime == NO_ANIMATION ||
1200 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1201 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001202 }
1203
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001204 ////////////////////////////////////////////////////////////////////////////
1205 // Animatable: an thing which can do animation.
1206 ////////////////////////////////////////////////////////////////////////////
1207 private abstract static class Animatable {
1208 public long mAnimationStartTime;
1209 public int mAnimationKind;
1210 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001211
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001212 // This should be overidden in subclass to change the animation values
1213 // give the progress value in [0, 1].
1214 protected abstract boolean interpolate(float progress);
1215 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001216
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001217 // Returns true if the animation values changes, so things need to be
1218 // redrawn.
1219 public boolean advanceAnimation() {
1220 if (mAnimationStartTime == NO_ANIMATION) {
1221 return false;
1222 }
1223 if (mAnimationStartTime == LAST_ANIMATION) {
1224 mAnimationStartTime = NO_ANIMATION;
1225 return startSnapback();
1226 }
1227
1228 float progress;
1229 if (mAnimationDuration == 0) {
1230 progress = 1;
1231 } else {
1232 long now = AnimationTime.get();
1233 progress =
1234 (float) (now - mAnimationStartTime) / mAnimationDuration;
1235 }
1236
1237 if (progress >= 1) {
1238 progress = 1;
1239 } else {
1240 progress = applyInterpolationCurve(mAnimationKind, progress);
1241 }
1242
1243 boolean done = interpolate(progress);
1244
1245 if (done) {
1246 mAnimationStartTime = LAST_ANIMATION;
1247 }
1248
1249 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001250 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001251
1252 private static float applyInterpolationCurve(int kind, float progress) {
1253 float f = 1 - progress;
1254 switch (kind) {
1255 case ANIM_KIND_SCROLL:
1256 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001257 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001258 progress = 1 - f; // linear
1259 break;
1260 case ANIM_KIND_SCALE:
1261 progress = 1 - f * f; // quadratic
1262 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001263 case ANIM_KIND_OPENING:
1264 progress = 1 - f * f * f; // x^3
1265 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001266 case ANIM_KIND_SNAPBACK:
1267 case ANIM_KIND_ZOOM:
1268 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001269 progress = 1 - f * f * f * f * f; // x^5
1270 break;
1271 }
1272 return progress;
1273 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001274 }
1275
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001276 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001277 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001278 ////////////////////////////////////////////////////////////////////////////
1279 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001280 public int mCurrentX, mFromX, mToX, mDefaultX;
1281 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001282 public int mFlingOffset;
1283
1284 @Override
1285 public boolean startSnapback() {
1286 if (mAnimationStartTime != NO_ANIMATION) return false;
1287 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001288 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001289
1290 Box b = mBoxes.get(0);
1291 float scaleMin = mExtraScalingRange ?
1292 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1293 float scaleMax = mExtraScalingRange ?
1294 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1295 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1296 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001297 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001298 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001299 int defaultX = mDefaultX;
1300 if (!mHasNext) x = Math.max(x, defaultX);
1301 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001302 } else {
1303 calculateStableBound(scale, HORIZONTAL_SLACK);
1304 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1305 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001306 if (mCurrentX != x || mCurrentY != y) {
1307 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001308 }
1309 return false;
1310 }
1311
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001312 // The updateDefaultXY() should be called whenever these variables
1313 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1314 // mFilmMode
1315 public void updateDefaultXY() {
1316 // We don't check mFilmMode and return 0 for mDefaultX. Because
1317 // otherwise if we decide to leave film mode because we are
1318 // centered, we will immediately back into film mode because we find
1319 // we are not centered.
1320 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1321 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1322 mDefaultY = mFilmMode ? 0 :
1323 mConstrainedFrame.centerY() - mViewH / 2;
1324 } else {
1325 mDefaultX = 0;
1326 mDefaultY = 0;
1327 }
1328 }
1329
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001330 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001331 private boolean doAnimation(int targetX, int targetY, int kind) {
1332 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001333 mAnimationKind = kind;
1334 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001335 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001336 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001337 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001338 mAnimationStartTime = AnimationTime.startTime();
1339 mAnimationDuration = ANIM_TIME[kind];
1340 mFlingOffset = 0;
1341 advanceAnimation();
1342 return true;
1343 }
1344
1345 @Override
1346 protected boolean interpolate(float progress) {
1347 if (mAnimationKind == ANIM_KIND_FLING) {
1348 return mFilmMode
1349 ? interpolateFlingFilm(progress)
1350 : interpolateFlingPage(progress);
1351 } else {
1352 return interpolateLinear(progress);
1353 }
1354 }
1355
1356 private boolean interpolateFlingFilm(float progress) {
1357 mFilmScroller.computeScrollOffset();
1358 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1359
1360 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001361 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001362 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001363 dir = EdgeView.RIGHT;
1364 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001365 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001366 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001367 dir = EdgeView.LEFT;
1368 }
1369 }
1370 if (dir != EdgeView.INVALID_DIRECTION) {
1371 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1372 mListener.onAbsorb(v, dir);
1373 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001374 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001375 }
1376 return mFilmScroller.isFinished();
1377 }
1378
1379 private boolean interpolateFlingPage(float progress) {
1380 mPageScroller.computeScrollOffset(progress);
1381 Box b = mBoxes.get(0);
1382 calculateStableBound(b.mCurrentScale);
1383
1384 int oldX = mCurrentX;
1385 mCurrentX = mPageScroller.getCurrX();
1386
1387 // Check if we hit the edges; show edge effects if we do.
1388 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1389 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1390 mListener.onAbsorb(v, EdgeView.RIGHT);
1391 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1392 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1393 mListener.onAbsorb(v, EdgeView.LEFT);
1394 }
1395
1396 return progress >= 1;
1397 }
1398
1399 private boolean interpolateLinear(float progress) {
1400 // Other animations
1401 if (progress >= 1) {
1402 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001403 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001404 return true;
1405 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001406 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1407 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001408 }
1409 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1410 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1411 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001412 return false;
1413 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001414 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001415 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001416 }
1417 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001418 }
1419
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001420 ////////////////////////////////////////////////////////////////////////////
1421 // Box: represents a rectangular area which shows a picture.
1422 ////////////////////////////////////////////////////////////////////////////
1423 private class Box extends Animatable {
1424 // Size of the bitmap
1425 public int mImageW, mImageH;
1426
1427 // This is true if we assume the image size is the same as view size
1428 // until we know the actual size of image. This is also used to
1429 // determine if there is an image ready to show.
1430 public boolean mUseViewSize;
1431
1432 // The minimum and maximum scale we allow for this box.
1433 public float mScaleMin, mScaleMax;
1434
1435 // The X/Y value indicates where the center of the box is on the view
1436 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1437 // actual values used currently. Note that the X values are implicitly
1438 // defined by Platform and Gaps.
1439 public int mCurrentY, mFromY, mToY;
1440 public float mCurrentScale, mFromScale, mToScale;
1441
1442 // The absolute X coordinate of the center of the box. This is only used
1443 // during moveBox().
1444 public int mAbsoluteX;
1445
1446 @Override
1447 public boolean startSnapback() {
1448 if (mAnimationStartTime != NO_ANIMATION) return false;
1449 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001450 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001451 if (mInScale && this == mBoxes.get(0)) return false;
1452
1453 int y;
1454 float scale;
1455
1456 if (this == mBoxes.get(0)) {
1457 float scaleMin = mExtraScalingRange ?
1458 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1459 float scaleMax = mExtraScalingRange ?
1460 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1461 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1462 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001463 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001464 } else {
1465 calculateStableBound(scale, HORIZONTAL_SLACK);
1466 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1467 }
1468 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001469 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001470 scale = mScaleMin;
1471 }
1472
1473 if (mCurrentY != y || mCurrentScale != scale) {
1474 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1475 }
1476 return false;
1477 }
1478
1479 private boolean doAnimation(int targetY, float targetScale, int kind) {
1480 targetScale = Utils.clamp(targetScale,
1481 SCALE_MIN_EXTRA * mScaleMin,
1482 SCALE_MAX_EXTRA * mScaleMax);
1483
1484 // If the scaled height is smaller than the view height, force it to be
1485 // in the center. (We do this for height only, not width, because the
1486 // user may want to scroll to the previous/next image.)
1487 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001488 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001489 }
1490
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001491 if (mCurrentY == targetY && mCurrentScale == targetScale
1492 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001493 return false;
1494 }
1495
1496 // Now starts an animation for the box.
1497 mAnimationKind = kind;
1498 mFromY = mCurrentY;
1499 mFromScale = mCurrentScale;
1500 mToY = targetY;
1501 mToScale = targetScale;
1502 mAnimationStartTime = AnimationTime.startTime();
1503 mAnimationDuration = ANIM_TIME[kind];
1504 advanceAnimation();
1505 return true;
1506 }
1507
1508 @Override
1509 protected boolean interpolate(float progress) {
1510 if (mAnimationKind == ANIM_KIND_FLING) {
1511 // Currently a Box can only be flung in page mode.
1512 return interpolateFlingPage(progress);
1513 } else {
1514 return interpolateLinear(progress);
1515 }
1516 }
1517
1518 private boolean interpolateFlingPage(float progress) {
1519 mPageScroller.computeScrollOffset(progress);
1520 calculateStableBound(mCurrentScale);
1521
1522 int oldY = mCurrentY;
1523 mCurrentY = mPageScroller.getCurrY();
1524
1525 // Check if we hit the edges; show edge effects if we do.
1526 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1527 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1528 mListener.onAbsorb(v, EdgeView.BOTTOM);
1529 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1530 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1531 mListener.onAbsorb(v, EdgeView.TOP);
1532 }
1533
1534 return progress >= 1;
1535 }
1536
1537 private boolean interpolateLinear(float progress) {
1538 if (progress >= 1) {
1539 mCurrentY = mToY;
1540 mCurrentScale = mToScale;
1541 return true;
1542 } else {
1543 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1544 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001545 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1546 float f = CaptureAnimation.calculateScale(progress);
1547 mCurrentScale *= f;
1548 return false;
1549 } else {
1550 return (mCurrentY == mToY && mCurrentScale == mToScale);
1551 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001552 }
1553 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001554 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001555
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001556 ////////////////////////////////////////////////////////////////////////////
1557 // Gap: represents a rectangular area which is between two boxes.
1558 ////////////////////////////////////////////////////////////////////////////
1559 private class Gap extends Animatable {
1560 // The default gap size between two boxes. The value may vary for
1561 // different image size of the boxes and for different modes (page or
1562 // film).
1563 public int mDefaultSize;
1564
1565 // The gap size between the two boxes.
1566 public int mCurrentGap, mFromGap, mToGap;
1567
1568 @Override
1569 public boolean startSnapback() {
1570 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001571 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001572 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001573
1574 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001575 public boolean doAnimation(int targetSize, int kind) {
1576 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1577 return false;
1578 }
1579 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001580 mFromGap = mCurrentGap;
1581 mToGap = targetSize;
1582 mAnimationStartTime = AnimationTime.startTime();
1583 mAnimationDuration = ANIM_TIME[mAnimationKind];
1584 advanceAnimation();
1585 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001586 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001587
1588 @Override
1589 protected boolean interpolate(float progress) {
1590 if (progress >= 1) {
1591 mCurrentGap = mToGap;
1592 return true;
1593 } else {
1594 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001595 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1596 float f = CaptureAnimation.calculateScale(progress);
1597 mCurrentGap = (int) (mCurrentGap * f);
1598 return false;
1599 } else {
1600 return (mCurrentGap == mToGap);
1601 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001602 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001603 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001604 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001605
1606 ////////////////////////////////////////////////////////////////////////////
1607 // FilmRatio: represents the progress of film mode change.
1608 ////////////////////////////////////////////////////////////////////////////
1609 private class FilmRatio extends Animatable {
1610 // The film ratio: 1 means switching to film mode is complete, 0 means
1611 // switching to page mode is complete.
1612 public float mCurrentRatio, mFromRatio, mToRatio;
1613
1614 @Override
1615 public boolean startSnapback() {
1616 float target = mFilmMode ? 1f : 0f;
1617 if (target == mToRatio) return false;
1618 return doAnimation(target, ANIM_KIND_SNAPBACK);
1619 }
1620
1621 // Starts an animation for the film ratio.
1622 private boolean doAnimation(float targetRatio, int kind) {
1623 mAnimationKind = kind;
1624 mFromRatio = mCurrentRatio;
1625 mToRatio = targetRatio;
1626 mAnimationStartTime = AnimationTime.startTime();
1627 mAnimationDuration = ANIM_TIME[mAnimationKind];
1628 advanceAnimation();
1629 return true;
1630 }
1631
1632 @Override
1633 protected boolean interpolate(float progress) {
1634 if (progress >= 1) {
1635 mCurrentRatio = mToRatio;
1636 return true;
1637 } else {
1638 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1639 return (mCurrentRatio == mToRatio);
1640 }
1641 }
1642 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001643}