blob: bb2b83ecb2cc1dd007531d682bcdf1cc6d7ccbc1 [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();
Yuli Huangbd7c0162012-05-15 22:36:59 +0800212 startOpeningAnimationIfNeeded();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800213 snapAndRedraw();
214 }
215
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800216 public void setConstrainedFrame(Rect f) {
217 if (mConstrainedFrame.equals(f)) return;
218 mConstrainedFrame.set(f);
219 mPlatform.updateDefaultXY();
220 updateScaleAndGapLimit();
221 snapAndRedraw();
222 }
223
224 public void setImageSize(int index, int width, int height, boolean force) {
225 if (force) {
226 Box b = mBoxes.get(index);
227 b.mImageW = width;
228 b.mImageH = height;
229 return;
230 }
231
Chih-Chung Changec412542011-09-26 17:34:06 +0800232 if (width == 0 || height == 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800233 initBox(index);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800234 } else if (!setBoxSize(index, width, height, false)) {
235 return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800236 }
237
238 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800239 snapAndRedraw();
240 }
241
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800242 // Returns false if the box size doesn't change.
243 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800244 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800245 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800246
Chih-Chung Changd8488622012-04-17 12:56:08 +0800247 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800248 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800249
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800250 b.mUseViewSize = isViewSize;
251
252 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800253 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800254 }
255
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800256 // The ratio of the old size and the new size.
Chih-Chung Changec412542011-09-26 17:34:06 +0800257 float ratio = Math.min(
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800258 (float) b.mImageW / width, (float) b.mImageH / height);
Chih-Chung Changec412542011-09-26 17:34:06 +0800259
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800260 b.mImageW = width;
261 b.mImageH = height;
262
Chih-Chung Changd8488622012-04-17 12:56:08 +0800263 // If this is the first time we receive an image size, we change the
264 // scale directly. Otherwise adjust the scales by a ratio, and snapback
265 // will animate the scale into the min/max bounds if necessary.
266 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800267 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800268 b.mAnimationStartTime = NO_ANIMATION;
269 } else {
270 b.mCurrentScale *= ratio;
271 b.mFromScale *= ratio;
272 b.mToScale *= ratio;
273 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800274
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800275 if (i == 0) {
276 mFocusX /= ratio;
277 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800278 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800279
280 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800281 }
282
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800283 private void startOpeningAnimationIfNeeded() {
284 if (mOpenAnimationRect == null) return;
285 Box b = mBoxes.get(0);
286 if (b.mUseViewSize) return;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800287
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800288 // Start animation from the saved rectangle if we have one.
289 Rect r = mOpenAnimationRect;
290 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800291
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800292 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
293 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800294 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
295 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800296 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
297 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800298
299 // Animate from large gaps for neighbor boxes to avoid them
300 // shown on the screen during opening animation.
301 for (int i = -1; i < 1; i++) {
302 Gap g = mGaps.get(i);
303 g.mCurrentGap = mViewW;
304 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
305 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800306 }
307
308 public void setFilmMode(boolean enabled) {
309 if (enabled == mFilmMode) return;
310 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800311
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800312 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800313 updateScaleAndGapLimit();
314 stopAnimation();
315 snapAndRedraw();
316 }
317
318 public void setExtraScalingRange(boolean enabled) {
319 if (mExtraScalingRange == enabled) return;
320 mExtraScalingRange = enabled;
321 if (!enabled) {
322 snapAndRedraw();
323 }
324 }
325
326 // This should be called whenever the scale range of boxes or the default
327 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800328 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800329 private void updateScaleAndGapLimit() {
330 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
331 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800332 b.mScaleMin = getMinimalScale(b);
333 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800334 }
335
336 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
337 Gap g = mGaps.get(i);
338 g.mDefaultSize = getDefaultGapSize(i);
339 }
340 }
341
342 // Returns the default gap size according the the size of the boxes around
343 // the gap and the current mode.
344 private int getDefaultGapSize(int i) {
345 if (mFilmMode) return IMAGE_GAP;
346 Box a = mBoxes.get(i);
347 Box b = mBoxes.get(i + 1);
348 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
349 }
350
351 // Here is how we layout the boxes in the page mode.
352 //
353 // previous current next
354 // ___________ ________________ __________
355 // | _______ | | __________ | | ______ |
356 // | | | | | | right->| | | | | |
357 // | | |<-------->|<--left | | | | | |
358 // | |_______| | | | |__________| | | |______| |
359 // |___________| | |________________| |__________|
360 // | <--> gapToSide()
361 // |
362 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
363 private int gapToSide(Box b) {
364 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
365 }
366
367 // Stop all animations at where they are now.
368 public void stopAnimation() {
369 mPlatform.mAnimationStartTime = NO_ANIMATION;
370 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
371 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
372 }
373 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
374 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
375 }
376 }
377
378 public void skipAnimation() {
379 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
380 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800381 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800382 mPlatform.mAnimationStartTime = NO_ANIMATION;
383 }
384 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
385 Box b = mBoxes.get(i);
386 if (b.mAnimationStartTime == NO_ANIMATION) continue;
387 b.mCurrentY = b.mToY;
388 b.mCurrentScale = b.mToScale;
389 b.mAnimationStartTime = NO_ANIMATION;
390 }
391 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
392 Gap g = mGaps.get(i);
393 if (g.mAnimationStartTime == NO_ANIMATION) continue;
394 g.mCurrentGap = g.mToGap;
395 g.mAnimationStartTime = NO_ANIMATION;
396 }
397 redraw();
398 }
399
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800400 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800401 snapAndRedraw();
402 }
403
404 ////////////////////////////////////////////////////////////////////////////
405 // Start an animations for the focused box
406 ////////////////////////////////////////////////////////////////////////////
407
408 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800409 tapX -= mViewW / 2;
410 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800411 Box b = mBoxes.get(0);
412
413 // Convert the tap position to distance to center in bitmap coordinates
414 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
415 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
416
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800417 int x = (int) (-tempX * targetScale + 0.5f);
418 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800419
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800420 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800421 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
422 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
423 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800424
425 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
426 }
427
428 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800429 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800430 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800431 }
432
Chih-Chung Changec412542011-09-26 17:34:06 +0800433 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800434 focusX -= mViewW / 2;
435 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800436 Box b = mBoxes.get(0);
437 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800438 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800439 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
440 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800441 }
442
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800443 // Scales the image by the given factor.
444 // Returns an out-of-range indicator:
445 // 1 if the intended scale is too large for the stable range.
446 // 0 if the intended scale is in the stable range.
447 // -1 if the intended scale is too small for the stable range.
448 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800449 focusX -= mViewW / 2;
450 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800451 Box b = mBoxes.get(0);
452 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800453
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800454 // We want to keep the focus point (on the bitmap) the same as when we
455 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800456 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800457 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800458 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800459 s *= getTargetScale(b);
460 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
461 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800462 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800463 if (s < b.mScaleMin) return -1;
464 if (s > b.mScaleMax) return 1;
465 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800466 }
467
468 public void endScale() {
469 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800470 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800471 }
472
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800473 // Slide the focused box to the center of the view.
474 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800475 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800476 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800477 }
478
479 // Slide the focused box to the center of the view with the capture
480 // animation. In addition to the sliding, the animation will also scale the
481 // the focused box, the specified neighbor box, and the gap between the
482 // two. The specified offset should be 1 or -1.
483 public void startCaptureAnimationSlide(int offset) {
484 Box b = mBoxes.get(0);
485 Box n = mBoxes.get(offset); // the neighbor box
486 Gap g = mGaps.get(offset); // the gap between the two boxes
487
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800488 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
489 ANIM_KIND_CAPTURE);
490 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
491 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800492 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
493 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800494 }
495
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800496 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800497 Box b = mBoxes.get(0);
498 Platform p = mPlatform;
499
500 int x = getTargetX(p) + (int) (dx + 0.5f);
501 int y = getTargetY(b) + (int) (dy + 0.5f);
502
503 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800504 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800505 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800506 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800507 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800508 }
509
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800510 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800511 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800512
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800513 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800514
515 // Vertical direction: If we have space to move in the vertical
516 // direction, we show the edge effect when scrolling reaches the edge.
517 if (mBoundTop != mBoundBottom) {
518 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800519 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800520 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800521 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800522 }
523 }
524
525 y = Utils.clamp(y, mBoundTop, mBoundBottom);
526
527 // Horizontal direction: we show the edge effect when the scrolling
528 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800529 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800530 int pixels = x - mBoundRight;
531 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800532 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800533 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800534 int pixels = mBoundLeft - x;
535 mListener.onPull(pixels, EdgeView.RIGHT);
536 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800537 }
538
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800539 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
540 }
541
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800542 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800543 Box b = mBoxes.get(0);
544
545 // Horizontal direction: we show the edge effect when the scrolling
546 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800547 x -= mPlatform.mDefaultX;
548 if (!mHasPrev && x > 0) {
549 mListener.onPull(x, EdgeView.LEFT);
550 x = 0;
551 } else if (!mHasNext && x < 0) {
552 mListener.onPull(-x, EdgeView.RIGHT);
553 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800554 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800555 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800556 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800557 }
558
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800559 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800560 int vx = (int) (velocityX + 0.5f);
561 int vy = (int) (velocityY + 0.5f);
562 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
563 }
564
565 private boolean flingPage(int velocityX, int velocityY) {
566 Box b = mBoxes.get(0);
567 Platform p = mPlatform;
568
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800569 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800570 if (viewWiderThanScaledImage(b.mCurrentScale) &&
571 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800572 return false;
573 }
574
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800575 // We only allow flinging in the directions where it won't go over the
576 // picture.
577 int edges = getImageAtEdges();
578 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
579 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
580 velocityX = 0;
581 }
582 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
583 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
584 velocityY = 0;
585 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800586
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800587 if (velocityX == 0 && velocityY == 0) return false;
588
589 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800590 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800591 int targetX = mPageScroller.getFinalX();
592 int targetY = mPageScroller.getFinalY();
593 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
594 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800595 return true;
596 }
597
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800598 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800599 Box b = mBoxes.get(0);
600 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800601
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800602 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800603 int defaultX = p.mDefaultX;
604 if ((!mHasPrev && p.mCurrentX >= defaultX)
605 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800606 return false;
607 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800608
609 if (velocityX == 0) return false;
610
611 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
612 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
613 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800614 // This value doesn't matter because we use mFilmScroller.isFinished()
615 // to decide when to stop. We set this to 0 so it's faster for
616 // Animatable.advanceAnimation() to calculate the progress (always 1).
617 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800618 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
619 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800620 }
621
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800622 ////////////////////////////////////////////////////////////////////////////
623 // Redraw
624 //
625 // If a method changes box positions directly, redraw()
626 // should be called.
627 //
628 // If a method may also cause a snapback to happen, snapAndRedraw() should
629 // be called.
630 //
631 // If a method starts an animation to change the position of focused box,
632 // startAnimation() should be called.
633 //
634 // If time advances to change the box position, advanceAnimation() should
635 // be called.
636 ////////////////////////////////////////////////////////////////////////////
637 private void redraw() {
638 layoutAndSetPosition();
639 mListener.invalidate();
640 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800641
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800642 private void snapAndRedraw() {
643 mPlatform.startSnapback();
644 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
645 mBoxes.get(i).startSnapback();
646 }
647 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
648 mGaps.get(i).startSnapback();
649 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800650 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800651 redraw();
652 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800653
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800654 private void startAnimation(int targetX, int targetY, float targetScale,
655 int kind) {
656 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800657 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800658 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
659 if (changed) redraw();
660 }
661
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800662 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800663 boolean changed = false;
664 changed |= mPlatform.advanceAnimation();
665 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
666 changed |= mBoxes.get(i).advanceAnimation();
667 }
668 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
669 changed |= mGaps.get(i).advanceAnimation();
670 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800671 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800672 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800673 }
674
Yuli Huangf320b842012-05-16 01:38:06 +0800675 public boolean inOpeningAnimation() {
676 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
677 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
678 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
679 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
680 }
681
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800682 ////////////////////////////////////////////////////////////////////////////
683 // Layout
684 ////////////////////////////////////////////////////////////////////////////
685
686 // Returns the display width of this box.
687 private int widthOf(Box b) {
688 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
689 }
690
691 // Returns the display height of this box.
692 private int heightOf(Box b) {
693 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
694 }
695
696 // Returns the display width of this box, using the given scale.
697 private int widthOf(Box b, float scale) {
698 return (int) (b.mImageW * scale + 0.5f);
699 }
700
701 // Returns the display height of this box, using the given scale.
702 private int heightOf(Box b, float scale) {
703 return (int) (b.mImageH * scale + 0.5f);
704 }
705
706 // Convert the information in mPlatform and mBoxes to mRects, so the user
707 // can get the position of each box by getPosition().
708 //
709 // Note the loop index goes from inside-out because each box's X coordinate
710 // is relative to its anchor box (except the focused box).
711 private void layoutAndSetPosition() {
712 // layout box 0 (focused box)
713 convertBoxToRect(0);
714 for (int i = 1; i <= BOX_MAX; i++) {
715 // layout box i and -i
716 convertBoxToRect(i);
717 convertBoxToRect(-i);
718 }
719 //dumpState();
720 }
721
722 private void dumpState() {
723 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
724 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800725 }
726
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800727 dumpRect(0);
728 for (int i = 1; i <= BOX_MAX; i++) {
729 dumpRect(i);
730 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800731 }
732
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800733 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
734 for (int j = i + 1; j <= BOX_MAX; j++) {
735 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
736 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
737 }
738 }
739 }
740 }
741
742 private void dumpRect(int i) {
743 StringBuilder sb = new StringBuilder();
744 Rect r = mRects.get(i);
745 sb.append("Rect " + i + ":");
746 sb.append("(");
747 sb.append(r.centerX());
748 sb.append(",");
749 sb.append(r.centerY());
750 sb.append(") [");
751 sb.append(r.width());
752 sb.append("x");
753 sb.append(r.height());
754 sb.append("]");
755 Log.d(TAG, sb.toString());
756 }
757
758 private void convertBoxToRect(int i) {
759 Box b = mBoxes.get(i);
760 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800761 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800762 int w = widthOf(b);
763 int h = heightOf(b);
764 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800765 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800766 r.left = x - w / 2;
767 r.right = r.left + w;
768 } else if (i > 0) {
769 Rect a = mRects.get(i - 1);
770 Gap g = mGaps.get(i - 1);
771 r.left = a.right + g.mCurrentGap;
772 r.right = r.left + w;
773 } else { // i < 0
774 Rect a = mRects.get(i + 1);
775 Gap g = mGaps.get(i);
776 r.right = a.left - g.mCurrentGap;
777 r.left = r.right - w;
778 }
779 r.top = y - h / 2;
780 r.bottom = r.top + h;
781 }
782
783 // Returns the position of a box.
784 public Rect getPosition(int index) {
785 return mRects.get(index);
786 }
787
788 ////////////////////////////////////////////////////////////////////////////
789 // Box management
790 ////////////////////////////////////////////////////////////////////////////
791
792 // Initialize the platform to be at the view center.
793 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800794 mPlatform.updateDefaultXY();
795 mPlatform.mCurrentX = mPlatform.mDefaultX;
796 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800797 mPlatform.mAnimationStartTime = NO_ANIMATION;
798 }
799
800 // Initialize a box to have the size of the view.
801 private void initBox(int index) {
802 Box b = mBoxes.get(index);
803 b.mImageW = mViewW;
804 b.mImageH = mViewH;
805 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800806 b.mScaleMin = getMinimalScale(b);
807 b.mScaleMax = getMaximalScale(b);
808 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800809 b.mCurrentScale = b.mScaleMin;
810 b.mAnimationStartTime = NO_ANIMATION;
811 }
812
813 // Initialize a gap. This can only be called after the boxes around the gap
814 // has been initialized.
815 private void initGap(int index) {
816 Gap g = mGaps.get(index);
817 g.mDefaultSize = getDefaultGapSize(index);
818 g.mCurrentGap = g.mDefaultSize;
819 g.mAnimationStartTime = NO_ANIMATION;
820 }
821
822 private void initGap(int index, int size) {
823 Gap g = mGaps.get(index);
824 g.mDefaultSize = getDefaultGapSize(index);
825 g.mCurrentGap = size;
826 g.mAnimationStartTime = NO_ANIMATION;
827 }
828
829 private void debugMoveBox(int fromIndex[]) {
830 StringBuilder s = new StringBuilder("moveBox:");
831 for (int i = 0; i < fromIndex.length; i++) {
832 int j = fromIndex[i];
833 if (j == Integer.MAX_VALUE) {
834 s.append(" N");
835 } else {
836 s.append(" ");
837 s.append(fromIndex[i]);
838 }
839 }
840 Log.d(TAG, s.toString());
841 }
842
843 // Move the boxes: it may indicate focus change, box deleted, box appearing,
844 // box reordered, etc.
845 //
846 // Each element in the fromIndex array indicates where each box was in the
847 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
848 // means the box is new.
849 //
850 // For example:
851 // N N N N N N N -- all new boxes
852 // -3 -2 -1 0 1 2 3 -- nothing changed
853 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800854 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800855 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800856 //
857 // hasPrev/hasNext indicates if there are previous/next boxes for the
858 // focused box. constrained indicates whether the focused box should be put
859 // into the constrained frame.
860 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
861 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800862 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800863 mHasPrev = hasPrev;
864 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800865
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800866 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
867
868 // 1. Get the absolute X coordiates for the boxes.
869 layoutAndSetPosition();
870 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
871 Box b = mBoxes.get(i);
872 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800873 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800874 }
875
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800876 // 2. copy boxes and gaps to temporary storage.
877 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
878 mTempBoxes.put(i, mBoxes.get(i));
879 mBoxes.put(i, null);
880 }
881 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
882 mTempGaps.put(i, mGaps.get(i));
883 mGaps.put(i, null);
884 }
885
886 // 3. move back boxes that are used in the new array.
887 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
888 int j = from.get(i);
889 if (j == Integer.MAX_VALUE) continue;
890 mBoxes.put(i, mTempBoxes.get(j));
891 mTempBoxes.put(j, null);
892 }
893
894 // 4. move back gaps if both boxes around it are kept together.
895 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
896 int j = from.get(i);
897 if (j == Integer.MAX_VALUE) continue;
898 int k = from.get(i + 1);
899 if (k == Integer.MAX_VALUE) continue;
900 if (j + 1 == k) {
901 mGaps.put(i, mTempGaps.get(j));
902 mTempGaps.put(j, null);
903 }
904 }
905
906 // 5. recycle the boxes that are not used in the new array.
907 int k = -BOX_MAX;
908 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
909 if (mBoxes.get(i) != null) continue;
910 while (mTempBoxes.get(k) == null) {
911 k++;
912 }
913 mBoxes.put(i, mTempBoxes.get(k++));
914 initBox(i);
915 }
916
917 // 6. Now give the recycled box a reasonable absolute X position.
918 //
919 // First try to find the first and the last box which the absolute X
920 // position is known.
921 int first, last;
922 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
923 if (from.get(first) != Integer.MAX_VALUE) break;
924 }
925 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
926 if (from.get(last) != Integer.MAX_VALUE) break;
927 }
928 // If there is no box has known X position at all, make the focused one
929 // as known.
930 if (first > BOX_MAX) {
931 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
932 first = last = 0;
933 }
934 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800935 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800936 // rare). For the boxes before first or after last, we will use a new
937 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800938 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800939 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800940 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800941 }
942
943 // 7. recycle the gaps that are not used in the new array.
944 k = -BOX_MAX;
945 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
946 if (mGaps.get(i) != null) continue;
947 while (mTempGaps.get(k) == null) {
948 k++;
949 }
950 mGaps.put(i, mTempGaps.get(k++));
951 Box a = mBoxes.get(i);
952 Box b = mBoxes.get(i + 1);
953 int wa = widthOf(a);
954 int wb = widthOf(b);
955 if (i >= first && i < last) {
956 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
957 initGap(i, g);
958 } else {
959 initGap(i);
960 }
961 }
962
Chih-Chung Changc4791b72012-05-18 19:10:36 -0700963 // 8. calculate the new absolute X coordinates for those box before
964 // first or after last.
965 for (int i = first - 1; i >= -BOX_MAX; i--) {
966 Box a = mBoxes.get(i + 1);
967 Box b = mBoxes.get(i);
968 int wa = widthOf(a);
969 int wb = widthOf(b);
970 Gap g = mGaps.get(i);
971 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
972 }
973
974 for (int i = last + 1; i <= BOX_MAX; i++) {
975 Box a = mBoxes.get(i - 1);
976 Box b = mBoxes.get(i);
977 int wa = widthOf(a);
978 int wb = widthOf(b);
979 Gap g = mGaps.get(i - 1);
980 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
981 }
982
983 // 9. offset the Platform position
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800984 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
985 mPlatform.mCurrentX += dx;
986 mPlatform.mFromX += dx;
987 mPlatform.mToX += dx;
988 mPlatform.mFlingOffset += dx;
989
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800990 if (mConstrained != constrained) {
991 mConstrained = constrained;
992 mPlatform.updateDefaultXY();
993 updateScaleAndGapLimit();
994 }
995
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800996 snapAndRedraw();
997 }
998
999 ////////////////////////////////////////////////////////////////////////////
1000 // Public utilities
1001 ////////////////////////////////////////////////////////////////////////////
1002
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001003 public boolean isAtMinimalScale() {
1004 Box b = mBoxes.get(0);
1005 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1006 }
1007
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001008 public boolean isCenter() {
1009 Box b = mBoxes.get(0);
1010 return mPlatform.mCurrentX == mPlatform.mDefaultX
1011 && b.mCurrentY == 0;
1012 }
1013
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001014 public int getImageWidth() {
1015 Box b = mBoxes.get(0);
1016 return b.mImageW;
1017 }
1018
1019 public int getImageHeight() {
1020 Box b = mBoxes.get(0);
1021 return b.mImageH;
1022 }
1023
1024 public float getImageScale() {
1025 Box b = mBoxes.get(0);
1026 return b.mCurrentScale;
1027 }
1028
1029 public int getImageAtEdges() {
1030 Box b = mBoxes.get(0);
1031 Platform p = mPlatform;
1032 calculateStableBound(b.mCurrentScale);
1033 int edges = 0;
1034 if (p.mCurrentX <= mBoundLeft) {
1035 edges |= IMAGE_AT_RIGHT_EDGE;
1036 }
1037 if (p.mCurrentX >= mBoundRight) {
1038 edges |= IMAGE_AT_LEFT_EDGE;
1039 }
1040 if (b.mCurrentY <= mBoundTop) {
1041 edges |= IMAGE_AT_BOTTOM_EDGE;
1042 }
1043 if (b.mCurrentY >= mBoundBottom) {
1044 edges |= IMAGE_AT_TOP_EDGE;
1045 }
1046 return edges;
1047 }
1048
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001049 public boolean isScrolling() {
1050 return mPlatform.mAnimationStartTime != NO_ANIMATION
1051 && mPlatform.mCurrentX != mPlatform.mToX;
1052 }
1053
1054 public void stopScrolling() {
1055 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
1056 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1057 }
1058
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001059 public float getFilmRatio() {
1060 return mFilmRatio.mCurrentRatio;
1061 }
1062
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001063 ////////////////////////////////////////////////////////////////////////////
1064 // Private utilities
1065 ////////////////////////////////////////////////////////////////////////////
1066
1067 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001068 float wFactor = 1.0f;
1069 float hFactor = 1.0f;
1070 int viewW, viewH;
1071
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001072 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1073 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001074 viewW = mConstrainedFrame.width();
1075 viewH = mConstrainedFrame.height();
1076 } else {
1077 viewW = mViewW;
1078 viewH = mViewH;
1079 }
1080
1081 if (mFilmMode) {
1082 if (mViewH > mViewW) { // portrait
1083 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1084 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1085 } else { // landscape
1086 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1087 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1088 }
1089 }
1090
1091 float s = Math.min(wFactor * viewW / b.mImageW,
1092 hFactor * viewH / b.mImageH);
1093 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001094 }
1095
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001096 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001097 if (mFilmMode) return getMinimalScale(b);
1098 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1099 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001100 }
1101
1102 private static boolean isAlmostEqual(float a, float b) {
1103 float diff = a - b;
1104 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001105 }
1106
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001107 // Calculates the stable region of mPlatform.mCurrentX and
1108 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001109 //
1110 // (1) If the dimension of scaled image >= view dimension, we will not
1111 // see black region outside the image (at that dimension).
1112 // (2) If the dimension of scaled image < view dimension, we will center
1113 // the scaled image.
1114 //
1115 // We might temporarily go out of this stable during user interaction,
1116 // but will "snap back" after user stops interaction.
1117 //
1118 // The results are stored in mBound{Left/Right/Top/Bottom}.
1119 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001120 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1121 // is used to extend the stable region by some pixels on each side
1122 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001123 private void calculateStableBound(float scale, int horizontalSlack) {
1124 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001125
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001126 // The width and height of the box in number of view pixels
1127 int w = widthOf(b, scale);
1128 int h = heightOf(b, scale);
1129
1130 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001131 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1132 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1133 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1134 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001135
1136 // If the scaled height is smaller than the view height,
1137 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001138 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001139 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001140 }
1141
1142 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001143 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001144 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001145 }
1146 }
1147
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001148 private void calculateStableBound(float scale) {
1149 calculateStableBound(scale, 0);
1150 }
1151
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001152 private boolean viewTallerThanScaledImage(float scale) {
1153 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001154 }
1155
1156 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001157 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001158 }
1159
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001160 private float getTargetScale(Box b) {
1161 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001162 }
1163
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001164 private int getTargetX(Platform p) {
1165 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001166 }
1167
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001168 private int getTargetY(Box b) {
1169 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001170 }
1171
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001172 private boolean useCurrentValueAsTarget(Animatable a) {
1173 return a.mAnimationStartTime == NO_ANIMATION ||
1174 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1175 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001176 }
1177
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001178 ////////////////////////////////////////////////////////////////////////////
1179 // Animatable: an thing which can do animation.
1180 ////////////////////////////////////////////////////////////////////////////
1181 private abstract static class Animatable {
1182 public long mAnimationStartTime;
1183 public int mAnimationKind;
1184 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001185
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001186 // This should be overidden in subclass to change the animation values
1187 // give the progress value in [0, 1].
1188 protected abstract boolean interpolate(float progress);
1189 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001190
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001191 // Returns true if the animation values changes, so things need to be
1192 // redrawn.
1193 public boolean advanceAnimation() {
1194 if (mAnimationStartTime == NO_ANIMATION) {
1195 return false;
1196 }
1197 if (mAnimationStartTime == LAST_ANIMATION) {
1198 mAnimationStartTime = NO_ANIMATION;
1199 return startSnapback();
1200 }
1201
1202 float progress;
1203 if (mAnimationDuration == 0) {
1204 progress = 1;
1205 } else {
1206 long now = AnimationTime.get();
1207 progress =
1208 (float) (now - mAnimationStartTime) / mAnimationDuration;
1209 }
1210
1211 if (progress >= 1) {
1212 progress = 1;
1213 } else {
1214 progress = applyInterpolationCurve(mAnimationKind, progress);
1215 }
1216
1217 boolean done = interpolate(progress);
1218
1219 if (done) {
1220 mAnimationStartTime = LAST_ANIMATION;
1221 }
1222
1223 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001224 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001225
1226 private static float applyInterpolationCurve(int kind, float progress) {
1227 float f = 1 - progress;
1228 switch (kind) {
1229 case ANIM_KIND_SCROLL:
1230 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001231 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001232 progress = 1 - f; // linear
1233 break;
1234 case ANIM_KIND_SCALE:
1235 progress = 1 - f * f; // quadratic
1236 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001237 case ANIM_KIND_OPENING:
1238 progress = 1 - f * f * f; // x^3
1239 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001240 case ANIM_KIND_SNAPBACK:
1241 case ANIM_KIND_ZOOM:
1242 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001243 progress = 1 - f * f * f * f * f; // x^5
1244 break;
1245 }
1246 return progress;
1247 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001248 }
1249
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001250 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001251 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001252 ////////////////////////////////////////////////////////////////////////////
1253 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001254 public int mCurrentX, mFromX, mToX, mDefaultX;
1255 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001256 public int mFlingOffset;
1257
1258 @Override
1259 public boolean startSnapback() {
1260 if (mAnimationStartTime != NO_ANIMATION) return false;
1261 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001262 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001263
1264 Box b = mBoxes.get(0);
1265 float scaleMin = mExtraScalingRange ?
1266 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1267 float scaleMax = mExtraScalingRange ?
1268 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1269 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1270 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001271 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001272 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001273 int defaultX = mDefaultX;
1274 if (!mHasNext) x = Math.max(x, defaultX);
1275 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001276 } else {
1277 calculateStableBound(scale, HORIZONTAL_SLACK);
1278 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1279 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001280 if (mCurrentX != x || mCurrentY != y) {
1281 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001282 }
1283 return false;
1284 }
1285
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001286 // The updateDefaultXY() should be called whenever these variables
1287 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1288 // mFilmMode
1289 public void updateDefaultXY() {
1290 // We don't check mFilmMode and return 0 for mDefaultX. Because
1291 // otherwise if we decide to leave film mode because we are
1292 // centered, we will immediately back into film mode because we find
1293 // we are not centered.
1294 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1295 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1296 mDefaultY = mFilmMode ? 0 :
1297 mConstrainedFrame.centerY() - mViewH / 2;
1298 } else {
1299 mDefaultX = 0;
1300 mDefaultY = 0;
1301 }
1302 }
1303
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001304 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001305 private boolean doAnimation(int targetX, int targetY, int kind) {
1306 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001307 mAnimationKind = kind;
1308 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001309 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001310 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001311 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001312 mAnimationStartTime = AnimationTime.startTime();
1313 mAnimationDuration = ANIM_TIME[kind];
1314 mFlingOffset = 0;
1315 advanceAnimation();
1316 return true;
1317 }
1318
1319 @Override
1320 protected boolean interpolate(float progress) {
1321 if (mAnimationKind == ANIM_KIND_FLING) {
1322 return mFilmMode
1323 ? interpolateFlingFilm(progress)
1324 : interpolateFlingPage(progress);
1325 } else {
1326 return interpolateLinear(progress);
1327 }
1328 }
1329
1330 private boolean interpolateFlingFilm(float progress) {
1331 mFilmScroller.computeScrollOffset();
1332 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1333
1334 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001335 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001336 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001337 dir = EdgeView.RIGHT;
1338 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001339 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001340 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001341 dir = EdgeView.LEFT;
1342 }
1343 }
1344 if (dir != EdgeView.INVALID_DIRECTION) {
1345 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1346 mListener.onAbsorb(v, dir);
1347 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001348 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001349 }
1350 return mFilmScroller.isFinished();
1351 }
1352
1353 private boolean interpolateFlingPage(float progress) {
1354 mPageScroller.computeScrollOffset(progress);
1355 Box b = mBoxes.get(0);
1356 calculateStableBound(b.mCurrentScale);
1357
1358 int oldX = mCurrentX;
1359 mCurrentX = mPageScroller.getCurrX();
1360
1361 // Check if we hit the edges; show edge effects if we do.
1362 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1363 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1364 mListener.onAbsorb(v, EdgeView.RIGHT);
1365 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1366 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1367 mListener.onAbsorb(v, EdgeView.LEFT);
1368 }
1369
1370 return progress >= 1;
1371 }
1372
1373 private boolean interpolateLinear(float progress) {
1374 // Other animations
1375 if (progress >= 1) {
1376 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001377 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001378 return true;
1379 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001380 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1381 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001382 }
1383 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1384 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1385 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001386 return false;
1387 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001388 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001389 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001390 }
1391 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001392 }
1393
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001394 ////////////////////////////////////////////////////////////////////////////
1395 // Box: represents a rectangular area which shows a picture.
1396 ////////////////////////////////////////////////////////////////////////////
1397 private class Box extends Animatable {
1398 // Size of the bitmap
1399 public int mImageW, mImageH;
1400
1401 // This is true if we assume the image size is the same as view size
1402 // until we know the actual size of image. This is also used to
1403 // determine if there is an image ready to show.
1404 public boolean mUseViewSize;
1405
1406 // The minimum and maximum scale we allow for this box.
1407 public float mScaleMin, mScaleMax;
1408
1409 // The X/Y value indicates where the center of the box is on the view
1410 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1411 // actual values used currently. Note that the X values are implicitly
1412 // defined by Platform and Gaps.
1413 public int mCurrentY, mFromY, mToY;
1414 public float mCurrentScale, mFromScale, mToScale;
1415
1416 // The absolute X coordinate of the center of the box. This is only used
1417 // during moveBox().
1418 public int mAbsoluteX;
1419
1420 @Override
1421 public boolean startSnapback() {
1422 if (mAnimationStartTime != NO_ANIMATION) return false;
1423 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001424 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001425 if (mInScale && this == mBoxes.get(0)) return false;
1426
1427 int y;
1428 float scale;
1429
1430 if (this == mBoxes.get(0)) {
1431 float scaleMin = mExtraScalingRange ?
1432 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1433 float scaleMax = mExtraScalingRange ?
1434 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1435 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1436 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001437 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001438 } else {
1439 calculateStableBound(scale, HORIZONTAL_SLACK);
1440 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1441 }
1442 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001443 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001444 scale = mScaleMin;
1445 }
1446
1447 if (mCurrentY != y || mCurrentScale != scale) {
1448 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1449 }
1450 return false;
1451 }
1452
1453 private boolean doAnimation(int targetY, float targetScale, int kind) {
1454 targetScale = Utils.clamp(targetScale,
1455 SCALE_MIN_EXTRA * mScaleMin,
1456 SCALE_MAX_EXTRA * mScaleMax);
1457
1458 // If the scaled height is smaller than the view height, force it to be
1459 // in the center. (We do this for height only, not width, because the
1460 // user may want to scroll to the previous/next image.)
1461 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001462 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001463 }
1464
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001465 if (mCurrentY == targetY && mCurrentScale == targetScale
1466 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001467 return false;
1468 }
1469
1470 // Now starts an animation for the box.
1471 mAnimationKind = kind;
1472 mFromY = mCurrentY;
1473 mFromScale = mCurrentScale;
1474 mToY = targetY;
1475 mToScale = targetScale;
1476 mAnimationStartTime = AnimationTime.startTime();
1477 mAnimationDuration = ANIM_TIME[kind];
1478 advanceAnimation();
1479 return true;
1480 }
1481
1482 @Override
1483 protected boolean interpolate(float progress) {
1484 if (mAnimationKind == ANIM_KIND_FLING) {
1485 // Currently a Box can only be flung in page mode.
1486 return interpolateFlingPage(progress);
1487 } else {
1488 return interpolateLinear(progress);
1489 }
1490 }
1491
1492 private boolean interpolateFlingPage(float progress) {
1493 mPageScroller.computeScrollOffset(progress);
1494 calculateStableBound(mCurrentScale);
1495
1496 int oldY = mCurrentY;
1497 mCurrentY = mPageScroller.getCurrY();
1498
1499 // Check if we hit the edges; show edge effects if we do.
1500 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1501 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1502 mListener.onAbsorb(v, EdgeView.BOTTOM);
1503 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1504 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1505 mListener.onAbsorb(v, EdgeView.TOP);
1506 }
1507
1508 return progress >= 1;
1509 }
1510
1511 private boolean interpolateLinear(float progress) {
1512 if (progress >= 1) {
1513 mCurrentY = mToY;
1514 mCurrentScale = mToScale;
1515 return true;
1516 } else {
1517 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1518 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001519 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1520 float f = CaptureAnimation.calculateScale(progress);
1521 mCurrentScale *= f;
1522 return false;
1523 } else {
1524 return (mCurrentY == mToY && mCurrentScale == mToScale);
1525 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001526 }
1527 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001528 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001529
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001530 ////////////////////////////////////////////////////////////////////////////
1531 // Gap: represents a rectangular area which is between two boxes.
1532 ////////////////////////////////////////////////////////////////////////////
1533 private class Gap extends Animatable {
1534 // The default gap size between two boxes. The value may vary for
1535 // different image size of the boxes and for different modes (page or
1536 // film).
1537 public int mDefaultSize;
1538
1539 // The gap size between the two boxes.
1540 public int mCurrentGap, mFromGap, mToGap;
1541
1542 @Override
1543 public boolean startSnapback() {
1544 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001545 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001546 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001547
1548 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001549 public boolean doAnimation(int targetSize, int kind) {
1550 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1551 return false;
1552 }
1553 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001554 mFromGap = mCurrentGap;
1555 mToGap = targetSize;
1556 mAnimationStartTime = AnimationTime.startTime();
1557 mAnimationDuration = ANIM_TIME[mAnimationKind];
1558 advanceAnimation();
1559 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001560 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001561
1562 @Override
1563 protected boolean interpolate(float progress) {
1564 if (progress >= 1) {
1565 mCurrentGap = mToGap;
1566 return true;
1567 } else {
1568 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001569 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1570 float f = CaptureAnimation.calculateScale(progress);
1571 mCurrentGap = (int) (mCurrentGap * f);
1572 return false;
1573 } else {
1574 return (mCurrentGap == mToGap);
1575 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001576 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001577 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001578 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001579
1580 ////////////////////////////////////////////////////////////////////////////
1581 // FilmRatio: represents the progress of film mode change.
1582 ////////////////////////////////////////////////////////////////////////////
1583 private class FilmRatio extends Animatable {
1584 // The film ratio: 1 means switching to film mode is complete, 0 means
1585 // switching to page mode is complete.
1586 public float mCurrentRatio, mFromRatio, mToRatio;
1587
1588 @Override
1589 public boolean startSnapback() {
1590 float target = mFilmMode ? 1f : 0f;
1591 if (target == mToRatio) return false;
1592 return doAnimation(target, ANIM_KIND_SNAPBACK);
1593 }
1594
1595 // Starts an animation for the film ratio.
1596 private boolean doAnimation(float targetRatio, int kind) {
1597 mAnimationKind = kind;
1598 mFromRatio = mCurrentRatio;
1599 mToRatio = targetRatio;
1600 mAnimationStartTime = AnimationTime.startTime();
1601 mAnimationDuration = ANIM_TIME[mAnimationKind];
1602 advanceAnimation();
1603 return true;
1604 }
1605
1606 @Override
1607 protected boolean interpolate(float progress) {
1608 if (progress >= 1) {
1609 mCurrentRatio = mToRatio;
1610 return true;
1611 } else {
1612 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1613 return (mCurrentRatio == mToRatio);
1614 }
1615 }
1616 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001617}