blob: d556cafdc287fe5af2f2bf31fd65a4046b3b83e9 [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 Chang6b891c62012-06-07 20:09:13 +080028import com.android.gallery3d.ui.PhotoView.Size;
Chih-Chung Changec412542011-09-26 17:34:06 +080029
Chih-Chung Changec412542011-09-26 17:34:06 +080030class PositionController {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080031 private static final String TAG = "PositionController";
32
Yuli Huang2ce3c3b2012-02-23 22:26:12 +080033 public static final int IMAGE_AT_LEFT_EDGE = 1;
34 public static final int IMAGE_AT_RIGHT_EDGE = 2;
35 public static final int IMAGE_AT_TOP_EDGE = 4;
36 public static final int IMAGE_AT_BOTTOM_EDGE = 8;
37
Chih-Chung Changc4791b72012-05-18 19:10:36 -070038 public static final int CAPTURE_ANIMATION_TIME = 700;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080039 public static final int SNAPBACK_ANIMATION_TIME = 600;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080040
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080041 // Special values for animation time.
Chih-Chung Changec412542011-09-26 17:34:06 +080042 private static final long NO_ANIMATION = -1;
43 private static final long LAST_ANIMATION = -2;
44
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080045 private static final int ANIM_KIND_NONE = -1;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080046 private static final int ANIM_KIND_SCROLL = 0;
47 private static final int ANIM_KIND_SCALE = 1;
48 private static final int ANIM_KIND_SNAPBACK = 2;
49 private static final int ANIM_KIND_SLIDE = 3;
50 private static final int ANIM_KIND_ZOOM = 4;
51 private static final int ANIM_KIND_OPENING = 5;
52 private static final int ANIM_KIND_FLING = 6;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080053 private static final int ANIM_KIND_FLING_X = 7;
54 private static final int ANIM_KIND_DELETE = 8;
55 private static final int ANIM_KIND_CAPTURE = 9;
Chih-Chung Changec412542011-09-26 17:34:06 +080056
Chih-Chung Chang676170e2011-09-30 18:33:17 +080057 // Animation time in milliseconds. The order must match ANIM_KIND_* above.
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080058 //
59 // The values for ANIM_KIND_FLING_X does't matter because we use
60 // mFilmScroller.isFinished() to decide when to stop. We set it to 0 so it's
61 // faster for Animatable.advanceAnimation() to calculate the progress
62 // (always 1).
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080063 private static final int ANIM_TIME[] = {
Chih-Chung Chang676170e2011-09-30 18:33:17 +080064 0, // ANIM_KIND_SCROLL
Chih-Chung Chang2ce59cb2012-06-18 17:24:58 +080065 0, // ANIM_KIND_SCALE
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080066 SNAPBACK_ANIMATION_TIME, // ANIM_KIND_SNAPBACK
Chih-Chung Chang676170e2011-09-30 18:33:17 +080067 400, // ANIM_KIND_SLIDE
68 300, // ANIM_KIND_ZOOM
Yuli Huang5338d192012-05-17 11:32:15 +080069 400, // ANIM_KIND_OPENING
Chih-Chung Changb3aab902011-10-03 21:11:39 +080070 0, // ANIM_KIND_FLING (the duration is calculated dynamically)
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080071 0, // ANIM_KIND_FLING_X (see the comment above)
72 0, // ANIM_KIND_DELETE (the duration is calculated dynamically)
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080073 CAPTURE_ANIMATION_TIME, // ANIM_KIND_CAPTURE
Chih-Chung Chang676170e2011-09-30 18:33:17 +080074 };
75
Chih-Chung Changec412542011-09-26 17:34:06 +080076 // We try to scale up the image to fill the screen. But in order not to
77 // scale too much for small icons, we limit the max up-scaling factor here.
78 private static final float SCALE_LIMIT = 4;
79
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080080 // For user's gestures, we give a temporary extra scaling range which goes
81 // above or below the usual scaling limits.
82 private static final float SCALE_MIN_EXTRA = 0.7f;
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080083 private static final float SCALE_MAX_EXTRA = 1.4f;
84
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080085 // Setting this true makes the extra scaling range permanent (until this is
86 // set to false again).
Chih-Chung Chang534b12f2012-03-21 19:01:30 +080087 private boolean mExtraScalingRange = false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +080088
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080089 // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
90 private boolean mFilmMode = false;
Chih-Chung Changb3aab902011-10-03 21:11:39 +080091
Chih-Chung Chang642561d2012-04-16 16:29:13 +080092 // These are the limits for width / height of the picture in film mode.
93 private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
94 private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
95 private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
96 private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080097
98 // In addition to the focused box (index == 0). We also keep information
99 // about this many boxes on each side.
100 private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800101 private static final int[] CENTER_OUT_INDEX = new int[2 * BOX_MAX + 1];
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800102
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800103 private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800104 private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
105
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800106 // These are constants for the delete gesture.
107 private static final int DEFAULT_DELETE_ANIMATION_DURATION = 200; // ms
108 private static final int MAX_DELETE_ANIMATION_DURATION = 400; // ms
109
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800110 private Listener mListener;
111 private volatile Rect mOpenAnimationRect;
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800112
113 // Use a large enough value, so we won't see the gray shadown in the beginning.
114 private int mViewW = 1200;
115 private int mViewH = 1200;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800116
117 // A scaling guesture is in progress.
118 private boolean mInScale;
119 // The focus point of the scaling gesture, relative to the center of the
120 // picture in bitmap pixels.
121 private float mFocusX, mFocusY;
122
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800123 // whether there is a previous/next picture.
124 private boolean mHasPrev, mHasNext;
125
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800126 // This is used by the fling animation (page mode).
127 private FlingScroller mPageScroller;
128
129 // This is used by the fling animation (film mode).
Chih-Chung Changd8488622012-04-17 12:56:08 +0800130 private OverScroller mFilmScroller;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800131
132 // The bound of the stable region that the focused box can stay, see the
133 // comments above calculateStableBound() for details.
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800134 private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
135
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800136 // Constrained frame is a rectangle that the focused box should fit into if
137 // it is constrained. It has two effects:
138 //
139 // (1) In page mode, if the focused box is constrained, scaling for the
140 // focused box is adjusted to fit into the constrained frame, instead of the
141 // whole view.
142 //
143 // (2) In page mode, if the focused box is constrained, the mPlatform's
144 // default center (mDefaultX/Y) is moved to the center of the constrained
145 // frame, instead of the view center.
146 //
147 private Rect mConstrainedFrame = new Rect();
148
149 // Whether the focused box is constrained.
150 //
151 // Our current program's first call to moveBox() sets constrained = true, so
152 // we set the initial value of this variable to true, and we will not see
153 // see unwanted transition animation.
154 private boolean mConstrained = true;
155
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800156 //
157 // ___________________________________________________________
158 // | _____ _____ _____ _____ _____ |
159 // | | | | | | | | | | | |
160 // | | Box | | Box | | Box*| | Box | | Box | |
161 // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
162 // | Gap Gap Gap Gap |
163 // |___________________________________________________________|
164 //
165 // <-- Platform -->
166 //
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800167 // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
Chih-Chung Changec412542011-09-26 17:34:06 +0800168
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800169 private Platform mPlatform = new Platform();
170 private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
171 // The gap at the right of a Box i is at index i. The gap at the left of a
172 // Box i is at index i - 1.
173 private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800174 private FilmRatio mFilmRatio = new FilmRatio();
Chih-Chung Changec412542011-09-26 17:34:06 +0800175
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800176 // These are only used during moveBox().
177 private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800178 private RangeArray<Gap> mTempGaps =
179 new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800180
181 // The output of the PositionController. Available throught getPosition().
182 private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
183
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800184 // The direction of a new picture should appear. New pictures pop from top
185 // if this value is true, or from bottom if this value is false.
186 boolean mPopFromTop;
187
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800188 public interface Listener {
189 void invalidate();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800190 boolean isHoldingDown();
191 boolean isHoldingDelete();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800192
193 // EdgeView
194 void onPull(int offset, int direction);
195 void onRelease();
196 void onAbsorb(int velocity, int direction);
Chih-Chung Changec412542011-09-26 17:34:06 +0800197 }
198
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800199 static {
200 // Initialize the CENTER_OUT_INDEX array.
201 // The array maps 0, 1, 2, 3, 4, ..., 2 * BOX_MAX
202 // to 0, 1, -1, 2, -2, ..., BOX_MAX, -BOX_MAX
203 for (int i = 0; i < CENTER_OUT_INDEX.length; i++) {
204 int j = (i + 1) / 2;
205 if ((i & 1) == 0) j = -j;
206 CENTER_OUT_INDEX[i] = j;
207 }
208 }
209
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800210 public PositionController(Context context, Listener listener) {
211 mListener = listener;
212 mPageScroller = new FlingScroller();
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800213 mFilmScroller = new OverScroller(context,
214 null /* default interpolator */, false /* no flywheel */);
Chih-Chung Changec412542011-09-26 17:34:06 +0800215
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800216 // Initialize the areas.
217 initPlatform();
218 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
219 mBoxes.put(i, new Box());
220 initBox(i);
221 mRects.put(i, new Rect());
222 }
223 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
224 mGaps.put(i, new Gap());
225 initGap(i);
226 }
227 }
228
229 public void setOpenAnimationRect(Rect r) {
230 mOpenAnimationRect = r;
231 }
232
233 public void setViewSize(int viewW, int viewH) {
234 if (viewW == mViewW && viewH == mViewH) return;
235
Chih-Chung Changf3f90752012-05-30 17:10:32 -0700236 boolean wasMinimal = isAtMinimalScale();
237
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800238 mViewW = viewW;
239 mViewH = viewH;
240 initPlatform();
241
242 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
243 setBoxSize(i, viewW, viewH, true);
244 }
245
246 updateScaleAndGapLimit();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700247
Chih-Chung Changf3f90752012-05-30 17:10:32 -0700248 // If the focused box was at minimal scale, we try to make it the
249 // minimal scale under the new view size.
250 if (wasMinimal) {
251 Box b = mBoxes.get(0);
252 b.mCurrentScale = b.mScaleMin;
253 }
254
Chih-Chung Change6251df2012-05-22 11:35:46 -0700255 // If we have the opening animation, do it. Otherwise go directly to the
256 // right position.
257 if (!startOpeningAnimationIfNeeded()) {
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700258 skipToFinalPosition();
Chih-Chung Change6251df2012-05-22 11:35:46 -0700259 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800260 }
261
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700262 public void setConstrainedFrame(Rect cFrame) {
263 if (mConstrainedFrame.equals(cFrame)) return;
264 mConstrainedFrame.set(cFrame);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800265 mPlatform.updateDefaultXY();
266 updateScaleAndGapLimit();
267 snapAndRedraw();
268 }
269
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800270 public void forceImageSize(int index, Size s) {
271 if (s.width == 0 || s.height == 0) return;
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700272 Box b = mBoxes.get(index);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800273 b.mImageW = s.width;
274 b.mImageH = s.height;
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700275 return;
276 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800277
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800278 public void setImageSize(int index, Size s, Rect cFrame) {
279 if (s.width == 0 || s.height == 0) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800280
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700281 boolean needUpdate = false;
282 if (cFrame != null && !mConstrainedFrame.equals(cFrame)) {
283 mConstrainedFrame.set(cFrame);
284 mPlatform.updateDefaultXY();
285 needUpdate = true;
286 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800287 needUpdate |= setBoxSize(index, s.width, s.height, false);
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700288
289 if (!needUpdate) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800290 updateScaleAndGapLimit();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800291 snapAndRedraw();
292 }
293
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800294 // Returns false if the box size doesn't change.
295 private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800296 Box b = mBoxes.get(i);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800297 boolean wasViewSize = b.mUseViewSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800298
Chih-Chung Changd8488622012-04-17 12:56:08 +0800299 // If we already have an image size, we don't want to use the view size.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800300 if (!wasViewSize && isViewSize) return false;
Chih-Chung Changd8488622012-04-17 12:56:08 +0800301
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800302 b.mUseViewSize = isViewSize;
303
304 if (width == b.mImageW && height == b.mImageH) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800305 return false;
Chih-Chung Changec412542011-09-26 17:34:06 +0800306 }
307
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800308 // The ratio of the old size and the new size.
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700309 //
310 // If the aspect ratio changes, we don't know if it is because one side
311 // grows or the other side shrinks. Currently we just assume the view
312 // angle of the longer side doesn't change (so the aspect ratio change
313 // is because the view angle of the shorter side changes). This matches
314 // what camera preview does.
315 float ratio = (width > height)
316 ? (float) b.mImageW / width
317 : (float) b.mImageH / height;
Chih-Chung Changec412542011-09-26 17:34:06 +0800318
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800319 b.mImageW = width;
320 b.mImageH = height;
321
Chih-Chung Changd8488622012-04-17 12:56:08 +0800322 // If this is the first time we receive an image size, we change the
323 // scale directly. Otherwise adjust the scales by a ratio, and snapback
324 // will animate the scale into the min/max bounds if necessary.
325 if (wasViewSize && !isViewSize) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800326 b.mCurrentScale = getMinimalScale(b);
Chih-Chung Changd8488622012-04-17 12:56:08 +0800327 b.mAnimationStartTime = NO_ANIMATION;
328 } else {
329 b.mCurrentScale *= ratio;
330 b.mFromScale *= ratio;
331 b.mToScale *= ratio;
332 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800333
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800334 if (i == 0) {
335 mFocusX /= ratio;
336 mFocusY /= ratio;
Chih-Chung Changec412542011-09-26 17:34:06 +0800337 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800338
339 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +0800340 }
341
Chih-Chung Change6251df2012-05-22 11:35:46 -0700342 private boolean startOpeningAnimationIfNeeded() {
343 if (mOpenAnimationRect == null) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800344 Box b = mBoxes.get(0);
Chih-Chung Change6251df2012-05-22 11:35:46 -0700345 if (b.mUseViewSize) return false;
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800346
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800347 // Start animation from the saved rectangle if we have one.
348 Rect r = mOpenAnimationRect;
349 mOpenAnimationRect = null;
Yuli Huangf320b842012-05-16 01:38:06 +0800350
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800351 mPlatform.mCurrentX = r.centerX() - mViewW / 2;
352 b.mCurrentY = r.centerY() - mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800353 b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
354 r.height() / (float) b.mImageH);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800355 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
356 ANIM_KIND_OPENING);
Yuli Huangf320b842012-05-16 01:38:06 +0800357
358 // Animate from large gaps for neighbor boxes to avoid them
359 // shown on the screen during opening animation.
360 for (int i = -1; i < 1; i++) {
361 Gap g = mGaps.get(i);
362 g.mCurrentGap = mViewW;
363 g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
364 }
Chih-Chung Change6251df2012-05-22 11:35:46 -0700365
366 return true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800367 }
368
369 public void setFilmMode(boolean enabled) {
370 if (enabled == mFilmMode) return;
371 mFilmMode = enabled;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800372
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800373 mPlatform.updateDefaultXY();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800374 updateScaleAndGapLimit();
375 stopAnimation();
376 snapAndRedraw();
377 }
378
379 public void setExtraScalingRange(boolean enabled) {
380 if (mExtraScalingRange == enabled) return;
381 mExtraScalingRange = enabled;
382 if (!enabled) {
383 snapAndRedraw();
384 }
385 }
386
387 // This should be called whenever the scale range of boxes or the default
388 // gap size may change. Currently this can happen due to change of view
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800389 // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800390 private void updateScaleAndGapLimit() {
391 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
392 Box b = mBoxes.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800393 b.mScaleMin = getMinimalScale(b);
394 b.mScaleMax = getMaximalScale(b);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800395 }
396
397 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
398 Gap g = mGaps.get(i);
399 g.mDefaultSize = getDefaultGapSize(i);
400 }
401 }
402
403 // Returns the default gap size according the the size of the boxes around
404 // the gap and the current mode.
405 private int getDefaultGapSize(int i) {
406 if (mFilmMode) return IMAGE_GAP;
407 Box a = mBoxes.get(i);
408 Box b = mBoxes.get(i + 1);
409 return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
410 }
411
412 // Here is how we layout the boxes in the page mode.
413 //
414 // previous current next
415 // ___________ ________________ __________
416 // | _______ | | __________ | | ______ |
417 // | | | | | | right->| | | | | |
418 // | | |<-------->|<--left | | | | | |
419 // | |_______| | | | |__________| | | |______| |
420 // |___________| | |________________| |__________|
421 // | <--> gapToSide()
422 // |
423 // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
424 private int gapToSide(Box b) {
425 return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
426 }
427
428 // Stop all animations at where they are now.
429 public void stopAnimation() {
430 mPlatform.mAnimationStartTime = NO_ANIMATION;
431 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
432 mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
433 }
434 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
435 mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
436 }
437 }
438
439 public void skipAnimation() {
440 if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
441 mPlatform.mCurrentX = mPlatform.mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800442 mPlatform.mCurrentY = mPlatform.mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800443 mPlatform.mAnimationStartTime = NO_ANIMATION;
444 }
445 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
446 Box b = mBoxes.get(i);
447 if (b.mAnimationStartTime == NO_ANIMATION) continue;
448 b.mCurrentY = b.mToY;
449 b.mCurrentScale = b.mToScale;
450 b.mAnimationStartTime = NO_ANIMATION;
451 }
452 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
453 Gap g = mGaps.get(i);
454 if (g.mAnimationStartTime == NO_ANIMATION) continue;
455 g.mCurrentGap = g.mToGap;
456 g.mAnimationStartTime = NO_ANIMATION;
457 }
458 redraw();
459 }
460
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800461 public void snapback() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800462 snapAndRedraw();
463 }
464
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -0700465 public void skipToFinalPosition() {
466 stopAnimation();
467 snapAndRedraw();
468 skipAnimation();
469 }
470
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800471 ////////////////////////////////////////////////////////////////////////////
472 // Start an animations for the focused box
473 ////////////////////////////////////////////////////////////////////////////
474
475 public void zoomIn(float tapX, float tapY, float targetScale) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800476 tapX -= mViewW / 2;
477 tapY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800478 Box b = mBoxes.get(0);
479
480 // Convert the tap position to distance to center in bitmap coordinates
481 float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
482 float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
483
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800484 int x = (int) (-tempX * targetScale + 0.5f);
485 int y = (int) (-tempY * targetScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800486
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800487 calculateStableBound(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800488 int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
489 int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
490 targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
Chih-Chung Changec412542011-09-26 17:34:06 +0800491
492 startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
493 }
494
495 public void resetToFullView() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800496 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800497 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
Chih-Chung Changec412542011-09-26 17:34:06 +0800498 }
499
Chih-Chung Changec412542011-09-26 17:34:06 +0800500 public void beginScale(float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800501 focusX -= mViewW / 2;
502 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800503 Box b = mBoxes.get(0);
504 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800505 mInScale = true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800506 mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
507 mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
Chih-Chung Changec412542011-09-26 17:34:06 +0800508 }
509
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800510 // Scales the image by the given factor.
511 // Returns an out-of-range indicator:
512 // 1 if the intended scale is too large for the stable range.
513 // 0 if the intended scale is in the stable range.
514 // -1 if the intended scale is too small for the stable range.
515 public int scaleBy(float s, float focusX, float focusY) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800516 focusX -= mViewW / 2;
517 focusY -= mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800518 Box b = mBoxes.get(0);
519 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800520
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800521 // We want to keep the focus point (on the bitmap) the same as when we
522 // begin the scale guesture, that is,
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800523 //
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800524 // (focusX' - currentX') / scale' = (focusX - currentX) / scale
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800525 //
Chih-Chung Chang192b7f12012-05-30 15:47:44 -0700526 s = b.clampScale(s * getTargetScale(b));
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800527 int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
528 int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
Chih-Chung Chang676170e2011-09-30 18:33:17 +0800529 startAnimation(x, y, s, ANIM_KIND_SCALE);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800530 if (s < b.mScaleMin) return -1;
531 if (s > b.mScaleMax) return 1;
532 return 0;
Chih-Chung Changec412542011-09-26 17:34:06 +0800533 }
534
535 public void endScale() {
536 mInScale = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800537 snapAndRedraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800538 }
539
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800540 // Slide the focused box to the center of the view.
541 public void startHorizontalSlide() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800542 Box b = mBoxes.get(0);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800543 startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800544 }
545
546 // Slide the focused box to the center of the view with the capture
547 // animation. In addition to the sliding, the animation will also scale the
548 // the focused box, the specified neighbor box, and the gap between the
549 // two. The specified offset should be 1 or -1.
550 public void startCaptureAnimationSlide(int offset) {
551 Box b = mBoxes.get(0);
552 Box n = mBoxes.get(offset); // the neighbor box
553 Gap g = mGaps.get(offset); // the gap between the two boxes
554
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800555 mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
556 ANIM_KIND_CAPTURE);
557 b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
558 n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800559 g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
560 redraw();
Chih-Chung Changec412542011-09-26 17:34:06 +0800561 }
562
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800563 // Only allow scrolling when we are not currently in an animation or we
564 // are in some animation with can be interrupted.
565 private boolean canScroll() {
566 Box b = mBoxes.get(0);
567 if (b.mAnimationStartTime == NO_ANIMATION) return true;
568 switch (b.mAnimationKind) {
569 case ANIM_KIND_SCROLL:
570 case ANIM_KIND_FLING:
571 case ANIM_KIND_FLING_X:
572 return true;
573 }
574 return false;
575 }
576
577 public void scrollPage(int dx, int dy) {
578 if (!canScroll()) return;
579
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800580 Box b = mBoxes.get(0);
581 Platform p = mPlatform;
582
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800583 calculateStableBound(b.mCurrentScale);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800584
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800585 int x = p.mCurrentX + dx;
586 int y = b.mCurrentY + dy;
587
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800588 // Vertical direction: If we have space to move in the vertical
589 // direction, we show the edge effect when scrolling reaches the edge.
590 if (mBoundTop != mBoundBottom) {
591 if (y < mBoundTop) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800592 mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800593 } else if (y > mBoundBottom) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800594 mListener.onPull(y - mBoundBottom, EdgeView.TOP);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800595 }
596 }
597
598 y = Utils.clamp(y, mBoundTop, mBoundBottom);
599
600 // Horizontal direction: we show the edge effect when the scrolling
601 // tries to go left of the first image or go right of the last image.
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800602 if (!mHasPrev && x > mBoundRight) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800603 int pixels = x - mBoundRight;
604 mListener.onPull(pixels, EdgeView.LEFT);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800605 x = mBoundRight;
Chih-Chung Changfb1a1552012-04-19 13:34:48 +0800606 } else if (!mHasNext && x < mBoundLeft) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800607 int pixels = mBoundLeft - x;
608 mListener.onPull(pixels, EdgeView.RIGHT);
609 x = mBoundLeft;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800610 }
611
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800612 startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
613 }
614
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800615 public void scrollFilmX(int dx) {
616 if (!canScroll()) return;
617
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800618 Box b = mBoxes.get(0);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800619 Platform p = mPlatform;
620
621 // Only allow scrolling when we are not currently in an animation or we
622 // are in some animation with can be interrupted.
623 if (b.mAnimationStartTime != NO_ANIMATION) {
624 switch (b.mAnimationKind) {
625 case ANIM_KIND_SCROLL:
626 case ANIM_KIND_FLING:
627 case ANIM_KIND_FLING_X:
628 break;
629 default:
630 return;
631 }
632 }
633
634 int x = p.mCurrentX + dx;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800635
636 // Horizontal direction: we show the edge effect when the scrolling
637 // tries to go left of the first image or go right of the last image.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800638 x -= mPlatform.mDefaultX;
639 if (!mHasPrev && x > 0) {
640 mListener.onPull(x, EdgeView.LEFT);
641 x = 0;
642 } else if (!mHasNext && x < 0) {
643 mListener.onPull(-x, EdgeView.RIGHT);
644 x = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800645 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800646 x += mPlatform.mDefaultX;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800647 startAnimation(x, b.mCurrentY, b.mCurrentScale, ANIM_KIND_SCROLL);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800648 }
649
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800650 public void scrollFilmY(int boxIndex, int dy) {
651 if (!canScroll()) return;
652
653 Box b = mBoxes.get(boxIndex);
654 int y = b.mCurrentY + dy;
655 b.doAnimation(y, b.mCurrentScale, ANIM_KIND_SCROLL);
656 redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800657 }
658
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800659 public boolean flingPage(int velocityX, int velocityY) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800660 Box b = mBoxes.get(0);
661 Platform p = mPlatform;
662
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800663 // We only want to do fling when the picture is zoomed-in.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800664 if (viewWiderThanScaledImage(b.mCurrentScale) &&
665 viewTallerThanScaledImage(b.mCurrentScale)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800666 return false;
667 }
668
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800669 // We only allow flinging in the directions where it won't go over the
670 // picture.
671 int edges = getImageAtEdges();
672 if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
673 (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
674 velocityX = 0;
675 }
676 if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
677 (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
678 velocityY = 0;
679 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +0800680
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800681 if (velocityX == 0 && velocityY == 0) return false;
682
683 mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800684 mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800685 int targetX = mPageScroller.getFinalX();
686 int targetY = mPageScroller.getFinalY();
687 ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800688 return startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
Chih-Chung Changb3aab902011-10-03 21:11:39 +0800689 }
690
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800691 public boolean flingFilmX(int velocityX) {
692 if (velocityX == 0) return false;
693
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800694 Box b = mBoxes.get(0);
695 Platform p = mPlatform;
Chih-Chung Changec412542011-09-26 17:34:06 +0800696
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800697 // If we are already at the edge, don't start the fling.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800698 int defaultX = p.mDefaultX;
699 if ((!mHasPrev && p.mCurrentX >= defaultX)
700 || (!mHasNext && p.mCurrentX <= defaultX)) {
Chih-Chung Changec412542011-09-26 17:34:06 +0800701 return false;
702 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800703
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800704 mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
705 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
706 int targetX = mFilmScroller.getFinalX();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800707 return startAnimation(
708 targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING_X);
709 }
710
711 // Moves the specified box out of screen. If velocityY is 0, a default
712 // velocity is used. Returns the time for the duration, or -1 if we cannot
713 // not do the animation.
714 public int flingFilmY(int boxIndex, int velocityY) {
715 Box b = mBoxes.get(boxIndex);
716
717 // Calculate targetY
718 int h = heightOf(b);
719 int targetY;
720 int FUZZY = 3; // TODO: figure out why this is needed.
721 if (velocityY < 0 || (velocityY == 0 && b.mCurrentY <= 0)) {
722 targetY = -mViewH / 2 - (h + 1) / 2 - FUZZY;
723 } else {
724 targetY = (mViewH + 1) / 2 + h / 2 + FUZZY;
725 }
726
727 // Calculate duration
728 int duration;
729 if (velocityY != 0) {
730 duration = (int) (Math.abs(targetY - b.mCurrentY) * 1000f
731 / Math.abs(velocityY));
732 duration = Math.min(MAX_DELETE_ANIMATION_DURATION, duration);
733 } else {
734 duration = DEFAULT_DELETE_ANIMATION_DURATION;
735 }
736
737 // Start animation
738 ANIM_TIME[ANIM_KIND_DELETE] = duration;
739 if (b.doAnimation(targetY, b.mCurrentScale, ANIM_KIND_DELETE)) {
740 redraw();
741 return duration;
742 }
743 return -1;
744 }
745
746 // Returns the index of the box which contains the given point (x, y)
747 // Returns Integer.MAX_VALUE if there is no hit. There may be more than
748 // one box contains the given point, and we want to give priority to the
749 // one closer to the focused index (0).
750 public int hitTest(int x, int y) {
751 for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
752 int j = CENTER_OUT_INDEX[i];
753 Rect r = mRects.get(j);
754 if (r.contains(x, y)) {
755 return j;
756 }
757 }
758
759 return Integer.MAX_VALUE;
Chih-Chung Changec412542011-09-26 17:34:06 +0800760 }
761
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800762 ////////////////////////////////////////////////////////////////////////////
763 // Redraw
764 //
765 // If a method changes box positions directly, redraw()
766 // should be called.
767 //
768 // If a method may also cause a snapback to happen, snapAndRedraw() should
769 // be called.
770 //
771 // If a method starts an animation to change the position of focused box,
772 // startAnimation() should be called.
773 //
774 // If time advances to change the box position, advanceAnimation() should
775 // be called.
776 ////////////////////////////////////////////////////////////////////////////
777 private void redraw() {
778 layoutAndSetPosition();
779 mListener.invalidate();
780 }
Chih-Chung Changec412542011-09-26 17:34:06 +0800781
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800782 private void snapAndRedraw() {
783 mPlatform.startSnapback();
784 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
785 mBoxes.get(i).startSnapback();
786 }
787 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
788 mGaps.get(i).startSnapback();
789 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800790 mFilmRatio.startSnapback();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800791 redraw();
792 }
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800793
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800794 private boolean startAnimation(int targetX, int targetY, float targetScale,
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800795 int kind) {
796 boolean changed = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800797 changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800798 changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
799 if (changed) redraw();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800800 return changed;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800801 }
802
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800803 public void advanceAnimation() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800804 boolean changed = false;
805 changed |= mPlatform.advanceAnimation();
806 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
807 changed |= mBoxes.get(i).advanceAnimation();
808 }
809 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
810 changed |= mGaps.get(i).advanceAnimation();
811 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800812 changed |= mFilmRatio.advanceAnimation();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800813 if (changed) redraw();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800814 }
815
Yuli Huangf320b842012-05-16 01:38:06 +0800816 public boolean inOpeningAnimation() {
817 return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
818 mPlatform.mAnimationStartTime != NO_ANIMATION) ||
819 (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
820 mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
821 }
822
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800823 ////////////////////////////////////////////////////////////////////////////
824 // Layout
825 ////////////////////////////////////////////////////////////////////////////
826
827 // Returns the display width of this box.
828 private int widthOf(Box b) {
829 return (int) (b.mImageW * b.mCurrentScale + 0.5f);
830 }
831
832 // Returns the display height of this box.
833 private int heightOf(Box b) {
834 return (int) (b.mImageH * b.mCurrentScale + 0.5f);
835 }
836
837 // Returns the display width of this box, using the given scale.
838 private int widthOf(Box b, float scale) {
839 return (int) (b.mImageW * scale + 0.5f);
840 }
841
842 // Returns the display height of this box, using the given scale.
843 private int heightOf(Box b, float scale) {
844 return (int) (b.mImageH * scale + 0.5f);
845 }
846
847 // Convert the information in mPlatform and mBoxes to mRects, so the user
848 // can get the position of each box by getPosition().
849 //
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800850 // Note we go from center-out because each box's X coordinate
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800851 // is relative to its anchor box (except the focused box).
852 private void layoutAndSetPosition() {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800853 for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
854 convertBoxToRect(CENTER_OUT_INDEX[i]);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800855 }
856 //dumpState();
857 }
858
859 private void dumpState() {
860 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
861 Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
Chih-Chung Changec412542011-09-26 17:34:06 +0800862 }
863
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800864 for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
865 dumpRect(CENTER_OUT_INDEX[i]);
Chih-Chung Changec412542011-09-26 17:34:06 +0800866 }
867
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800868 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
869 for (int j = i + 1; j <= BOX_MAX; j++) {
870 if (Rect.intersects(mRects.get(i), mRects.get(j))) {
871 Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
872 }
873 }
874 }
875 }
876
877 private void dumpRect(int i) {
878 StringBuilder sb = new StringBuilder();
879 Rect r = mRects.get(i);
880 sb.append("Rect " + i + ":");
881 sb.append("(");
882 sb.append(r.centerX());
883 sb.append(",");
884 sb.append(r.centerY());
885 sb.append(") [");
886 sb.append(r.width());
887 sb.append("x");
888 sb.append(r.height());
889 sb.append("]");
890 Log.d(TAG, sb.toString());
891 }
892
893 private void convertBoxToRect(int i) {
894 Box b = mBoxes.get(i);
895 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800896 int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800897 int w = widthOf(b);
898 int h = heightOf(b);
899 if (i == 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800900 int x = mPlatform.mCurrentX + mViewW / 2;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800901 r.left = x - w / 2;
902 r.right = r.left + w;
903 } else if (i > 0) {
904 Rect a = mRects.get(i - 1);
905 Gap g = mGaps.get(i - 1);
906 r.left = a.right + g.mCurrentGap;
907 r.right = r.left + w;
908 } else { // i < 0
909 Rect a = mRects.get(i + 1);
910 Gap g = mGaps.get(i);
911 r.right = a.left - g.mCurrentGap;
912 r.left = r.right - w;
913 }
914 r.top = y - h / 2;
915 r.bottom = r.top + h;
916 }
917
918 // Returns the position of a box.
919 public Rect getPosition(int index) {
920 return mRects.get(index);
921 }
922
923 ////////////////////////////////////////////////////////////////////////////
924 // Box management
925 ////////////////////////////////////////////////////////////////////////////
926
927 // Initialize the platform to be at the view center.
928 private void initPlatform() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800929 mPlatform.updateDefaultXY();
930 mPlatform.mCurrentX = mPlatform.mDefaultX;
931 mPlatform.mCurrentY = mPlatform.mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800932 mPlatform.mAnimationStartTime = NO_ANIMATION;
933 }
934
935 // Initialize a box to have the size of the view.
936 private void initBox(int index) {
937 Box b = mBoxes.get(index);
938 b.mImageW = mViewW;
939 b.mImageH = mViewH;
940 b.mUseViewSize = true;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800941 b.mScaleMin = getMinimalScale(b);
942 b.mScaleMax = getMaximalScale(b);
943 b.mCurrentY = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800944 b.mCurrentScale = b.mScaleMin;
945 b.mAnimationStartTime = NO_ANIMATION;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800946 b.mAnimationKind = ANIM_KIND_NONE;
947 }
948
949 // Initialize a box to a given size.
950 private void initBox(int index, Size size) {
951 if (size.width == 0 || size.height == 0) {
952 initBox(index);
953 return;
954 }
955 Box b = mBoxes.get(index);
956 b.mImageW = size.width;
957 b.mImageH = size.height;
958 b.mUseViewSize = false;
959 b.mScaleMin = getMinimalScale(b);
960 b.mScaleMax = getMaximalScale(b);
961 b.mCurrentY = 0;
962 b.mCurrentScale = b.mScaleMin;
963 b.mAnimationStartTime = NO_ANIMATION;
964 b.mAnimationKind = ANIM_KIND_NONE;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800965 }
966
967 // Initialize a gap. This can only be called after the boxes around the gap
968 // has been initialized.
969 private void initGap(int index) {
970 Gap g = mGaps.get(index);
971 g.mDefaultSize = getDefaultGapSize(index);
972 g.mCurrentGap = g.mDefaultSize;
973 g.mAnimationStartTime = NO_ANIMATION;
974 }
975
976 private void initGap(int index, int size) {
977 Gap g = mGaps.get(index);
978 g.mDefaultSize = getDefaultGapSize(index);
979 g.mCurrentGap = size;
980 g.mAnimationStartTime = NO_ANIMATION;
981 }
982
983 private void debugMoveBox(int fromIndex[]) {
984 StringBuilder s = new StringBuilder("moveBox:");
985 for (int i = 0; i < fromIndex.length; i++) {
986 int j = fromIndex[i];
987 if (j == Integer.MAX_VALUE) {
988 s.append(" N");
989 } else {
990 s.append(" ");
991 s.append(fromIndex[i]);
992 }
993 }
994 Log.d(TAG, s.toString());
995 }
996
997 // Move the boxes: it may indicate focus change, box deleted, box appearing,
998 // box reordered, etc.
999 //
1000 // Each element in the fromIndex array indicates where each box was in the
1001 // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
1002 // means the box is new.
1003 //
1004 // For example:
1005 // N N N N N N N -- all new boxes
1006 // -3 -2 -1 0 1 2 3 -- nothing changed
1007 // -2 -1 0 1 2 3 N -- focus goes to the next box
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001008 // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001009 // -3 -2 -1 1 2 3 N -- the focused box was deleted.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001010 //
1011 // hasPrev/hasNext indicates if there are previous/next boxes for the
1012 // focused box. constrained indicates whether the focused box should be put
1013 // into the constrained frame.
1014 public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001015 boolean constrained, Size[] sizes) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001016 //debugMoveBox(fromIndex);
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001017 mHasPrev = hasPrev;
1018 mHasNext = hasNext;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001019
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001020 RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
1021
1022 // 1. Get the absolute X coordiates for the boxes.
1023 layoutAndSetPosition();
1024 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1025 Box b = mBoxes.get(i);
1026 Rect r = mRects.get(i);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001027 b.mAbsoluteX = r.centerX() - mViewW / 2;
Chih-Chung Changec412542011-09-26 17:34:06 +08001028 }
1029
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001030 // 2. copy boxes and gaps to temporary storage.
1031 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1032 mTempBoxes.put(i, mBoxes.get(i));
1033 mBoxes.put(i, null);
1034 }
1035 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1036 mTempGaps.put(i, mGaps.get(i));
1037 mGaps.put(i, null);
1038 }
1039
1040 // 3. move back boxes that are used in the new array.
1041 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1042 int j = from.get(i);
1043 if (j == Integer.MAX_VALUE) continue;
1044 mBoxes.put(i, mTempBoxes.get(j));
1045 mTempBoxes.put(j, null);
1046 }
1047
1048 // 4. move back gaps if both boxes around it are kept together.
1049 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1050 int j = from.get(i);
1051 if (j == Integer.MAX_VALUE) continue;
1052 int k = from.get(i + 1);
1053 if (k == Integer.MAX_VALUE) continue;
1054 if (j + 1 == k) {
1055 mGaps.put(i, mTempGaps.get(j));
1056 mTempGaps.put(j, null);
1057 }
1058 }
1059
1060 // 5. recycle the boxes that are not used in the new array.
1061 int k = -BOX_MAX;
1062 for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1063 if (mBoxes.get(i) != null) continue;
1064 while (mTempBoxes.get(k) == null) {
1065 k++;
1066 }
1067 mBoxes.put(i, mTempBoxes.get(k++));
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001068 initBox(i, sizes[i + BOX_MAX]);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001069 }
1070
1071 // 6. Now give the recycled box a reasonable absolute X position.
1072 //
1073 // First try to find the first and the last box which the absolute X
1074 // position is known.
1075 int first, last;
1076 for (first = -BOX_MAX; first <= BOX_MAX; first++) {
1077 if (from.get(first) != Integer.MAX_VALUE) break;
1078 }
1079 for (last = BOX_MAX; last >= -BOX_MAX; last--) {
1080 if (from.get(last) != Integer.MAX_VALUE) break;
1081 }
1082 // If there is no box has known X position at all, make the focused one
1083 // as known.
1084 if (first > BOX_MAX) {
1085 mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
1086 first = last = 0;
1087 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001088 // Now for those boxes between first and last, assign their position to
1089 // align to the previous box or the next box with known position. For
1090 // the boxes before first or after last, we will use a new default gap
1091 // size below.
1092
1093 // Align to the previous box
1094 for (int i = Math.max(0, first + 1); i < last; i++) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001095 if (from.get(i) != Integer.MAX_VALUE) continue;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001096 Box a = mBoxes.get(i - 1);
1097 Box b = mBoxes.get(i);
1098 int wa = widthOf(a);
1099 int wb = widthOf(b);
1100 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2
1101 + getDefaultGapSize(i);
1102 if (mPopFromTop) {
1103 b.mCurrentY = -(mViewH / 2 + heightOf(b) / 2);
1104 } else {
1105 b.mCurrentY = (mViewH / 2 + heightOf(b) / 2);
1106 }
1107 }
1108
1109 // Align to the next box
1110 for (int i = Math.min(-1, last - 1); i > first; i--) {
1111 if (from.get(i) != Integer.MAX_VALUE) continue;
1112 Box a = mBoxes.get(i + 1);
1113 Box b = mBoxes.get(i);
1114 int wa = widthOf(a);
1115 int wb = widthOf(b);
1116 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2)
1117 - getDefaultGapSize(i);
1118 if (mPopFromTop) {
1119 b.mCurrentY = -(mViewH / 2 + heightOf(b) / 2);
1120 } else {
1121 b.mCurrentY = (mViewH / 2 + heightOf(b) / 2);
1122 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001123 }
1124
1125 // 7. recycle the gaps that are not used in the new array.
1126 k = -BOX_MAX;
1127 for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1128 if (mGaps.get(i) != null) continue;
1129 while (mTempGaps.get(k) == null) {
1130 k++;
1131 }
1132 mGaps.put(i, mTempGaps.get(k++));
1133 Box a = mBoxes.get(i);
1134 Box b = mBoxes.get(i + 1);
1135 int wa = widthOf(a);
1136 int wb = widthOf(b);
1137 if (i >= first && i < last) {
1138 int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
1139 initGap(i, g);
1140 } else {
1141 initGap(i);
1142 }
1143 }
1144
Chih-Chung Changc4791b72012-05-18 19:10:36 -07001145 // 8. calculate the new absolute X coordinates for those box before
1146 // first or after last.
1147 for (int i = first - 1; i >= -BOX_MAX; i--) {
1148 Box a = mBoxes.get(i + 1);
1149 Box b = mBoxes.get(i);
1150 int wa = widthOf(a);
1151 int wb = widthOf(b);
1152 Gap g = mGaps.get(i);
1153 b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
1154 }
1155
1156 for (int i = last + 1; i <= BOX_MAX; i++) {
1157 Box a = mBoxes.get(i - 1);
1158 Box b = mBoxes.get(i);
1159 int wa = widthOf(a);
1160 int wb = widthOf(b);
1161 Gap g = mGaps.get(i - 1);
1162 b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
1163 }
1164
1165 // 9. offset the Platform position
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001166 int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
1167 mPlatform.mCurrentX += dx;
1168 mPlatform.mFromX += dx;
1169 mPlatform.mToX += dx;
1170 mPlatform.mFlingOffset += dx;
1171
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001172 if (mConstrained != constrained) {
1173 mConstrained = constrained;
1174 mPlatform.updateDefaultXY();
1175 updateScaleAndGapLimit();
1176 }
1177
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001178 snapAndRedraw();
1179 }
1180
1181 ////////////////////////////////////////////////////////////////////////////
1182 // Public utilities
1183 ////////////////////////////////////////////////////////////////////////////
1184
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001185 public boolean isAtMinimalScale() {
1186 Box b = mBoxes.get(0);
1187 return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1188 }
1189
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001190 public boolean isCenter() {
1191 Box b = mBoxes.get(0);
1192 return mPlatform.mCurrentX == mPlatform.mDefaultX
1193 && b.mCurrentY == 0;
1194 }
1195
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001196 public int getImageWidth() {
1197 Box b = mBoxes.get(0);
1198 return b.mImageW;
1199 }
1200
1201 public int getImageHeight() {
1202 Box b = mBoxes.get(0);
1203 return b.mImageH;
1204 }
1205
1206 public float getImageScale() {
1207 Box b = mBoxes.get(0);
1208 return b.mCurrentScale;
1209 }
1210
1211 public int getImageAtEdges() {
1212 Box b = mBoxes.get(0);
1213 Platform p = mPlatform;
1214 calculateStableBound(b.mCurrentScale);
1215 int edges = 0;
1216 if (p.mCurrentX <= mBoundLeft) {
1217 edges |= IMAGE_AT_RIGHT_EDGE;
1218 }
1219 if (p.mCurrentX >= mBoundRight) {
1220 edges |= IMAGE_AT_LEFT_EDGE;
1221 }
1222 if (b.mCurrentY <= mBoundTop) {
1223 edges |= IMAGE_AT_BOTTOM_EDGE;
1224 }
1225 if (b.mCurrentY >= mBoundBottom) {
1226 edges |= IMAGE_AT_TOP_EDGE;
1227 }
1228 return edges;
1229 }
1230
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001231 public boolean isScrolling() {
1232 return mPlatform.mAnimationStartTime != NO_ANIMATION
1233 && mPlatform.mCurrentX != mPlatform.mToX;
1234 }
1235
1236 public void stopScrolling() {
1237 if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
Angus Kong95c2aea2012-05-24 16:20:57 -07001238 if (mFilmMode) mFilmScroller.forceFinished(true);
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001239 mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
1240 }
1241
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001242 public float getFilmRatio() {
1243 return mFilmRatio.mCurrentRatio;
1244 }
1245
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001246 public void setPopFromTop(boolean top) {
1247 mPopFromTop = top;
1248 }
1249
1250 public boolean hasDeletingBox() {
1251 for(int i = -BOX_MAX; i <= BOX_MAX; i++) {
1252 if (mBoxes.get(i).mAnimationKind == ANIM_KIND_DELETE) {
1253 return true;
1254 }
1255 }
1256 return false;
1257 }
1258
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001259 ////////////////////////////////////////////////////////////////////////////
1260 // Private utilities
1261 ////////////////////////////////////////////////////////////////////////////
1262
1263 private float getMinimalScale(Box b) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001264 float wFactor = 1.0f;
1265 float hFactor = 1.0f;
1266 int viewW, viewH;
1267
Chih-Chung Changb56ff732012-05-01 02:25:09 +08001268 if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1269 && b == mBoxes.get(0)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001270 viewW = mConstrainedFrame.width();
1271 viewH = mConstrainedFrame.height();
1272 } else {
1273 viewW = mViewW;
1274 viewH = mViewH;
1275 }
1276
1277 if (mFilmMode) {
1278 if (mViewH > mViewW) { // portrait
1279 wFactor = FILM_MODE_PORTRAIT_WIDTH;
1280 hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1281 } else { // landscape
1282 wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1283 hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1284 }
1285 }
1286
1287 float s = Math.min(wFactor * viewW / b.mImageW,
1288 hFactor * viewH / b.mImageH);
1289 return Math.min(SCALE_LIMIT, s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001290 }
1291
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001292 private float getMaximalScale(Box b) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001293 if (mFilmMode) return getMinimalScale(b);
1294 if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1295 return SCALE_LIMIT;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001296 }
1297
1298 private static boolean isAlmostEqual(float a, float b) {
1299 float diff = a - b;
1300 return (diff < 0 ? -diff : diff) < 0.02f;
Chih-Chung Changec412542011-09-26 17:34:06 +08001301 }
1302
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001303 // Calculates the stable region of mPlatform.mCurrentX and
1304 // mBoxes.get(0).mCurrentY, where "stable" means
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001305 //
1306 // (1) If the dimension of scaled image >= view dimension, we will not
1307 // see black region outside the image (at that dimension).
1308 // (2) If the dimension of scaled image < view dimension, we will center
1309 // the scaled image.
1310 //
1311 // We might temporarily go out of this stable during user interaction,
1312 // but will "snap back" after user stops interaction.
1313 //
1314 // The results are stored in mBound{Left/Right/Top/Bottom}.
1315 //
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001316 // An extra parameter "horizontalSlack" (which has the value of 0 usually)
1317 // is used to extend the stable region by some pixels on each side
1318 // horizontally.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001319 private void calculateStableBound(float scale, int horizontalSlack) {
1320 Box b = mBoxes.get(0);
Chih-Chung Chang8f568da2012-01-05 12:00:53 +08001321
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001322 // The width and height of the box in number of view pixels
1323 int w = widthOf(b, scale);
1324 int h = heightOf(b, scale);
1325
1326 // When the edge of the view is aligned with the edge of the box
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001327 mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1328 mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1329 mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1330 mBoundBottom = h / 2 - mViewH / 2;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001331
1332 // If the scaled height is smaller than the view height,
1333 // force it to be in the center.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001334 if (viewTallerThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001335 mBoundTop = mBoundBottom = 0;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001336 }
1337
1338 // Same for width
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001339 if (viewWiderThanScaledImage(scale)) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001340 mBoundLeft = mBoundRight = mPlatform.mDefaultX;
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001341 }
1342 }
1343
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001344 private void calculateStableBound(float scale) {
1345 calculateStableBound(scale, 0);
1346 }
1347
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001348 private boolean viewTallerThanScaledImage(float scale) {
1349 return mViewH >= heightOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001350 }
1351
1352 private boolean viewWiderThanScaledImage(float scale) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001353 return mViewW >= widthOf(mBoxes.get(0), scale);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001354 }
1355
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001356 private float getTargetScale(Box b) {
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001357 return b.mAnimationStartTime == NO_ANIMATION
1358 ? b.mCurrentScale : b.mToScale;
Chih-Chung Changec412542011-09-26 17:34:06 +08001359 }
1360
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001361 ////////////////////////////////////////////////////////////////////////////
1362 // Animatable: an thing which can do animation.
1363 ////////////////////////////////////////////////////////////////////////////
1364 private abstract static class Animatable {
1365 public long mAnimationStartTime;
1366 public int mAnimationKind;
1367 public int mAnimationDuration;
Chih-Chung Changec412542011-09-26 17:34:06 +08001368
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001369 // This should be overidden in subclass to change the animation values
1370 // give the progress value in [0, 1].
1371 protected abstract boolean interpolate(float progress);
1372 public abstract boolean startSnapback();
Chih-Chung Changec412542011-09-26 17:34:06 +08001373
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001374 // Returns true if the animation values changes, so things need to be
1375 // redrawn.
1376 public boolean advanceAnimation() {
1377 if (mAnimationStartTime == NO_ANIMATION) {
1378 return false;
1379 }
1380 if (mAnimationStartTime == LAST_ANIMATION) {
1381 mAnimationStartTime = NO_ANIMATION;
1382 return startSnapback();
1383 }
1384
1385 float progress;
1386 if (mAnimationDuration == 0) {
1387 progress = 1;
1388 } else {
1389 long now = AnimationTime.get();
1390 progress =
1391 (float) (now - mAnimationStartTime) / mAnimationDuration;
1392 }
1393
1394 if (progress >= 1) {
1395 progress = 1;
1396 } else {
1397 progress = applyInterpolationCurve(mAnimationKind, progress);
1398 }
1399
1400 boolean done = interpolate(progress);
1401
1402 if (done) {
1403 mAnimationStartTime = LAST_ANIMATION;
1404 }
1405
1406 return true;
Chih-Chung Changec412542011-09-26 17:34:06 +08001407 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001408
1409 private static float applyInterpolationCurve(int kind, float progress) {
1410 float f = 1 - progress;
1411 switch (kind) {
1412 case ANIM_KIND_SCROLL:
1413 case ANIM_KIND_FLING:
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001414 case ANIM_KIND_FLING_X:
1415 case ANIM_KIND_DELETE:
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001416 case ANIM_KIND_CAPTURE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001417 progress = 1 - f; // linear
1418 break;
1419 case ANIM_KIND_SCALE:
1420 progress = 1 - f * f; // quadratic
1421 break;
Yuli Huang5338d192012-05-17 11:32:15 +08001422 case ANIM_KIND_OPENING:
1423 progress = 1 - f * f * f; // x^3
1424 break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001425 case ANIM_KIND_SNAPBACK:
1426 case ANIM_KIND_ZOOM:
1427 case ANIM_KIND_SLIDE:
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001428 progress = 1 - f * f * f * f * f; // x^5
1429 break;
1430 }
1431 return progress;
1432 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001433 }
1434
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001435 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001436 // Platform: captures the global X/Y movement.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001437 ////////////////////////////////////////////////////////////////////////////
1438 private class Platform extends Animatable {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001439 public int mCurrentX, mFromX, mToX, mDefaultX;
1440 public int mCurrentY, mFromY, mToY, mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001441 public int mFlingOffset;
1442
1443 @Override
1444 public boolean startSnapback() {
1445 if (mAnimationStartTime != NO_ANIMATION) return false;
1446 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001447 && mListener.isHoldingDown()) return false;
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001448 if (mInScale) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001449
1450 Box b = mBoxes.get(0);
1451 float scaleMin = mExtraScalingRange ?
1452 b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1453 float scaleMax = mExtraScalingRange ?
1454 b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1455 float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1456 int x = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001457 int y = mDefaultY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001458 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001459 int defaultX = mDefaultX;
1460 if (!mHasNext) x = Math.max(x, defaultX);
1461 if (!mHasPrev) x = Math.min(x, defaultX);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001462 } else {
1463 calculateStableBound(scale, HORIZONTAL_SLACK);
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001464 // If the picture is zoomed-in, we want to keep the focus point
1465 // stay in the same position on screen, so we need to adjust
1466 // target mCurrentX (which is the center of the focused
1467 // box). The position of the focus point on screen (relative the
1468 // the center of the view) is:
1469 //
1470 // mCurrentX + scale * mFocusX = mCurrentX' + scale' * mFocusX
1471 // => mCurrentX' = mCurrentX + (scale - scale') * mFocusX
1472 //
1473 if (!viewWiderThanScaledImage(scale)) {
1474 float scaleDiff = b.mCurrentScale - scale;
1475 x += (int) (mFocusX * scaleDiff + 0.5f);
1476 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001477 x = Utils.clamp(x, mBoundLeft, mBoundRight);
1478 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001479 if (mCurrentX != x || mCurrentY != y) {
1480 return doAnimation(x, y, ANIM_KIND_SNAPBACK);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001481 }
1482 return false;
1483 }
1484
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001485 // The updateDefaultXY() should be called whenever these variables
1486 // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1487 // mFilmMode
1488 public void updateDefaultXY() {
1489 // We don't check mFilmMode and return 0 for mDefaultX. Because
1490 // otherwise if we decide to leave film mode because we are
1491 // centered, we will immediately back into film mode because we find
1492 // we are not centered.
1493 if (mConstrained && !mConstrainedFrame.isEmpty()) {
1494 mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1495 mDefaultY = mFilmMode ? 0 :
1496 mConstrainedFrame.centerY() - mViewH / 2;
1497 } else {
1498 mDefaultX = 0;
1499 mDefaultY = 0;
1500 }
1501 }
1502
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001503 // Starts an animation for the platform.
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001504 private boolean doAnimation(int targetX, int targetY, int kind) {
1505 if (mCurrentX == targetX && mCurrentY == targetY) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001506 mAnimationKind = kind;
1507 mFromX = mCurrentX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001508 mFromY = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001509 mToX = targetX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001510 mToY = targetY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001511 mAnimationStartTime = AnimationTime.startTime();
1512 mAnimationDuration = ANIM_TIME[kind];
1513 mFlingOffset = 0;
1514 advanceAnimation();
1515 return true;
1516 }
1517
1518 @Override
1519 protected boolean interpolate(float progress) {
1520 if (mAnimationKind == ANIM_KIND_FLING) {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001521 return interpolateFlingPage(progress);
1522 } else if (mAnimationKind == ANIM_KIND_FLING_X) {
1523 return interpolateFlingFilm(progress);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001524 } else {
1525 return interpolateLinear(progress);
1526 }
1527 }
1528
1529 private boolean interpolateFlingFilm(float progress) {
1530 mFilmScroller.computeScrollOffset();
1531 mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1532
1533 int dir = EdgeView.INVALID_DIRECTION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001534 if (mCurrentX < mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001535 if (!mHasNext) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001536 dir = EdgeView.RIGHT;
1537 }
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001538 } else if (mCurrentX > mDefaultX) {
Chih-Chung Changfb1a1552012-04-19 13:34:48 +08001539 if (!mHasPrev) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001540 dir = EdgeView.LEFT;
1541 }
1542 }
1543 if (dir != EdgeView.INVALID_DIRECTION) {
1544 int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1545 mListener.onAbsorb(v, dir);
1546 mFilmScroller.forceFinished(true);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001547 mCurrentX = mDefaultX;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001548 }
1549 return mFilmScroller.isFinished();
1550 }
1551
1552 private boolean interpolateFlingPage(float progress) {
1553 mPageScroller.computeScrollOffset(progress);
1554 Box b = mBoxes.get(0);
1555 calculateStableBound(b.mCurrentScale);
1556
1557 int oldX = mCurrentX;
1558 mCurrentX = mPageScroller.getCurrX();
1559
1560 // Check if we hit the edges; show edge effects if we do.
1561 if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1562 int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1563 mListener.onAbsorb(v, EdgeView.RIGHT);
1564 } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1565 int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1566 mListener.onAbsorb(v, EdgeView.LEFT);
1567 }
1568
1569 return progress >= 1;
1570 }
1571
1572 private boolean interpolateLinear(float progress) {
1573 // Other animations
1574 if (progress >= 1) {
1575 mCurrentX = mToX;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001576 mCurrentY = mToY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001577 return true;
1578 } else {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001579 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1580 progress = CaptureAnimation.calculateSlide(progress);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001581 }
1582 mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1583 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1584 if (mAnimationKind == ANIM_KIND_CAPTURE) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001585 return false;
1586 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001587 return (mCurrentX == mToX && mCurrentY == mToY);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001588 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001589 }
1590 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001591 }
1592
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001593 ////////////////////////////////////////////////////////////////////////////
1594 // Box: represents a rectangular area which shows a picture.
1595 ////////////////////////////////////////////////////////////////////////////
1596 private class Box extends Animatable {
1597 // Size of the bitmap
1598 public int mImageW, mImageH;
1599
1600 // This is true if we assume the image size is the same as view size
1601 // until we know the actual size of image. This is also used to
1602 // determine if there is an image ready to show.
1603 public boolean mUseViewSize;
1604
1605 // The minimum and maximum scale we allow for this box.
1606 public float mScaleMin, mScaleMax;
1607
1608 // The X/Y value indicates where the center of the box is on the view
1609 // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1610 // actual values used currently. Note that the X values are implicitly
1611 // defined by Platform and Gaps.
1612 public int mCurrentY, mFromY, mToY;
1613 public float mCurrentScale, mFromScale, mToScale;
1614
1615 // The absolute X coordinate of the center of the box. This is only used
1616 // during moveBox().
1617 public int mAbsoluteX;
1618
1619 @Override
1620 public boolean startSnapback() {
1621 if (mAnimationStartTime != NO_ANIMATION) return false;
1622 if (mAnimationKind == ANIM_KIND_SCROLL
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001623 && mListener.isHoldingDown()) return false;
1624 if (mAnimationKind == ANIM_KIND_DELETE
1625 && mListener.isHoldingDelete()) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001626 if (mInScale && this == mBoxes.get(0)) return false;
1627
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001628 int y = mCurrentY;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001629 float scale;
1630
1631 if (this == mBoxes.get(0)) {
1632 float scaleMin = mExtraScalingRange ?
1633 mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1634 float scaleMax = mExtraScalingRange ?
1635 mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1636 scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1637 if (mFilmMode) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001638 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001639 } else {
1640 calculateStableBound(scale, HORIZONTAL_SLACK);
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001641 // If the picture is zoomed-in, we want to keep the focus
1642 // point stay in the same position on screen. See the
1643 // comment in Platform.startSnapback for details.
1644 if (!viewTallerThanScaledImage(scale)) {
1645 float scaleDiff = mCurrentScale - scale;
1646 y += (int) (mFocusY * scaleDiff + 0.5f);
1647 }
1648 y = Utils.clamp(y, mBoundTop, mBoundBottom);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001649 }
1650 } else {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001651 y = 0;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001652 scale = mScaleMin;
1653 }
1654
1655 if (mCurrentY != y || mCurrentScale != scale) {
1656 return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1657 }
1658 return false;
1659 }
1660
1661 private boolean doAnimation(int targetY, float targetScale, int kind) {
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001662 targetScale = clampScale(targetScale);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001663
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001664 if (mCurrentY == targetY && mCurrentScale == targetScale
1665 && kind != ANIM_KIND_CAPTURE) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001666 return false;
1667 }
1668
1669 // Now starts an animation for the box.
1670 mAnimationKind = kind;
1671 mFromY = mCurrentY;
1672 mFromScale = mCurrentScale;
1673 mToY = targetY;
1674 mToScale = targetScale;
1675 mAnimationStartTime = AnimationTime.startTime();
1676 mAnimationDuration = ANIM_TIME[kind];
1677 advanceAnimation();
1678 return true;
1679 }
1680
Chih-Chung Chang192b7f12012-05-30 15:47:44 -07001681 // Clamps the input scale to the range that doAnimation() can reach.
1682 public float clampScale(float s) {
1683 return Utils.clamp(s,
1684 SCALE_MIN_EXTRA * mScaleMin,
1685 SCALE_MAX_EXTRA * mScaleMax);
1686 }
1687
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001688 @Override
1689 protected boolean interpolate(float progress) {
1690 if (mAnimationKind == ANIM_KIND_FLING) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001691 return interpolateFlingPage(progress);
1692 } else {
1693 return interpolateLinear(progress);
1694 }
1695 }
1696
1697 private boolean interpolateFlingPage(float progress) {
1698 mPageScroller.computeScrollOffset(progress);
1699 calculateStableBound(mCurrentScale);
1700
1701 int oldY = mCurrentY;
1702 mCurrentY = mPageScroller.getCurrY();
1703
1704 // Check if we hit the edges; show edge effects if we do.
1705 if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1706 int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1707 mListener.onAbsorb(v, EdgeView.BOTTOM);
1708 } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1709 int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1710 mListener.onAbsorb(v, EdgeView.TOP);
1711 }
1712
1713 return progress >= 1;
1714 }
1715
1716 private boolean interpolateLinear(float progress) {
1717 if (progress >= 1) {
1718 mCurrentY = mToY;
1719 mCurrentScale = mToScale;
1720 return true;
1721 } else {
1722 mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1723 mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001724 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1725 float f = CaptureAnimation.calculateScale(progress);
1726 mCurrentScale *= f;
1727 return false;
1728 } else {
1729 return (mCurrentY == mToY && mCurrentScale == mToScale);
1730 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001731 }
1732 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001733 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001734
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001735 ////////////////////////////////////////////////////////////////////////////
1736 // Gap: represents a rectangular area which is between two boxes.
1737 ////////////////////////////////////////////////////////////////////////////
1738 private class Gap extends Animatable {
1739 // The default gap size between two boxes. The value may vary for
1740 // different image size of the boxes and for different modes (page or
1741 // film).
1742 public int mDefaultSize;
1743
1744 // The gap size between the two boxes.
1745 public int mCurrentGap, mFromGap, mToGap;
1746
1747 @Override
1748 public boolean startSnapback() {
1749 if (mAnimationStartTime != NO_ANIMATION) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001750 return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001751 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001752
1753 // Starts an animation for a gap.
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001754 public boolean doAnimation(int targetSize, int kind) {
1755 if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
1756 return false;
1757 }
1758 mAnimationKind = kind;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001759 mFromGap = mCurrentGap;
1760 mToGap = targetSize;
1761 mAnimationStartTime = AnimationTime.startTime();
1762 mAnimationDuration = ANIM_TIME[mAnimationKind];
1763 advanceAnimation();
1764 return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001765 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001766
1767 @Override
1768 protected boolean interpolate(float progress) {
1769 if (progress >= 1) {
1770 mCurrentGap = mToGap;
1771 return true;
1772 } else {
1773 mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001774 if (mAnimationKind == ANIM_KIND_CAPTURE) {
1775 float f = CaptureAnimation.calculateScale(progress);
1776 mCurrentGap = (int) (mCurrentGap * f);
1777 return false;
1778 } else {
1779 return (mCurrentGap == mToGap);
1780 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001781 }
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001782 }
Chih-Chung Chang532d93c2011-10-12 17:10:33 +08001783 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001784
1785 ////////////////////////////////////////////////////////////////////////////
1786 // FilmRatio: represents the progress of film mode change.
1787 ////////////////////////////////////////////////////////////////////////////
1788 private class FilmRatio extends Animatable {
1789 // The film ratio: 1 means switching to film mode is complete, 0 means
1790 // switching to page mode is complete.
1791 public float mCurrentRatio, mFromRatio, mToRatio;
1792
1793 @Override
1794 public boolean startSnapback() {
1795 float target = mFilmMode ? 1f : 0f;
1796 if (target == mToRatio) return false;
1797 return doAnimation(target, ANIM_KIND_SNAPBACK);
1798 }
1799
1800 // Starts an animation for the film ratio.
1801 private boolean doAnimation(float targetRatio, int kind) {
1802 mAnimationKind = kind;
1803 mFromRatio = mCurrentRatio;
1804 mToRatio = targetRatio;
1805 mAnimationStartTime = AnimationTime.startTime();
1806 mAnimationDuration = ANIM_TIME[mAnimationKind];
1807 advanceAnimation();
1808 return true;
1809 }
1810
1811 @Override
1812 protected boolean interpolate(float progress) {
1813 if (progress >= 1) {
1814 mCurrentRatio = mToRatio;
1815 return true;
1816 } else {
1817 mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1818 return (mCurrentRatio == mToRatio);
1819 }
1820 }
1821 }
Chih-Chung Changec412542011-09-26 17:34:06 +08001822}