blob: b6737226be6e90028ee465b82a3180c04721bceb [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
Chih-Chung Changf3f90752012-05-30 17:10:32 -0700203 boolean wasMinimal = isAtMinimalScale();
204
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800205 mViewW = viewW;
206 mViewH = viewH;
207 initPlatform();
208
209 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
210 setBoxSize(i, viewW, viewH, true);
211 }
212
213 updateScaleAndGapLimit();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700214
Chih-Chung Changf3f90752012-05-30 17:10:32 -0700215 // If the focused box was at minimal scale, we try to make it the
216 // minimal scale under the new view size.
217 if (wasMinimal) {
218 Box b = mBoxes.get(0);
219 b.mCurrentScale = b.mScaleMin;
220 }
221
Chih-Chung Change6251df2012-05-22 11:35:46 -0700222 // If we have the opening animation, do it. Otherwise go directly to the
223 // right position.
224 if (!startOpeningAnimationIfNeeded()) {
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700225 skipToFinalPosition();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700226 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800227 }
228
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700229 public void setConstrainedFrame(Rect cFrame) {
230 if (mConstrainedFrame.equals(cFrame)) return;
231 mConstrainedFrame.set(cFrame);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800232 mPlatform.updateDefaultXY();
233 updateScaleAndGapLimit();
234 snapAndRedraw();
235 }
236
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700237 public void forceImageSize(int index, int width, int height) {
238 if (width == 0 || height == 0) return;
239 Box b = mBoxes.get(index);
240 b.mImageW = width;
241 b.mImageH = height;
242 return;
243 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800244
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700245 public void setImageSize(int index, int width, int height, Rect cFrame) {
246 if (width == 0 || height == 0) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800247
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700248 boolean needUpdate = false;
249 if (cFrame != null && !mConstrainedFrame.equals(cFrame)) {
250 mConstrainedFrame.set(cFrame);
251 mPlatform.updateDefaultXY();
252 needUpdate = true;
253 }
254 needUpdate |= setBoxSize(index, width, height, false);
255
256 if (!needUpdate) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800257 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800258 snapAndRedraw();
259 }
260
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800261 // Returns false if the box size doesn't change.
262 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800263 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800264 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800265
Chih-Chung Changd8488622012-04-17 12:56:08 +0800266 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800267 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800268
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800269 b.mUseViewSize = isViewSize;
270
271 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800272 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800273 }
274
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800275 // The ratio of the old size and the new size.
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700276 //
277 // If the aspect ratio changes, we don't know if it is because one side
278 // grows or the other side shrinks. Currently we just assume the view
279 // angle of the longer side doesn't change (so the aspect ratio change
280 // is because the view angle of the shorter side changes). This matches
281 // what camera preview does.
282 float ratio = (width > height)
283 ? (float) b.mImageW / width
284 : (float) b.mImageH / height;
Chih-Chung Changec412542011-09-26 17:34:06 +0800285
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800286 b.mImageW = width;
287 b.mImageH = height;
288
Chih-Chung Changd8488622012-04-17 12:56:08 +0800289 // If this is the first time we receive an image size, we change the
290 // scale directly. Otherwise adjust the scales by a ratio, and snapback
291 // will animate the scale into the min/max bounds if necessary.
292 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800293 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800294 b.mAnimationStartTime = NO_ANIMATION;
295 } else {
296 b.mCurrentScale *= ratio;
297 b.mFromScale *= ratio;
298 b.mToScale *= ratio;
299 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800300
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800301 if (i == 0) {
302 mFocusX /= ratio;
303 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800304 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800305
306 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800307 }
308
Chih-Chung Change6251df2012-05-22 11:35:46 -0700309 private boolean startOpeningAnimationIfNeeded() {
310 if (mOpenAnimationRect == null) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800311 Box b = mBoxes.get(0);
Chih-Chung Change6251df2012-05-22 11:35:46 -0700312 if (b.mUseViewSize) return false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800313
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800314 // Start animation from the saved rectangle if we have one.
315 Rect r = mOpenAnimationRect;
316 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800317
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800318 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
319 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800320 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
321 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800322 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
323 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800324
325 // Animate from large gaps for neighbor boxes to avoid them
326 // shown on the screen during opening animation.
327 for (int i = -1; i < 1; i++) {
328 Gap g = mGaps.get(i);
329 g.mCurrentGap = mViewW;
330 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
331 }
Chih-Chung Change6251df2012-05-22 11:35:46 -0700332
333 return true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800334 }
335
336 public void setFilmMode(boolean enabled) {
337 if (enabled == mFilmMode) return;
338 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800339
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800340 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800341 updateScaleAndGapLimit();
342 stopAnimation();
343 snapAndRedraw();
344 }
345
346 public void setExtraScalingRange(boolean enabled) {
347 if (mExtraScalingRange == enabled) return;
348 mExtraScalingRange = enabled;
349 if (!enabled) {
350 snapAndRedraw();
351 }
352 }
353
354 // This should be called whenever the scale range of boxes or the default
355 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800356 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800357 private void updateScaleAndGapLimit() {
358 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
359 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800360 b.mScaleMin = getMinimalScale(b);
361 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800362 }
363
364 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
365 Gap g = mGaps.get(i);
366 g.mDefaultSize = getDefaultGapSize(i);
367 }
368 }
369
370 // Returns the default gap size according the the size of the boxes around
371 // the gap and the current mode.
372 private int getDefaultGapSize(int i) {
373 if (mFilmMode) return IMAGE_GAP;
374 Box a = mBoxes.get(i);
375 Box b = mBoxes.get(i + 1);
376 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
377 }
378
379 // Here is how we layout the boxes in the page mode.
380 //
381 // previous current next
382 // ___________ ________________ __________
383 // | _______ | | __________ | | ______ |
384 // | | | | | | right->| | | | | |
385 // | | |<-------->|<--left | | | | | |
386 // | |_______| | | | |__________| | | |______| |
387 // |___________| | |________________| |__________|
388 // | <--> gapToSide()
389 // |
390 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
391 private int gapToSide(Box b) {
392 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
393 }
394
395 // Stop all animations at where they are now.
396 public void stopAnimation() {
397 mPlatform.mAnimationStartTime = NO_ANIMATION;
398 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
399 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
400 }
401 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
402 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
403 }
404 }
405
406 public void skipAnimation() {
407 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
408 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800409 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800410 mPlatform.mAnimationStartTime = NO_ANIMATION;
411 }
412 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
413 Box b = mBoxes.get(i);
414 if (b.mAnimationStartTime == NO_ANIMATION) continue;
415 b.mCurrentY = b.mToY;
416 b.mCurrentScale = b.mToScale;
417 b.mAnimationStartTime = NO_ANIMATION;
418 }
419 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
420 Gap g = mGaps.get(i);
421 if (g.mAnimationStartTime == NO_ANIMATION) continue;
422 g.mCurrentGap = g.mToGap;
423 g.mAnimationStartTime = NO_ANIMATION;
424 }
425 redraw();
426 }
427
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800428 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800429 snapAndRedraw();
430 }
431
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700432 public void skipToFinalPosition() {
433 stopAnimation();
434 snapAndRedraw();
435 skipAnimation();
436 }
437
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800438 ////////////////////////////////////////////////////////////////////////////
439 // Start an animations for the focused box
440 ////////////////////////////////////////////////////////////////////////////
441
442 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800443 tapX -= mViewW / 2;
444 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800445 Box b = mBoxes.get(0);
446
447 // Convert the tap position to distance to center in bitmap coordinates
448 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
449 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
450
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800451 int x = (int) (-tempX * targetScale + 0.5f);
452 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800453
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800454 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800455 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
456 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
457 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800458
459 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
460 }
461
462 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800463 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800464 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800465 }
466
Chih-Chung Changec412542011-09-26 17:34:06 +0800467 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800468 focusX -= mViewW / 2;
469 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800470 Box b = mBoxes.get(0);
471 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800472 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800473 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
474 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800475 }
476
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800477 // Scales the image by the given factor.
478 // Returns an out-of-range indicator:
479 // 1 if the intended scale is too large for the stable range.
480 // 0 if the intended scale is in the stable range.
481 // -1 if the intended scale is too small for the stable range.
482 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800483 focusX -= mViewW / 2;
484 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800485 Box b = mBoxes.get(0);
486 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800487
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800488 // We want to keep the focus point (on the bitmap) the same as when we
489 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800490 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800491 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800492 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800493 s *= getTargetScale(b);
494 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
495 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800496 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800497 if (s < b.mScaleMin) return -1;
498 if (s > b.mScaleMax) return 1;
499 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800500 }
501
502 public void endScale() {
503 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800504 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800505 }
506
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800507 // Slide the focused box to the center of the view.
508 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800509 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800510 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800511 }
512
513 // Slide the focused box to the center of the view with the capture
514 // animation. In addition to the sliding, the animation will also scale the
515 // the focused box, the specified neighbor box, and the gap between the
516 // two. The specified offset should be 1 or -1.
517 public void startCaptureAnimationSlide(int offset) {
518 Box b = mBoxes.get(0);
519 Box n = mBoxes.get(offset); // the neighbor box
520 Gap g = mGaps.get(offset); // the gap between the two boxes
521
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800522 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
523 ANIM_KIND_CAPTURE);
524 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
525 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800526 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
527 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800528 }
529
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800530 public void startScroll(float dx, float dy) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800531 Box b = mBoxes.get(0);
532 Platform p = mPlatform;
533
534 int x = getTargetX(p) + (int) (dx + 0.5f);
535 int y = getTargetY(b) + (int) (dy + 0.5f);
536
537 if (mFilmMode) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800538 scrollToFilm(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800539 } else {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800540 scrollToPage(x, y);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800541 }
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800542 }
543
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800544 private void scrollToPage(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800545 Box b = mBoxes.get(0);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800546
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800547 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800548
549 // Vertical direction: If we have space to move in the vertical
550 // direction, we show the edge effect when scrolling reaches the edge.
551 if (mBoundTop != mBoundBottom) {
552 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800553 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800554 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800555 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800556 }
557 }
558
559 y = Utils.clamp(y, mBoundTop, mBoundBottom);
560
561 // Horizontal direction: we show the edge effect when the scrolling
562 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800563 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800564 int pixels = x - mBoundRight;
565 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800566 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800567 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800568 int pixels = mBoundLeft - x;
569 mListener.onPull(pixels, EdgeView.RIGHT);
570 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800571 }
572
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800573 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
574 }
575
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800576 private void scrollToFilm(int x, int y) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800577 Box b = mBoxes.get(0);
578
579 // Horizontal direction: we show the edge effect when the scrolling
580 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800581 x -= mPlatform.mDefaultX;
582 if (!mHasPrev && x > 0) {
583 mListener.onPull(x, EdgeView.LEFT);
584 x = 0;
585 } else if (!mHasNext && x < 0) {
586 mListener.onPull(-x, EdgeView.RIGHT);
587 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800588 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800589 x += mPlatform.mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800590 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800591 }
592
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800593 public boolean fling(float velocityX, float velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800594 int vx = (int) (velocityX + 0.5f);
595 int vy = (int) (velocityY + 0.5f);
596 return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
597 }
598
599 private boolean flingPage(int velocityX, int velocityY) {
600 Box b = mBoxes.get(0);
601 Platform p = mPlatform;
602
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800603 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800604 if (viewWiderThanScaledImage(b.mCurrentScale) &&
605 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800606 return false;
607 }
608
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800609 // We only allow flinging in the directions where it won't go over the
610 // picture.
611 int edges = getImageAtEdges();
612 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
613 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
614 velocityX = 0;
615 }
616 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
617 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
618 velocityY = 0;
619 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800620
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800621 if (velocityX == 0 && velocityY == 0) return false;
622
623 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800624 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800625 int targetX = mPageScroller.getFinalX();
626 int targetY = mPageScroller.getFinalY();
627 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
628 startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800629 return true;
630 }
631
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800632 private boolean flingFilm(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800633 Box b = mBoxes.get(0);
634 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800635
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800636 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800637 int defaultX = p.mDefaultX;
638 if ((!mHasPrev && p.mCurrentX >= defaultX)
639 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800640 return false;
641 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800642
643 if (velocityX == 0) return false;
644
645 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
646 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
647 int targetX = mFilmScroller.getFinalX();
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800648 // This value doesn't matter because we use mFilmScroller.isFinished()
649 // to decide when to stop. We set this to 0 so it's faster for
650 // Animatable.advanceAnimation() to calculate the progress (always 1).
651 ANIM_TIME[ANIM_KIND_FLING] = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800652 startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
653 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800654 }
655
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800656 ////////////////////////////////////////////////////////////////////////////
657 // Redraw
658 //
659 // If a method changes box positions directly, redraw()
660 // should be called.
661 //
662 // If a method may also cause a snapback to happen, snapAndRedraw() should
663 // be called.
664 //
665 // If a method starts an animation to change the position of focused box,
666 // startAnimation() should be called.
667 //
668 // If time advances to change the box position, advanceAnimation() should
669 // be called.
670 ////////////////////////////////////////////////////////////////////////////
671 private void redraw() {
672 layoutAndSetPosition();
673 mListener.invalidate();
674 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800675
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800676 private void snapAndRedraw() {
677 mPlatform.startSnapback();
678 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
679 mBoxes.get(i).startSnapback();
680 }
681 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
682 mGaps.get(i).startSnapback();
683 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800684 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800685 redraw();
686 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800687
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800688 private void startAnimation(int targetX, int targetY, float targetScale,
689 int kind) {
690 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800691 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800692 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
693 if (changed) redraw();
694 }
695
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800696 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800697 boolean changed = false;
698 changed |= mPlatform.advanceAnimation();
699 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
700 changed |= mBoxes.get(i).advanceAnimation();
701 }
702 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
703 changed |= mGaps.get(i).advanceAnimation();
704 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800705 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800706 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800707 }
708
Yuli Huangf320b842012-05-16 01:38:06 +0800709 public boolean inOpeningAnimation() {
710 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
711 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
712 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
713 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
714 }
715
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800716 ////////////////////////////////////////////////////////////////////////////
717 // Layout
718 ////////////////////////////////////////////////////////////////////////////
719
720 // Returns the display width of this box.
721 private int widthOf(Box b) {
722 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
723 }
724
725 // Returns the display height of this box.
726 private int heightOf(Box b) {
727 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
728 }
729
730 // Returns the display width of this box, using the given scale.
731 private int widthOf(Box b, float scale) {
732 return (int) (b.mImageW * scale + 0.5f);
733 }
734
735 // Returns the display height of this box, using the given scale.
736 private int heightOf(Box b, float scale) {
737 return (int) (b.mImageH * scale + 0.5f);
738 }
739
740 // Convert the information in mPlatform and mBoxes to mRects, so the user
741 // can get the position of each box by getPosition().
742 //
743 // Note the loop index goes from inside-out because each box's X coordinate
744 // is relative to its anchor box (except the focused box).
745 private void layoutAndSetPosition() {
746 // layout box 0 (focused box)
747 convertBoxToRect(0);
748 for (int i = 1; i <= BOX_MAX; i++) {
749 // layout box i and -i
750 convertBoxToRect(i);
751 convertBoxToRect(-i);
752 }
753 //dumpState();
754 }
755
756 private void dumpState() {
757 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
758 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800759 }
760
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800761 dumpRect(0);
762 for (int i = 1; i <= BOX_MAX; i++) {
763 dumpRect(i);
764 dumpRect(-i);
Chih-Chung Changec412542011-09-26 17:34:06 +0800765 }
766
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800767 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
768 for (int j = i + 1; j <= BOX_MAX; j++) {
769 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
770 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
771 }
772 }
773 }
774 }
775
776 private void dumpRect(int i) {
777 StringBuilder sb = new StringBuilder();
778 Rect r = mRects.get(i);
779 sb.append("Rect " + i + ":");
780 sb.append("(");
781 sb.append(r.centerX());
782 sb.append(",");
783 sb.append(r.centerY());
784 sb.append(") [");
785 sb.append(r.width());
786 sb.append("x");
787 sb.append(r.height());
788 sb.append("]");
789 Log.d(TAG, sb.toString());
790 }
791
792 private void convertBoxToRect(int i) {
793 Box b = mBoxes.get(i);
794 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800795 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800796 int w = widthOf(b);
797 int h = heightOf(b);
798 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800799 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800800 r.left = x - w / 2;
801 r.right = r.left + w;
802 } else if (i > 0) {
803 Rect a = mRects.get(i - 1);
804 Gap g = mGaps.get(i - 1);
805 r.left = a.right + g.mCurrentGap;
806 r.right = r.left + w;
807 } else { // i < 0
808 Rect a = mRects.get(i + 1);
809 Gap g = mGaps.get(i);
810 r.right = a.left - g.mCurrentGap;
811 r.left = r.right - w;
812 }
813 r.top = y - h / 2;
814 r.bottom = r.top + h;
815 }
816
817 // Returns the position of a box.
818 public Rect getPosition(int index) {
819 return mRects.get(index);
820 }
821
822 ////////////////////////////////////////////////////////////////////////////
823 // Box management
824 ////////////////////////////////////////////////////////////////////////////
825
826 // Initialize the platform to be at the view center.
827 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800828 mPlatform.updateDefaultXY();
829 mPlatform.mCurrentX = mPlatform.mDefaultX;
830 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800831 mPlatform.mAnimationStartTime = NO_ANIMATION;
832 }
833
834 // Initialize a box to have the size of the view.
835 private void initBox(int index) {
836 Box b = mBoxes.get(index);
837 b.mImageW = mViewW;
838 b.mImageH = mViewH;
839 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800840 b.mScaleMin = getMinimalScale(b);
841 b.mScaleMax = getMaximalScale(b);
842 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800843 b.mCurrentScale = b.mScaleMin;
844 b.mAnimationStartTime = NO_ANIMATION;
845 }
846
847 // Initialize a gap. This can only be called after the boxes around the gap
848 // has been initialized.
849 private void initGap(int index) {
850 Gap g = mGaps.get(index);
851 g.mDefaultSize = getDefaultGapSize(index);
852 g.mCurrentGap = g.mDefaultSize;
853 g.mAnimationStartTime = NO_ANIMATION;
854 }
855
856 private void initGap(int index, int size) {
857 Gap g = mGaps.get(index);
858 g.mDefaultSize = getDefaultGapSize(index);
859 g.mCurrentGap = size;
860 g.mAnimationStartTime = NO_ANIMATION;
861 }
862
863 private void debugMoveBox(int fromIndex[]) {
864 StringBuilder s = new StringBuilder("moveBox:");
865 for (int i = 0; i < fromIndex.length; i++) {
866 int j = fromIndex[i];
867 if (j == Integer.MAX_VALUE) {
868 s.append(" N");
869 } else {
870 s.append(" ");
871 s.append(fromIndex[i]);
872 }
873 }
874 Log.d(TAG, s.toString());
875 }
876
877 // Move the boxes: it may indicate focus change, box deleted, box appearing,
878 // box reordered, etc.
879 //
880 // Each element in the fromIndex array indicates where each box was in the
881 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
882 // means the box is new.
883 //
884 // For example:
885 // N N N N N N N -- all new boxes
886 // -3 -2 -1 0 1 2 3 -- nothing changed
887 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800888 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800889 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800890 //
891 // hasPrev/hasNext indicates if there are previous/next boxes for the
892 // focused box. constrained indicates whether the focused box should be put
893 // into the constrained frame.
894 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
895 boolean constrained) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800896 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800897 mHasPrev = hasPrev;
898 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800899
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800900 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
901
902 // 1. Get the absolute X coordiates for the boxes.
903 layoutAndSetPosition();
904 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
905 Box b = mBoxes.get(i);
906 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800907 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +0800908 }
909
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800910 // 2. copy boxes and gaps to temporary storage.
911 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
912 mTempBoxes.put(i, mBoxes.get(i));
913 mBoxes.put(i, null);
914 }
915 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
916 mTempGaps.put(i, mGaps.get(i));
917 mGaps.put(i, null);
918 }
919
920 // 3. move back boxes that are used in the new array.
921 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
922 int j = from.get(i);
923 if (j == Integer.MAX_VALUE) continue;
924 mBoxes.put(i, mTempBoxes.get(j));
925 mTempBoxes.put(j, null);
926 }
927
928 // 4. move back gaps if both boxes around it are kept together.
929 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
930 int j = from.get(i);
931 if (j == Integer.MAX_VALUE) continue;
932 int k = from.get(i + 1);
933 if (k == Integer.MAX_VALUE) continue;
934 if (j + 1 == k) {
935 mGaps.put(i, mTempGaps.get(j));
936 mTempGaps.put(j, null);
937 }
938 }
939
940 // 5. recycle the boxes that are not used in the new array.
941 int k = -BOX_MAX;
942 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
943 if (mBoxes.get(i) != null) continue;
944 while (mTempBoxes.get(k) == null) {
945 k++;
946 }
947 mBoxes.put(i, mTempBoxes.get(k++));
948 initBox(i);
949 }
950
951 // 6. Now give the recycled box a reasonable absolute X position.
952 //
953 // First try to find the first and the last box which the absolute X
954 // position is known.
955 int first, last;
956 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
957 if (from.get(first) != Integer.MAX_VALUE) break;
958 }
959 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
960 if (from.get(last) != Integer.MAX_VALUE) break;
961 }
962 // If there is no box has known X position at all, make the focused one
963 // as known.
964 if (first > BOX_MAX) {
965 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
966 first = last = 0;
967 }
968 // Now for those boxes between first and last, just assign the same
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800969 // position as the next box. (We can do better, but this should be
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800970 // rare). For the boxes before first or after last, we will use a new
971 // default gap size below.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800972 for (int i = last - 1; i > first; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800973 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800974 mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800975 }
976
977 // 7. recycle the gaps that are not used in the new array.
978 k = -BOX_MAX;
979 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
980 if (mGaps.get(i) != null) continue;
981 while (mTempGaps.get(k) == null) {
982 k++;
983 }
984 mGaps.put(i, mTempGaps.get(k++));
985 Box a = mBoxes.get(i);
986 Box b = mBoxes.get(i + 1);
987 int wa = widthOf(a);
988 int wb = widthOf(b);
989 if (i >= first && i < last) {
990 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
991 initGap(i, g);
992 } else {
993 initGap(i);
994 }
995 }
996
Chih-Chung Changc4791b72012-05-18 19:10:36 -0700997 // 8. calculate the new absolute X coordinates for those box before
998 // first or after last.
999 for (int i = first - 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);
1005 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
1006 }
1007
1008 for (int i = last + 1; i <= BOX_MAX; i++) {
1009 Box a = mBoxes.get(i - 1);
1010 Box b = mBoxes.get(i);
1011 int wa = widthOf(a);
1012 int wb = widthOf(b);
1013 Gap g = mGaps.get(i - 1);
1014 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
1015 }
1016
1017 // 9. offset the Platform position
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001018 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
1019 mPlatform.mCurrentX += dx;
1020 mPlatform.mFromX += dx;
1021 mPlatform.mToX += dx;
1022 mPlatform.mFlingOffset += dx;
1023
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001024 if (mConstrained != constrained) {
1025 mConstrained = constrained;
1026 mPlatform.updateDefaultXY();
1027 updateScaleAndGapLimit();
1028 }
1029
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001030 snapAndRedraw();
1031 }
1032
1033 ////////////////////////////////////////////////////////////////////////////
1034 // Public utilities
1035 ////////////////////////////////////////////////////////////////////////////
1036
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001037 public boolean isAtMinimalScale() {
1038 Box b = mBoxes.get(0);
1039 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1040 }
1041
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001042 public boolean isCenter() {
1043 Box b = mBoxes.get(0);
1044 return mPlatform.mCurrentX == mPlatform.mDefaultX
1045 && b.mCurrentY == 0;
1046 }
1047
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001048 public int getImageWidth() {
1049 Box b = mBoxes.get(0);
1050 return b.mImageW;
1051 }
1052
1053 public int getImageHeight() {
1054 Box b = mBoxes.get(0);
1055 return b.mImageH;
1056 }
1057
1058 public float getImageScale() {
1059 Box b = mBoxes.get(0);
1060 return b.mCurrentScale;
1061 }
1062
1063 public int getImageAtEdges() {
1064 Box b = mBoxes.get(0);
1065 Platform p = mPlatform;
1066 calculateStableBound(b.mCurrentScale);
1067 int edges = 0;
1068 if (p.mCurrentX <= mBoundLeft) {
1069 edges |= IMAGE_AT_RIGHT_EDGE;
1070 }
1071 if (p.mCurrentX >= mBoundRight) {
1072 edges |= IMAGE_AT_LEFT_EDGE;
1073 }
1074 if (b.mCurrentY <= mBoundTop) {
1075 edges |= IMAGE_AT_BOTTOM_EDGE;
1076 }
1077 if (b.mCurrentY >= mBoundBottom) {
1078 edges |= IMAGE_AT_TOP_EDGE;
1079 }
1080 return edges;
1081 }
1082
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001083 public boolean isScrolling() {
1084 return mPlatform.mAnimationStartTime != NO_ANIMATION
1085 && mPlatform.mCurrentX != mPlatform.mToX;
1086 }
1087
1088 public void stopScrolling() {
1089 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
Angus Kong95c2aea2012-05-24 16:20:57 -07001090 if (mFilmMode) mFilmScroller.forceFinished(true);
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001091 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1092 }
1093
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001094 public float getFilmRatio() {
1095 return mFilmRatio.mCurrentRatio;
1096 }
1097
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001098 ////////////////////////////////////////////////////////////////////////////
1099 // Private utilities
1100 ////////////////////////////////////////////////////////////////////////////
1101
1102 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001103 float wFactor = 1.0f;
1104 float hFactor = 1.0f;
1105 int viewW, viewH;
1106
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001107 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1108 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001109 viewW = mConstrainedFrame.width();
1110 viewH = mConstrainedFrame.height();
1111 } else {
1112 viewW = mViewW;
1113 viewH = mViewH;
1114 }
1115
1116 if (mFilmMode) {
1117 if (mViewH > mViewW) { // portrait
1118 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1119 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1120 } else { // landscape
1121 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1122 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1123 }
1124 }
1125
1126 float s = Math.min(wFactor * viewW / b.mImageW,
1127 hFactor * viewH / b.mImageH);
1128 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001129 }
1130
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001131 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001132 if (mFilmMode) return getMinimalScale(b);
1133 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1134 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001135 }
1136
1137 private static boolean isAlmostEqual(float a, float b) {
1138 float diff = a - b;
1139 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001140 }
1141
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001142 // Calculates the stable region of mPlatform.mCurrentX and
1143 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001144 //
1145 // (1) If the dimension of scaled image >= view dimension, we will not
1146 // see black region outside the image (at that dimension).
1147 // (2) If the dimension of scaled image < view dimension, we will center
1148 // the scaled image.
1149 //
1150 // We might temporarily go out of this stable during user interaction,
1151 // but will "snap back" after user stops interaction.
1152 //
1153 // The results are stored in mBound{Left/Right/Top/Bottom}.
1154 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001155 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1156 // is used to extend the stable region by some pixels on each side
1157 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001158 private void calculateStableBound(float scale, int horizontalSlack) {
1159 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001160
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001161 // The width and height of the box in number of view pixels
1162 int w = widthOf(b, scale);
1163 int h = heightOf(b, scale);
1164
1165 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001166 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1167 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1168 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1169 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001170
1171 // If the scaled height is smaller than the view height,
1172 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001173 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001174 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001175 }
1176
1177 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001178 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001179 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001180 }
1181 }
1182
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001183 private void calculateStableBound(float scale) {
1184 calculateStableBound(scale, 0);
1185 }
1186
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001187 private boolean viewTallerThanScaledImage(float scale) {
1188 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001189 }
1190
1191 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001192 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001193 }
1194
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001195 private float getTargetScale(Box b) {
1196 return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001197 }
1198
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001199 private int getTargetX(Platform p) {
1200 return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
Chih-Chung Changec412542011-09-26 17:34:06 +08001201 }
1202
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001203 private int getTargetY(Box b) {
1204 return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
Chih-Chung Changec412542011-09-26 17:34:06 +08001205 }
1206
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001207 private boolean useCurrentValueAsTarget(Animatable a) {
1208 return a.mAnimationStartTime == NO_ANIMATION ||
1209 a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1210 a.mAnimationKind == ANIM_KIND_FLING;
Chih-Chung Changec412542011-09-26 17:34:06 +08001211 }
1212
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001213 ////////////////////////////////////////////////////////////////////////////
1214 // Animatable: an thing which can do animation.
1215 ////////////////////////////////////////////////////////////////////////////
1216 private abstract static class Animatable {
1217 public long mAnimationStartTime;
1218 public int mAnimationKind;
1219 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001220
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001221 // This should be overidden in subclass to change the animation values
1222 // give the progress value in [0, 1].
1223 protected abstract boolean interpolate(float progress);
1224 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001225
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001226 // Returns true if the animation values changes, so things need to be
1227 // redrawn.
1228 public boolean advanceAnimation() {
1229 if (mAnimationStartTime == NO_ANIMATION) {
1230 return false;
1231 }
1232 if (mAnimationStartTime == LAST_ANIMATION) {
1233 mAnimationStartTime = NO_ANIMATION;
1234 return startSnapback();
1235 }
1236
1237 float progress;
1238 if (mAnimationDuration == 0) {
1239 progress = 1;
1240 } else {
1241 long now = AnimationTime.get();
1242 progress =
1243 (float) (now - mAnimationStartTime) / mAnimationDuration;
1244 }
1245
1246 if (progress >= 1) {
1247 progress = 1;
1248 } else {
1249 progress = applyInterpolationCurve(mAnimationKind, progress);
1250 }
1251
1252 boolean done = interpolate(progress);
1253
1254 if (done) {
1255 mAnimationStartTime = LAST_ANIMATION;
1256 }
1257
1258 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001259 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001260
1261 private static float applyInterpolationCurve(int kind, float progress) {
1262 float f = 1 - progress;
1263 switch (kind) {
1264 case ANIM_KIND_SCROLL:
1265 case ANIM_KIND_FLING:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001266 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001267 progress = 1 - f; // linear
1268 break;
1269 case ANIM_KIND_SCALE:
1270 progress = 1 - f * f; // quadratic
1271 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001272 case ANIM_KIND_OPENING:
1273 progress = 1 - f * f * f; // x^3
1274 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001275 case ANIM_KIND_SNAPBACK:
1276 case ANIM_KIND_ZOOM:
1277 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001278 progress = 1 - f * f * f * f * f; // x^5
1279 break;
1280 }
1281 return progress;
1282 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001283 }
1284
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001285 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001286 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001287 ////////////////////////////////////////////////////////////////////////////
1288 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001289 public int mCurrentX, mFromX, mToX, mDefaultX;
1290 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001291 public int mFlingOffset;
1292
1293 @Override
1294 public boolean startSnapback() {
1295 if (mAnimationStartTime != NO_ANIMATION) return false;
1296 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001297 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001298
1299 Box b = mBoxes.get(0);
1300 float scaleMin = mExtraScalingRange ?
1301 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1302 float scaleMax = mExtraScalingRange ?
1303 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1304 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1305 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001306 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001307 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001308 int defaultX = mDefaultX;
1309 if (!mHasNext) x = Math.max(x, defaultX);
1310 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001311 } else {
1312 calculateStableBound(scale, HORIZONTAL_SLACK);
1313 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1314 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001315 if (mCurrentX != x || mCurrentY != y) {
1316 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001317 }
1318 return false;
1319 }
1320
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001321 // The updateDefaultXY() should be called whenever these variables
1322 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1323 // mFilmMode
1324 public void updateDefaultXY() {
1325 // We don't check mFilmMode and return 0 for mDefaultX. Because
1326 // otherwise if we decide to leave film mode because we are
1327 // centered, we will immediately back into film mode because we find
1328 // we are not centered.
1329 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1330 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1331 mDefaultY = mFilmMode ? 0 :
1332 mConstrainedFrame.centerY() - mViewH / 2;
1333 } else {
1334 mDefaultX = 0;
1335 mDefaultY = 0;
1336 }
1337 }
1338
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001339 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001340 private boolean doAnimation(int targetX, int targetY, int kind) {
1341 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001342 mAnimationKind = kind;
1343 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001344 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001345 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001346 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001347 mAnimationStartTime = AnimationTime.startTime();
1348 mAnimationDuration = ANIM_TIME[kind];
1349 mFlingOffset = 0;
1350 advanceAnimation();
1351 return true;
1352 }
1353
1354 @Override
1355 protected boolean interpolate(float progress) {
1356 if (mAnimationKind == ANIM_KIND_FLING) {
1357 return mFilmMode
1358 ? interpolateFlingFilm(progress)
1359 : interpolateFlingPage(progress);
1360 } else {
1361 return interpolateLinear(progress);
1362 }
1363 }
1364
1365 private boolean interpolateFlingFilm(float progress) {
1366 mFilmScroller.computeScrollOffset();
1367 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1368
1369 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001370 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001371 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001372 dir = EdgeView.RIGHT;
1373 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001374 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001375 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001376 dir = EdgeView.LEFT;
1377 }
1378 }
1379 if (dir != EdgeView.INVALID_DIRECTION) {
1380 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1381 mListener.onAbsorb(v, dir);
1382 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001383 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001384 }
1385 return mFilmScroller.isFinished();
1386 }
1387
1388 private boolean interpolateFlingPage(float progress) {
1389 mPageScroller.computeScrollOffset(progress);
1390 Box b = mBoxes.get(0);
1391 calculateStableBound(b.mCurrentScale);
1392
1393 int oldX = mCurrentX;
1394 mCurrentX = mPageScroller.getCurrX();
1395
1396 // Check if we hit the edges; show edge effects if we do.
1397 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1398 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1399 mListener.onAbsorb(v, EdgeView.RIGHT);
1400 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1401 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1402 mListener.onAbsorb(v, EdgeView.LEFT);
1403 }
1404
1405 return progress >= 1;
1406 }
1407
1408 private boolean interpolateLinear(float progress) {
1409 // Other animations
1410 if (progress >= 1) {
1411 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001412 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001413 return true;
1414 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001415 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1416 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001417 }
1418 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1419 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1420 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001421 return false;
1422 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001423 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001424 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001425 }
1426 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001427 }
1428
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001429 ////////////////////////////////////////////////////////////////////////////
1430 // Box: represents a rectangular area which shows a picture.
1431 ////////////////////////////////////////////////////////////////////////////
1432 private class Box extends Animatable {
1433 // Size of the bitmap
1434 public int mImageW, mImageH;
1435
1436 // This is true if we assume the image size is the same as view size
1437 // until we know the actual size of image. This is also used to
1438 // determine if there is an image ready to show.
1439 public boolean mUseViewSize;
1440
1441 // The minimum and maximum scale we allow for this box.
1442 public float mScaleMin, mScaleMax;
1443
1444 // The X/Y value indicates where the center of the box is on the view
1445 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1446 // actual values used currently. Note that the X values are implicitly
1447 // defined by Platform and Gaps.
1448 public int mCurrentY, mFromY, mToY;
1449 public float mCurrentScale, mFromScale, mToScale;
1450
1451 // The absolute X coordinate of the center of the box. This is only used
1452 // during moveBox().
1453 public int mAbsoluteX;
1454
1455 @Override
1456 public boolean startSnapback() {
1457 if (mAnimationStartTime != NO_ANIMATION) return false;
1458 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001459 && mListener.isHolding()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001460 if (mInScale && this == mBoxes.get(0)) return false;
1461
1462 int y;
1463 float scale;
1464
1465 if (this == mBoxes.get(0)) {
1466 float scaleMin = mExtraScalingRange ?
1467 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1468 float scaleMax = mExtraScalingRange ?
1469 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1470 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1471 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001472 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001473 } else {
1474 calculateStableBound(scale, HORIZONTAL_SLACK);
1475 y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1476 }
1477 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001478 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001479 scale = mScaleMin;
1480 }
1481
1482 if (mCurrentY != y || mCurrentScale != scale) {
1483 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1484 }
1485 return false;
1486 }
1487
1488 private boolean doAnimation(int targetY, float targetScale, int kind) {
1489 targetScale = Utils.clamp(targetScale,
1490 SCALE_MIN_EXTRA * mScaleMin,
1491 SCALE_MAX_EXTRA * mScaleMax);
1492
1493 // If the scaled height is smaller than the view height, force it to be
1494 // in the center. (We do this for height only, not width, because the
1495 // user may want to scroll to the previous/next image.)
1496 if (!mInScale && viewTallerThanScaledImage(targetScale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001497 targetY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001498 }
1499
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001500 if (mCurrentY == targetY && mCurrentScale == targetScale
1501 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001502 return false;
1503 }
1504
1505 // Now starts an animation for the box.
1506 mAnimationKind = kind;
1507 mFromY = mCurrentY;
1508 mFromScale = mCurrentScale;
1509 mToY = targetY;
1510 mToScale = targetScale;
1511 mAnimationStartTime = AnimationTime.startTime();
1512 mAnimationDuration = ANIM_TIME[kind];
1513 advanceAnimation();
1514 return true;
1515 }
1516
1517 @Override
1518 protected boolean interpolate(float progress) {
1519 if (mAnimationKind == ANIM_KIND_FLING) {
1520 // Currently a Box can only be flung in page mode.
1521 return interpolateFlingPage(progress);
1522 } else {
1523 return interpolateLinear(progress);
1524 }
1525 }
1526
1527 private boolean interpolateFlingPage(float progress) {
1528 mPageScroller.computeScrollOffset(progress);
1529 calculateStableBound(mCurrentScale);
1530
1531 int oldY = mCurrentY;
1532 mCurrentY = mPageScroller.getCurrY();
1533
1534 // Check if we hit the edges; show edge effects if we do.
1535 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1536 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1537 mListener.onAbsorb(v, EdgeView.BOTTOM);
1538 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1539 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1540 mListener.onAbsorb(v, EdgeView.TOP);
1541 }
1542
1543 return progress >= 1;
1544 }
1545
1546 private boolean interpolateLinear(float progress) {
1547 if (progress >= 1) {
1548 mCurrentY = mToY;
1549 mCurrentScale = mToScale;
1550 return true;
1551 } else {
1552 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1553 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001554 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1555 float f = CaptureAnimation.calculateScale(progress);
1556 mCurrentScale *= f;
1557 return false;
1558 } else {
1559 return (mCurrentY == mToY && mCurrentScale == mToScale);
1560 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001561 }
1562 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001563 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001564
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001565 ////////////////////////////////////////////////////////////////////////////
1566 // Gap: represents a rectangular area which is between two boxes.
1567 ////////////////////////////////////////////////////////////////////////////
1568 private class Gap extends Animatable {
1569 // The default gap size between two boxes. The value may vary for
1570 // different image size of the boxes and for different modes (page or
1571 // film).
1572 public int mDefaultSize;
1573
1574 // The gap size between the two boxes.
1575 public int mCurrentGap, mFromGap, mToGap;
1576
1577 @Override
1578 public boolean startSnapback() {
1579 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001580 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001581 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001582
1583 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001584 public boolean doAnimation(int targetSize, int kind) {
1585 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1586 return false;
1587 }
1588 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001589 mFromGap = mCurrentGap;
1590 mToGap = targetSize;
1591 mAnimationStartTime = AnimationTime.startTime();
1592 mAnimationDuration = ANIM_TIME[mAnimationKind];
1593 advanceAnimation();
1594 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001595 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001596
1597 @Override
1598 protected boolean interpolate(float progress) {
1599 if (progress >= 1) {
1600 mCurrentGap = mToGap;
1601 return true;
1602 } else {
1603 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001604 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1605 float f = CaptureAnimation.calculateScale(progress);
1606 mCurrentGap = (int) (mCurrentGap * f);
1607 return false;
1608 } else {
1609 return (mCurrentGap == mToGap);
1610 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001611 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001612 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001613 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001614
1615 ////////////////////////////////////////////////////////////////////////////
1616 // FilmRatio: represents the progress of film mode change.
1617 ////////////////////////////////////////////////////////////////////////////
1618 private class FilmRatio extends Animatable {
1619 // The film ratio: 1 means switching to film mode is complete, 0 means
1620 // switching to page mode is complete.
1621 public float mCurrentRatio, mFromRatio, mToRatio;
1622
1623 @Override
1624 public boolean startSnapback() {
1625 float target = mFilmMode ? 1f : 0f;
1626 if (target == mToRatio) return false;
1627 return doAnimation(target, ANIM_KIND_SNAPBACK);
1628 }
1629
1630 // Starts an animation for the film ratio.
1631 private boolean doAnimation(float targetRatio, int kind) {
1632 mAnimationKind = kind;
1633 mFromRatio = mCurrentRatio;
1634 mToRatio = targetRatio;
1635 mAnimationStartTime = AnimationTime.startTime();
1636 mAnimationDuration = ANIM_TIME[mAnimationKind];
1637 advanceAnimation();
1638 return true;
1639 }
1640
1641 @Override
1642 protected boolean interpolate(float progress) {
1643 if (progress >= 1) {
1644 mCurrentRatio = mToRatio;
1645 return true;
1646 } else {
1647 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1648 return (mCurrentRatio == mToRatio);
1649 }
1650 }
1651 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001652}