1ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang/*
2ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * Copyright (C) 2011 The Android Open Source Project
3ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang *
4ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * Licensed under the Apache License, Version 2.0 (the "License");
5ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * you may not use this file except in compliance with the License.
6ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * You may obtain a copy of the License at
7ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang *
8ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang *      http://www.apache.org/licenses/LICENSE-2.0
9ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang *
10ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * Unless required by applicable law or agreed to in writing, software
11ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * distributed under the License is distributed on an "AS IS" BASIS,
12ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * See the License for the specific language governing permissions and
14ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang * limitations under the License.
15ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang */
16ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
17ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changpackage com.android.gallery3d.ui;
18ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
19ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.content.Context;
2004ac045bf8da5082bbb0bdc9ea5f9c9b5b796ad0Yuli Huangimport android.graphics.Rect;
21b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport android.util.Log;
22b27df4650459068b409924493bbadaf25bb7e191Bobby Georgescuimport android.widget.Scroller;
23ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
242abaaf7caa9a83ba1ea759868aabde7f21387184John Reckimport com.android.gallery3d.app.PhotoPage;
252b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.common.Utils;
2628cb4161da5fc3756933ca67d509b8af1c6275f1Owen Linimport com.android.gallery3d.ui.PhotoView.Size;
272b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.GalleryUtils;
28b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeArray;
29b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeIntArray;
302b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin
31ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changclass PositionController {
32b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final String TAG = "PositionController";
33b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
342ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_LEFT_EDGE = 1;
352ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_RIGHT_EDGE = 2;
362ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_TOP_EDGE = 4;
372ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_BOTTOM_EDGE = 8;
382ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
39c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang    public static final int CAPTURE_ANIMATION_TIME = 700;
406b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public static final int SNAPBACK_ANIMATION_TIME = 600;
41f5ce6aeba448f418c99736465f7a02dacd7715bbChih-Chung Chang
42b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Special values for animation time.
43ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long NO_ANIMATION = -1;
44ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long LAST_ANIMATION = -2;
45ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
466b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int ANIM_KIND_NONE = -1;
47b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SCROLL = 0;
48b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SCALE = 1;
49b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SNAPBACK = 2;
50b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SLIDE = 3;
51b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_ZOOM = 4;
52b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_OPENING = 5;
53b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_FLING = 6;
546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int ANIM_KIND_FLING_X = 7;
556b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int ANIM_KIND_DELETE = 8;
566b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int ANIM_KIND_CAPTURE = 9;
57ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
58676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Animation time in milliseconds. The order must match ANIM_KIND_* above.
596b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    //
606b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // The values for ANIM_KIND_FLING_X does't matter because we use
616b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // mFilmScroller.isFinished() to decide when to stop. We set it to 0 so it's
626b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // faster for Animatable.advanceAnimation() to calculate the progress
636b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // (always 1).
64b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_TIME[] = {
65676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        0,    // ANIM_KIND_SCROLL
662ce59cbd4368eaf0f30cfea28891bd47155182cbChih-Chung Chang        0,    // ANIM_KIND_SCALE
676b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        SNAPBACK_ANIMATION_TIME,  // ANIM_KIND_SNAPBACK
68676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        400,  // ANIM_KIND_SLIDE
69676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        300,  // ANIM_KIND_ZOOM
704831c7494373c360062a7becac820eba3bc39287Bobby Georgescu        300,  // ANIM_KIND_OPENING
71b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        0,    // ANIM_KIND_FLING (the duration is calculated dynamically)
726b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        0,    // ANIM_KIND_FLING_X (see the comment above)
736b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        0,    // ANIM_KIND_DELETE (the duration is calculated dynamically)
74f5ce6aeba448f418c99736465f7a02dacd7715bbChih-Chung Chang        CAPTURE_ANIMATION_TIME,  // ANIM_KIND_CAPTURE
75676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    };
76676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
77ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // We try to scale up the image to fill the screen. But in order not to
78ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // scale too much for small icons, we limit the max up-scaling factor here.
79ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final float SCALE_LIMIT = 4;
80ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
81b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // For user's gestures, we give a temporary extra scaling range which goes
82b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // above or below the usual scaling limits.
83b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final float SCALE_MIN_EXTRA = 0.7f;
84534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private static final float SCALE_MAX_EXTRA = 1.4f;
85534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
86b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Setting this true makes the extra scaling range permanent (until this is
87b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // set to false again).
88534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private boolean mExtraScalingRange = false;
89676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
90b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
91b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mFilmMode = false;
92b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
93642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    // These are the limits for width / height of the picture in film mode.
94642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
95642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
96642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
97642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
98b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
99b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // In addition to the focused box (index == 0). We also keep information
100b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // about this many boxes on each side.
101b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
1026b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int[] CENTER_OUT_INDEX = new int[2 * BOX_MAX + 1];
103ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
104fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
105b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
106ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1076b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // These are constants for the delete gesture.
1086b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int DEFAULT_DELETE_ANIMATION_DURATION = 200; // ms
1096b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private static final int MAX_DELETE_ANIMATION_DURATION = 400; // ms
1106b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
111b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private Listener mListener;
112b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private volatile Rect mOpenAnimationRect;
113ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1148b930828c7a33d668876972c22a515e9163592f7Pin Ting    // Use a large enough value, so we won't see the gray shadow in the beginning.
115ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private int mViewW = 1200;
116ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private int mViewH = 1200;
117ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1188b930828c7a33d668876972c22a515e9163592f7Pin Ting    // A scaling gesture is in progress.
119b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mInScale;
120b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The focus point of the scaling gesture, relative to the center of the
121b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // picture in bitmap pixels.
122b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float mFocusX, mFocusY;
123ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
124fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    // whether there is a previous/next picture.
125fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private boolean mHasPrev, mHasNext;
126fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang
127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (page mode).
128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private FlingScroller mPageScroller;
129ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (film mode).
131b27df4650459068b409924493bbadaf25bb7e191Bobby Georgescu    private Scroller mFilmScroller;
132ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
133b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The bound of the stable region that the focused box can stay, see the
134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // comments above calculateStableBound() for details.
135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
136ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
137bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Constrained frame is a rectangle that the focused box should fit into if
138bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // it is constrained. It has two effects:
139bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
140bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (1) In page mode, if the focused box is constrained, scaling for the
141bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box is adjusted to fit into the constrained frame, instead of the
142bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // whole view.
143bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
144bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (2) In page mode, if the focused box is constrained, the mPlatform's
145bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // default center (mDefaultX/Y) is moved to the center of the constrained
146bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // frame, instead of the view center.
147bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
148bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mConstrainedFrame = new Rect();
149bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
150bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Whether the focused box is constrained.
151bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
152bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Our current program's first call to moveBox() sets constrained = true, so
153bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // we set the initial value of this variable to true, and we will not see
154bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // see unwanted transition animation.
155bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private boolean mConstrained = true;
156bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
157b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
158b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________________________________________________________
159b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |   _____       _____       _____       _____       _____   |
160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |     |     |     |     |     |     |     |     |     |  |
161b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  | Box |     | Box |     | Box*|     | Box |     | Box |  |
162b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |_____|.....|_____|.....|_____|.....|_____|.....|_____|  |
163b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |          Gap         Gap         Gap         Gap          |
164b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________________________________________________________|
165b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
166b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                       <--  Platform  -->
167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
168bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
169ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private Platform mPlatform = new Platform();
171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The gap at the right of a Box i is at index i. The gap at the left of a
173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Box i is at index i - 1.
174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
175ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private FilmRatio mFilmRatio = new FilmRatio();
176ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // These are only used during moveBox().
178b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
179fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private RangeArray<Gap> mTempGaps =
180fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
181ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1828b930828c7a33d668876972c22a515e9163592f7Pin Ting    // The output of the PositionController. Available through getPosition().
183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
184b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1856b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // The direction of a new picture should appear. New pictures pop from top
1866b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // if this value is true, or from bottom if this value is false.
1876b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    boolean mPopFromTop;
1886b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public interface Listener {
190b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void invalidate();
1916b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        boolean isHoldingDown();
1926b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        boolean isHoldingDelete();
193ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
194b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // EdgeView
195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onPull(int offset, int direction);
196b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onRelease();
197b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onAbsorb(int velocity, int direction);
198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
199ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2006b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    static {
2016b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Initialize the CENTER_OUT_INDEX array.
2026b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // The array maps 0, 1, 2, 3, 4, ..., 2 * BOX_MAX
2036b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // to 0, 1, -1, 2, -2, ..., BOX_MAX, -BOX_MAX
2046b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = 0; i < CENTER_OUT_INDEX.length; i++) {
2056b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int j = (i + 1) / 2;
2066b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if ((i & 1) == 0) j = -j;
2076b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            CENTER_OUT_INDEX[i] = j;
2086b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
2096b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
2106b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
211b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public PositionController(Context context, Listener listener) {
212b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener = listener;
213b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller = new FlingScroller();
2142abaaf7caa9a83ba1ea759868aabde7f21387184John Reck        mFilmScroller = new Scroller(context, null, false);
215b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Initialize the areas.
217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
219b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, new Box());
220b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(i);
221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mRects.put(i, new Rect());
222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, new Gap());
225b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initGap(i);
226b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
227ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
228ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setOpenAnimationRect(Rect r) {
230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = r;
231b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
232676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
233b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setViewSize(int viewW, int viewH) {
234b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewW == mViewW && viewH == mViewH) return;
235ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
236f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang        boolean wasMinimal = isAtMinimalScale();
237f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang
238b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewW = viewW;
239b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewH = viewH;
240b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
241ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
242b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
243b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            setBoxSize(i, viewW, viewH, true);
244b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
245ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
246b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
247e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang
248f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang        // If the focused box was at minimal scale, we try to make it the
249f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang        // minimal scale under the new view size.
250f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang        if (wasMinimal) {
251f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang            Box b = mBoxes.get(0);
252f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang            b.mCurrentScale = b.mScaleMin;
253f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang        }
254f3f90752ca92372d61ca0ec22a9846261cfbcbdeChih-Chung Chang
255e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        // If we have the opening animation, do it. Otherwise go directly to the
256e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        // right position.
257e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        if (!startOpeningAnimationIfNeeded()) {
25842e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang            skipToFinalPosition();
259e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        }
260ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
261ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2629f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang    public void setConstrainedFrame(Rect cFrame) {
2639f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        if (mConstrainedFrame.equals(cFrame)) return;
2649f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        mConstrainedFrame.set(cFrame);
265bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
266bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        updateScaleAndGapLimit();
267bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        snapAndRedraw();
268bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
269bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
2706b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void forceImageSize(int index, Size s) {
2716b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (s.width == 0 || s.height == 0) return;
2729f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        Box b = mBoxes.get(index);
2736b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mImageW = s.width;
2746b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mImageH = s.height;
2759f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        return;
2769f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang    }
2779f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang
2786b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void setImageSize(int index, Size s, Rect cFrame) {
2796b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (s.width == 0 || s.height == 0) return;
280bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
2819f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        boolean needUpdate = false;
2829f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        if (cFrame != null && !mConstrainedFrame.equals(cFrame)) {
2839f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang            mConstrainedFrame.set(cFrame);
2849f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang            mPlatform.updateDefaultXY();
2859f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang            needUpdate = true;
286b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
2876b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        needUpdate |= setBoxSize(index, s.width, s.height, false);
288ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2899f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        if (!needUpdate) return;
290b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
291b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
292ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
293ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2942c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Returns false if the box size doesn't change.
2952c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
296b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
297d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        boolean wasViewSize = b.mUseViewSize;
298d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang
299d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // If we already have an image size, we don't want to use the view size.
3002c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (!wasViewSize && isViewSize) return false;
301ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
302b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = isViewSize;
303ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (width == b.mImageW && height == b.mImageH) {
3052c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return false;
306676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        }
307676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
308b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The ratio of the old size and the new size.
3099f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        //
3109f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        // If the aspect ratio changes, we don't know if it is because one side
3119f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        // grows or the other side shrinks. Currently we just assume the view
3129f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        // angle of the longer side doesn't change (so the aspect ratio change
3139f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        // is because the view angle of the shorter side changes). This matches
3149f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        // what camera preview does.
3159f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang        float ratio = (width > height)
3169f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang                ? (float) b.mImageW / width
3179f44f35c211229bc0c5ca17823f5d7b6bb3583a1Chih-Chung Chang                : (float) b.mImageH / height;
318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
319bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageW = width;
320bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageH = height;
321bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
322a65914485de7b82b96b616c7be738ab952e09c83John Reck        // If this is the first time we receive an image size or we are in fullscreen,
323a65914485de7b82b96b616c7be738ab952e09c83John Reck        // we change the scale directly. Otherwise adjust the scales by a ratio,
324a65914485de7b82b96b616c7be738ab952e09c83John Reck        // and snapback will animate the scale into the min/max bounds if necessary.
325a65914485de7b82b96b616c7be738ab952e09c83John Reck        if ((wasViewSize && !isViewSize) || !mFilmMode) {
326bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mCurrentScale = getMinimalScale(b);
327d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
328d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        } else {
329d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mCurrentScale *= ratio;
330d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mFromScale *= ratio;
331d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mToScale *= ratio;
332d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        }
333676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusX /= ratio;
336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusY /= ratio;
337ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
3382c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
3392c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        return true;
340ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
341ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
342e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang    private boolean startOpeningAnimationIfNeeded() {
343e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        if (mOpenAnimationRect == null) return false;
344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
345e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        if (b.mUseViewSize) return false;
346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Start animation from the saved rectangle if we have one.
348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mOpenAnimationRect;
349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = null;
350f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang
351bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = r.centerX() - mViewW / 2;
352bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = r.centerY() - mViewH / 2;
353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                r.height() / (float) b.mImageH);
355bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
356bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_OPENING);
357f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang
358f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang        // Animate from large gaps for neighbor boxes to avoid them
359f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang        // shown on the screen during opening animation.
360f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang        for (int i = -1; i < 1; i++) {
361f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang            Gap g = mGaps.get(i);
362f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang            g.mCurrentGap = mViewW;
363f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang            g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
364f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang        }
365e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang
366e6251df06f0b3c8f556043f8b725b4b19be7474dChih-Chung Chang        return true;
367ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
368ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setFilmMode(boolean enabled) {
370b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (enabled == mFilmMode) return;
371b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmMode = enabled;
372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
373bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        stopAnimation();
376b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
377ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
378ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setExtraScalingRange(boolean enabled) {
380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mExtraScalingRange == enabled) return;
381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mExtraScalingRange = enabled;
382b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!enabled) {
383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            snapAndRedraw();
384b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
385ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
386ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
387b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This should be called whenever the scale range of boxes or the default
388b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // gap size may change. Currently this can happen due to change of view
389bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
390b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void updateScaleAndGapLimit() {
391b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
392b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
393bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMin = getMinimalScale(b);
394bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMax = getMaximalScale(b);
395b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
396ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
397b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
398b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
399b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mDefaultSize = getDefaultGapSize(i);
400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
401b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
402676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
403b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the default gap size according the the size of the boxes around
404b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // the gap and the current mode.
405b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getDefaultGapSize(int i) {
406b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return IMAGE_GAP;
407b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box a = mBoxes.get(i);
408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i + 1);
409b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
410ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
411ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
412b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Here is how we layout the boxes in the page mode.
413b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   previous             current             next
415b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________       ________________     __________
416b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  _______  |     |   __________   |   |  ______  |
417b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       | |     |  |   right->|  |   | |      | |
418b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       |<-------->|<--left   |  |   | |      | |
419b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |_______| |  |  |  |__________|  |   | |______| |
420b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________|  |  |________________|   |__________|
421b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |  <--> gapToSide()
422b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |
423b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
424b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int gapToSide(Box b) {
425b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
426ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
427ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Stop all animations at where they are now.
429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void stopAnimation() {
430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
433b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
434b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
435b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
436534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang        }
437534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    }
438534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void skipAnimation() {
440b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mCurrentX = mPlatform.mToX;
442bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.mCurrentY = mPlatform.mToY;
443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mAnimationStartTime = NO_ANIMATION;
444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
447b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (b.mAnimationStartTime == NO_ANIMATION) continue;
448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentY = b.mToY;
449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentScale = b.mToScale;
450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (g.mAnimationStartTime == NO_ANIMATION) continue;
455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mCurrentGap = g.mToGap;
456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mAnimationStartTime = NO_ANIMATION;
457b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
458b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
459ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
460ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
4612c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void snapback() {
462b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
463ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
464ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
46542e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang    public void skipToFinalPosition() {
46642e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang        stopAnimation();
46742e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang        snapAndRedraw();
46842e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang        skipAnimation();
46942e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang    }
47042e1fed4edd9ed1e326aab2f7969242c56812952Chih-Chung Chang
471b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
472b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Start an animations for the focused box
473b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
474b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
475b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void zoomIn(float tapX, float tapY, float targetScale) {
476bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapX -= mViewW / 2;
477bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapY -= mViewH / 2;
478b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
479b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
480b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Convert the tap position to distance to center in bitmap coordinates
481b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
482b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
483b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
484bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int x = (int) (-tempX * targetScale + 0.5f);
485bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = (int) (-tempY * targetScale + 0.5f);
486b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
487b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(targetScale);
488b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
489b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
490b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
491b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
492b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
493ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
494ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
495b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void resetToFullView() {
496b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
497bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
498b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
499b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
500b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void beginScale(float focusX, float focusY) {
501bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
502bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
503b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
504b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
505b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = true;
506b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
507b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
508b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
509b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
510b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Scales the image by the given factor.
511b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns an out-of-range indicator:
512b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   1 if the intended scale is too large for the stable range.
513b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   0 if the intended scale is in the stable range.
514b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  -1 if the intended scale is too small for the stable range.
515b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int scaleBy(float s, float focusX, float focusY) {
516bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
517bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
518b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
519b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
520b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
521b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // We want to keep the focus point (on the bitmap) the same as when we
5228b930828c7a33d668876972c22a515e9163592f7Pin Ting        // begin the scale gesture, that is,
523b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
524b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // (focusX' - currentX') / scale' = (focusX - currentX) / scale
525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
526192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang        s = b.clampScale(s * getTargetScale(b));
527b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
528b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
529b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, s, ANIM_KIND_SCALE);
530b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s < b.mScaleMin) return -1;
531b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s > b.mScaleMax) return 1;
532b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return 0;
533b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
534b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
535b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void endScale() {
536b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = false;
537b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
538ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
539ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
5402c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view.
5412c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startHorizontalSlide() {
542b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
543bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
5442c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
5452c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
5462c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view with the capture
5472c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // animation. In addition to the sliding, the animation will also scale the
5482c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // the focused box, the specified neighbor box, and the gap between the
5492c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // two. The specified offset should be 1 or -1.
5502c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startCaptureAnimationSlide(int offset) {
5512c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box b = mBoxes.get(0);
5522c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box n = mBoxes.get(offset);  // the neighbor box
5532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Gap g = mGaps.get(offset);  // the gap between the two boxes
5542c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
555bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
556bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_CAPTURE);
557bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
558bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
5592c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
5602c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        redraw();
561ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
562ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
5636b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Only allow scrolling when we are not currently in an animation or we
5646b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // are in some animation with can be interrupted.
5656b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private boolean canScroll() {
566b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
5676b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (b.mAnimationStartTime == NO_ANIMATION) return true;
5686b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        switch (b.mAnimationKind) {
5696b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            case ANIM_KIND_SCROLL:
5706b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            case ANIM_KIND_FLING:
5716b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            case ANIM_KIND_FLING_X:
5726b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                return true;
573b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
5746b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return false;
575676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    }
576676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
5776b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void scrollPage(int dx, int dy) {
5786b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (!canScroll()) return;
5796b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
580b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
5816b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        Platform p = mPlatform;
582532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
583b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
584532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
5856b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int x = p.mCurrentX + dx;
5866b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int y = b.mCurrentY + dy;
5876b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
588532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Vertical direction: If we have space to move in the vertical
589532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // direction, we show the edge effect when scrolling reaches the edge.
590532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (mBoundTop != mBoundBottom) {
591532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            if (y < mBoundTop) {
592b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
593532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            } else if (y > mBoundBottom) {
594b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(y - mBoundBottom, EdgeView.TOP);
595532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            }
596532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
597532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
598532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        y = Utils.clamp(y, mBoundTop, mBoundBottom);
599532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
600532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
601532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // tries to go left of the first image or go right of the last image.
602fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        if (!mHasPrev && x > mBoundRight) {
603b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = x - mBoundRight;
604b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.LEFT);
605532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            x = mBoundRight;
606fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        } else if (!mHasNext && x < mBoundLeft) {
607b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = mBoundLeft - x;
608b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.RIGHT);
609b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            x = mBoundLeft;
610b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
611b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
612b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
613b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
614b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
6156b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void scrollFilmX(int dx) {
6166b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (!canScroll()) return;
6176b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
618b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
6196b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        Platform p = mPlatform;
6206b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
6216b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Only allow scrolling when we are not currently in an animation or we
6226b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // are in some animation with can be interrupted.
6236b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (b.mAnimationStartTime != NO_ANIMATION) {
6246b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            switch (b.mAnimationKind) {
6256b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                case ANIM_KIND_SCROLL:
6266b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                case ANIM_KIND_FLING:
6276b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                case ANIM_KIND_FLING_X:
6286b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    break;
6296b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                default:
6306b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    return;
6316b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            }
6326b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
6336b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
6346b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int x = p.mCurrentX + dx;
635b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
637b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // tries to go left of the first image or go right of the last image.
638bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x -= mPlatform.mDefaultX;
639bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (!mHasPrev && x > 0) {
640bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(x, EdgeView.LEFT);
641bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
642bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else if (!mHasNext && x < 0) {
643bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(-x, EdgeView.RIGHT);
644bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
645532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
646bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x += mPlatform.mDefaultX;
6476b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        startAnimation(x, b.mCurrentY, b.mCurrentScale, ANIM_KIND_SCROLL);
648532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
649532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
6506b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void scrollFilmY(int boxIndex, int dy) {
6516b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (!canScroll()) return;
6526b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
6536b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        Box b = mBoxes.get(boxIndex);
6546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int y = b.mCurrentY + dy;
6556b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.doAnimation(y, b.mCurrentScale, ANIM_KIND_SCROLL);
6566b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        redraw();
657b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
658b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
6596b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public boolean flingPage(int velocityX, int velocityY) {
660b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
661b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
662b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
663b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // We only want to do fling when the picture is zoomed-in.
664b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewWiderThanScaledImage(b.mCurrentScale) &&
665b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            viewTallerThanScaledImage(b.mCurrentScale)) {
6662ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            return false;
6672ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
6682ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
6692ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // We only allow flinging in the directions where it won't go over the
6702ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // picture.
6712ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        int edges = getImageAtEdges();
6722ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
6732ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
6742ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityX = 0;
6752ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
6762ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
6772ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
6782ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityY = 0;
6792ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
680b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
681b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX == 0 && velocityY == 0) return false;
682b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
683b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
684b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
685b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mPageScroller.getFinalX();
686b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = mPageScroller.getFinalY();
687b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
6886b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
689b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
690b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
6916b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public boolean flingFilmX(int velocityX) {
6926b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (velocityX == 0) return false;
6936b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
694b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
695b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
696b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
697b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we are already at the edge, don't start the fling.
698bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int defaultX = p.mDefaultX;
699bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if ((!mHasPrev && p.mCurrentX >= defaultX)
700bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                || (!mHasNext && p.mCurrentX <= defaultX)) {
701b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
7026068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang        }
703ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
704b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
705b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mFilmScroller.getFinalX();
7076b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return startAnimation(
7086b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING_X);
7096b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
7106b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7116b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Moves the specified box out of screen. If velocityY is 0, a default
7126b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // velocity is used. Returns the time for the duration, or -1 if we cannot
7136b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // not do the animation.
7146b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public int flingFilmY(int boxIndex, int velocityY) {
7156b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        Box b = mBoxes.get(boxIndex);
7166b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7176b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Calculate targetY
7186b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int h = heightOf(b);
7196b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int targetY;
7206b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int FUZZY = 3;  // TODO: figure out why this is needed.
7216b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (velocityY < 0 || (velocityY == 0 && b.mCurrentY <= 0)) {
7226b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            targetY = -mViewH / 2 - (h + 1) / 2 - FUZZY;
7236b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        } else {
7246b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            targetY = (mViewH + 1) / 2 + h / 2 + FUZZY;
7256b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
7266b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7276b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Calculate duration
7286b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        int duration;
7296b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (velocityY != 0) {
7306b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            duration = (int) (Math.abs(targetY - b.mCurrentY) * 1000f
7316b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    / Math.abs(velocityY));
7326b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            duration = Math.min(MAX_DELETE_ANIMATION_DURATION, duration);
7336b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        } else {
7346b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            duration = DEFAULT_DELETE_ANIMATION_DURATION;
7356b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
7366b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7376b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Start animation
7386b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        ANIM_TIME[ANIM_KIND_DELETE] = duration;
7396b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (b.doAnimation(targetY, b.mCurrentScale, ANIM_KIND_DELETE)) {
7406b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            redraw();
7416b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            return duration;
7426b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
7436b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return -1;
7446b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
7456b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7466b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Returns the index of the box which contains the given point (x, y)
7476b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Returns Integer.MAX_VALUE if there is no hit. There may be more than
7486b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // one box contains the given point, and we want to give priority to the
7496b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // one closer to the focused index (0).
7506b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public int hitTest(int x, int y) {
7516b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
7526b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int j = CENTER_OUT_INDEX[i];
7536b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            Rect r = mRects.get(j);
7546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (r.contains(x, y)) {
7556b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                return j;
7566b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            }
7576b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
7586b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
7596b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return Integer.MAX_VALUE;
760b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
761ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
762b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
763b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Redraw
764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
765b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method changes box positions directly, redraw()
766b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  should be called.
767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method may also cause a snapback to happen, snapAndRedraw() should
769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
770b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
771b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method starts an animation to change the position of focused box,
772b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  startAnimation() should be called.
773b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
774b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If time advances to change the box position, advanceAnimation() should
775b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
776b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
777b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void redraw() {
778b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
779b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener.invalidate();
780b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
781b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
782b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void snapAndRedraw() {
783b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.startSnapback();
784b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
785b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).startSnapback();
786b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
787b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
788b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).startSnapback();
789ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
790ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        mFilmRatio.startSnapback();
791b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
792b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
793ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
7946b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private boolean startAnimation(int targetX, int targetY, float targetScale,
795b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int kind) {
796b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
797bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
798b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
799b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
8006b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return changed;
801b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
802b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
803b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang    public void advanceAnimation() {
804b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
805b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mPlatform.advanceAnimation();
806b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
807b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mBoxes.get(i).advanceAnimation();
808b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
809b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
810b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mGaps.get(i).advanceAnimation();
811b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
812ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        changed |= mFilmRatio.advanceAnimation();
813b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
814ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
815ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
816f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang    public boolean inOpeningAnimation() {
817f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang        return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
818f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang                mPlatform.mAnimationStartTime != NO_ANIMATION) ||
819f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang               (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
820f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang                mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
821f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang    }
822f320b8429dd5c68c117b8e9b1a36a435f4f4ff13Yuli Huang
823b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
824b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Layout
825b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
826b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
827b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box.
828b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b) {
829b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * b.mCurrentScale + 0.5f);
830b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
831b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
832b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box.
833b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b) {
834b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * b.mCurrentScale + 0.5f);
835b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
836b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
837b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box, using the given scale.
838b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b, float scale) {
839b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * scale + 0.5f);
840b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
841b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
842b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box, using the given scale.
843b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b, float scale) {
844b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * scale + 0.5f);
845b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
846b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
847b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Convert the information in mPlatform and mBoxes to mRects, so the user
848b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // can get the position of each box by getPosition().
849b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
8506b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Note we go from center-out because each box's X coordinate
851b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // is relative to its anchor box (except the focused box).
852b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void layoutAndSetPosition() {
8536b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
8546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            convertBoxToRect(CENTER_OUT_INDEX[i]);
855ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
856b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //dumpState();
857b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
858ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
85928cb4161da5fc3756933ca67d509b8af1c6275f1Owen Lin    @SuppressWarnings("unused")
860b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpState() {
861b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
862b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
863ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
864ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
8656b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = 0; i < 2 * BOX_MAX + 1; i++) {
8666b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            dumpRect(CENTER_OUT_INDEX[i]);
867b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
868b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
869b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
870b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            for (int j = i + 1; j <= BOX_MAX; j++) {
871b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (Rect.intersects(mRects.get(i), mRects.get(j))) {
872b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
873b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
874ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            }
875b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
876b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
877b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
878b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpRect(int i) {
879b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder sb = new StringBuilder();
880b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
881b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("Rect " + i + ":");
882b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("(");
883b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerX());
884b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(",");
885b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerY());
886b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(") [");
887b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.width());
888b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("x");
889b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.height());
890b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("]");
891b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, sb.toString());
892b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
893b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
894b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void convertBoxToRect(int i) {
895b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
896b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
897bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
898b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b);
899b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b);
900b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
901bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int x = mPlatform.mCurrentX + mViewW / 2;
902b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = x - w / 2;
903b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
904b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (i > 0) {
905b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i - 1);
906b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i - 1);
907b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = a.right + g.mCurrentGap;
908b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
909b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else {  // i < 0
910b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i + 1);
911b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
912b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = a.left - g.mCurrentGap;
913b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = r.right - w;
914b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
915b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.top = y - h / 2;
916b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.bottom = r.top + h;
917b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
918b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
919b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the position of a box.
920b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public Rect getPosition(int index) {
921b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mRects.get(index);
922b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
923b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
924b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
925b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box management
926b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
927b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
928b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize the platform to be at the view center.
929b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initPlatform() {
930bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
931bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = mPlatform.mDefaultX;
932bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentY = mPlatform.mDefaultY;
933b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
934b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
935b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
936b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a box to have the size of the view.
937b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initBox(int index) {
938b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(index);
939b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageW = mViewW;
940b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageH = mViewH;
941b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = true;
942bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMin = getMinimalScale(b);
943bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMax = getMaximalScale(b);
944bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = 0;
945b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = b.mScaleMin;
946b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mAnimationStartTime = NO_ANIMATION;
9476b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mAnimationKind = ANIM_KIND_NONE;
9486b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
9496b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
9506b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    // Initialize a box to a given size.
9516b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    private void initBox(int index, Size size) {
9526b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        if (size.width == 0 || size.height == 0) {
9536b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            initBox(index);
9546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            return;
9556b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
9566b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        Box b = mBoxes.get(index);
9576b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mImageW = size.width;
9586b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mImageH = size.height;
9596b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mUseViewSize = false;
9606b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mScaleMin = getMinimalScale(b);
9616b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mScaleMax = getMaximalScale(b);
9626b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mCurrentY = 0;
9636b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mCurrentScale = b.mScaleMin;
9646b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mAnimationStartTime = NO_ANIMATION;
9656b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        b.mAnimationKind = ANIM_KIND_NONE;
966b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
967b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
968b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a gap. This can only be called after the boxes around the gap
969b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // has been initialized.
970b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index) {
971b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
972b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
973b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = g.mDefaultSize;
974b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
975b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
976b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
977b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index, int size) {
978b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
979b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
980b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = size;
981b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
982b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
983b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
98428cb4161da5fc3756933ca67d509b8af1c6275f1Owen Lin    @SuppressWarnings("unused")
985b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void debugMoveBox(int fromIndex[]) {
986b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder s = new StringBuilder("moveBox:");
987b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < fromIndex.length; i++) {
988b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = fromIndex[i];
989b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) {
990b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" N");
991b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else {
992b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" ");
993b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(fromIndex[i]);
994b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            }
995ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
996b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, s.toString());
9976068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang    }
9986068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang
999b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Move the boxes: it may indicate focus change, box deleted, box appearing,
1000b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // box reordered, etc.
1001b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
1002b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Each element in the fromIndex array indicates where each box was in the
1003b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
1004b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // means the box is new.
1005b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
1006b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // For example:
1007b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // N N N N N N N -- all new boxes
1008b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 0 1 2 3 -- nothing changed
1009b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -2 -1 0 1 2 3 N -- focus goes to the next box
10108b930828c7a33d668876972c22a515e9163592f7Pin Ting    // N -3 -2 -1 0 1 2 -- focus goes to the previous box
1011b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 1 2 3 N -- the focused box was deleted.
1012bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
1013bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // hasPrev/hasNext indicates if there are previous/next boxes for the
1014bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box. constrained indicates whether the focused box should be put
1015bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // into the constrained frame.
1016bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
10176b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            boolean constrained, Size[] sizes) {
1018b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //debugMoveBox(fromIndex);
1019fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasPrev = hasPrev;
1020fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasNext = hasNext;
1021bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1022b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
1023b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
10248b930828c7a33d668876972c22a515e9163592f7Pin Ting        // 1. Get the absolute X coordinates for the boxes.
1025b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
1026b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1027b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
1028b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mRects.get(i);
1029bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mAbsoluteX = r.centerX() - mViewW / 2;
1030ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1031ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1032b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 2. copy boxes and gaps to temporary storage.
1033b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1034b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(i, mBoxes.get(i));
1035b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, null);
1036b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1037b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1038b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempGaps.put(i, mGaps.get(i));
1039b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, null);
1040b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1041b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1042b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 3. move back boxes that are used in the new array.
1043b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1044b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
1045b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
1046b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(j));
1047b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(j, null);
1048b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1049ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1050b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 4. move back gaps if both boxes around it are kept together.
1051b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1052b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
1053b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
1054b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int k = from.get(i + 1);
1055b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (k == Integer.MAX_VALUE) continue;
1056b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j + 1 == k) {
1057b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mGaps.put(i, mTempGaps.get(j));
1058b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mTempGaps.put(j, null);
1059b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1060b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1061534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
1062b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 5. recycle the boxes that are not used in the new array.
1063b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int k = -BOX_MAX;
1064b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
1065b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mBoxes.get(i) != null) continue;
1066b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempBoxes.get(k) == null) {
1067b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
1068b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1069b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(k++));
10706b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            initBox(i, sizes[i + BOX_MAX]);
1071ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1072ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1073b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 6. Now give the recycled box a reasonable absolute X position.
1074b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
1075b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // First try to find the first and the last box which the absolute X
1076b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // position is known.
1077b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int first, last;
1078b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (first = -BOX_MAX; first <= BOX_MAX; first++) {
1079b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(first) != Integer.MAX_VALUE) break;
1080b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1081b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (last = BOX_MAX; last >= -BOX_MAX; last--) {
1082b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(last) != Integer.MAX_VALUE) break;
1083b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1084b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If there is no box has known X position at all, make the focused one
1085b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // as known.
1086b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (first > BOX_MAX) {
1087b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
1088b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            first = last = 0;
1089b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
10906b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Now for those boxes between first and last, assign their position to
10916b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // align to the previous box or the next box with known position. For
10926b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // the boxes before first or after last, we will use a new default gap
10936b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // size below.
10946b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
10956b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Align to the previous box
10966b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = Math.max(0, first + 1); i < last; i++) {
1097b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(i) != Integer.MAX_VALUE) continue;
10986b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            Box a = mBoxes.get(i - 1);
10996b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            Box b = mBoxes.get(i);
11006b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int wa = widthOf(a);
11016b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int wb = widthOf(b);
11026b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2
11036b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    + getDefaultGapSize(i);
11046b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (mPopFromTop) {
11056b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                b.mCurrentY = -(mViewH / 2 + heightOf(b) / 2);
11066b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            } else {
11076b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                b.mCurrentY = (mViewH / 2 + heightOf(b) / 2);
11086b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            }
11096b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
11106b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
11116b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        // Align to the next box
11126b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for (int i = Math.min(-1, last - 1); i > first; i--) {
11136b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (from.get(i) != Integer.MAX_VALUE) continue;
11146b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            Box a = mBoxes.get(i + 1);
11156b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            Box b = mBoxes.get(i);
11166b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int wa = widthOf(a);
11176b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            int wb = widthOf(b);
11186b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2)
11196b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    - getDefaultGapSize(i);
11206b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (mPopFromTop) {
11216b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                b.mCurrentY = -(mViewH / 2 + heightOf(b) / 2);
11226b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            } else {
11236b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                b.mCurrentY = (mViewH / 2 + heightOf(b) / 2);
11246b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            }
1125b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1126ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 7. recycle the gaps that are not used in the new array.
1128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        k = -BOX_MAX;
1129b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
1130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mGaps.get(i) != null) continue;
1131b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempGaps.get(k) == null) {
1132b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
1133b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, mTempGaps.get(k++));
1135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box a = mBoxes.get(i);
1136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i + 1);
1137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wa = widthOf(a);
1138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wb = widthOf(b);
1139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (i >= first && i < last) {
1140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
1141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i, g);
1142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i);
1144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1145ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1146ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1147c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        // 8. calculate the new absolute X coordinates for those box before
1148c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        // first or after last.
1149c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        for (int i = first - 1; i >= -BOX_MAX; i--) {
1150c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Box a = mBoxes.get(i + 1);
1151c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Box b = mBoxes.get(i);
1152c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            int wa = widthOf(a);
1153c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            int wb = widthOf(b);
1154c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Gap g = mGaps.get(i);
1155c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
1156c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        }
1157c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang
1158c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        for (int i = last + 1; i <= BOX_MAX; i++) {
1159c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Box a = mBoxes.get(i - 1);
1160c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Box b = mBoxes.get(i);
1161c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            int wa = widthOf(a);
1162c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            int wb = widthOf(b);
1163c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            Gap g = mGaps.get(i - 1);
1164c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang            b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
1165c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        }
1166c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang
1167c4791b7721a8417be5be33a67c8ade6e82b03a2cChih-Chung Chang        // 9. offset the Platform position
1168b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
1169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mCurrentX += dx;
1170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFromX += dx;
1171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mToX += dx;
1172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFlingOffset += dx;
1173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1174bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mConstrained != constrained) {
1175bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mConstrained = constrained;
1176bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.updateDefaultXY();
1177bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateScaleAndGapLimit();
1178bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1179bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1180b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
1181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1184b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Public utilities
1185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1187b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public boolean isAtMinimalScale() {
1188b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
1189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
1190b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1192bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public boolean isCenter() {
1193bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        Box b = mBoxes.get(0);
1194bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mPlatform.mCurrentX == mPlatform.mDefaultX
1195bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            && b.mCurrentY == 0;
1196bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
1197bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageWidth() {
1199b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
1200b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageW;
1201b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageHeight() {
1204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
1205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageH;
1206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1208b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public float getImageScale() {
1209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
1210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mCurrentScale;
1211b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1212b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1213b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageAtEdges() {
1214b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
1215b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
1216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
1217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int edges = 0;
1218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX <= mBoundLeft) {
1219b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_RIGHT_EDGE;
1220ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX >= mBoundRight) {
1222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_LEFT_EDGE;
1223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY <= mBoundTop) {
1225b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_BOTTOM_EDGE;
1226b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1227b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY >= mBoundBottom) {
1228b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_TOP_EDGE;
1229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return edges;
1231b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1232b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
123317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    public boolean isScrolling() {
123417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        return mPlatform.mAnimationStartTime != NO_ANIMATION
123517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                && mPlatform.mCurrentX != mPlatform.mToX;
123617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
123717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
123817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    public void stopScrolling() {
123917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
124095c2aeab0bf398b45d8a9c562fd110d1e3756139Angus Kong        if (mFilmMode) mFilmScroller.forceFinished(true);
124117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
124217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
124317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
1244ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    public float getFilmRatio() {
1245ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return mFilmRatio.mCurrentRatio;
1246ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1247ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
12486b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public void setPopFromTop(boolean top) {
12496b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        mPopFromTop = top;
12506b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
12516b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
12526b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    public boolean hasDeletingBox() {
12536b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        for(int i = -BOX_MAX; i <= BOX_MAX; i++) {
12546b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (mBoxes.get(i).mAnimationKind == ANIM_KIND_DELETE) {
12556b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                return true;
12566b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            }
12576b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        }
12586b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang        return false;
12596b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang    }
12606b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang
1261b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1262b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Private utilities
1263b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1264b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1265b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getMinimalScale(Box b) {
1266bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float wFactor = 1.0f;
1267bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float hFactor = 1.0f;
1268bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int viewW, viewH;
1269bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1270b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang        if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1271b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang                && b == mBoxes.get(0)) {
1272bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mConstrainedFrame.width();
1273bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mConstrainedFrame.height();
1274bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else {
1275bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mViewW;
1276bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mViewH;
1277bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1278bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1279bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mFilmMode) {
1280bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mViewH > mViewW) {  // portrait
1281bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_PORTRAIT_WIDTH;
1282bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1283bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {  // landscape
1284bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1285bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1286bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1287bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1288bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1289bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float s = Math.min(wFactor * viewW / b.mImageW,
1290bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor * viewH / b.mImageH);
1291bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return Math.min(SCALE_LIMIT, s);
1292b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1293ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1294bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private float getMaximalScale(Box b) {
1295ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        if (mFilmMode) return getMinimalScale(b);
1296ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1297ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return SCALE_LIMIT;
1298b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1299b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1300b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static boolean isAlmostEqual(float a, float b) {
1301b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float diff = a - b;
1302b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (diff < 0 ? -diff : diff) < 0.02f;
1303ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1304ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1305bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Calculates the stable region of mPlatform.mCurrentX and
1306bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // mBoxes.get(0).mCurrentY, where "stable" means
1307b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1308b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (1) If the dimension of scaled image >= view dimension, we will not
1309b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // see black region outside the image (at that dimension).
1310b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (2) If the dimension of scaled image < view dimension, we will center
1311b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // the scaled image.
1312b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1313b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // We might temporarily go out of this stable during user interaction,
1314b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // but will "snap back" after user stops interaction.
1315b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1316b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // The results are stored in mBound{Left/Right/Top/Bottom}.
1317b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
13188f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // An extra parameter "horizontalSlack" (which has the value of 0 usually)
13198f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // is used to extend the stable region by some pixels on each side
13208f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // horizontally.
1321b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale, int horizontalSlack) {
1322b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
13238f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang
1324b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The width and height of the box in number of view pixels
1325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b, scale);
1326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b, scale);
1327b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // When the edge of the view is aligned with the edge of the box
1329bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1330bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1331bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1332bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundBottom = h / 2 - mViewH / 2;
1333b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1334b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // If the scaled height is smaller than the view height,
1335b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // force it to be in the center.
1336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewTallerThanScaledImage(scale)) {
1337bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundTop = mBoundBottom = 0;
1338b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
1339b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1340b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // Same for width
13412ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if (viewWiderThanScaledImage(scale)) {
1342bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundLeft = mBoundRight = mPlatform.mDefaultX;
1343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale) {
1347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(scale, 0);
1348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean viewTallerThanScaledImage(float scale) {
1351b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewH >= heightOf(mBoxes.get(0), scale);
13522ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
13532ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
13542ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    private boolean viewWiderThanScaledImage(float scale) {
1355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewW >= widthOf(mBoxes.get(0), scale);
13562ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
13572ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
1358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getTargetScale(Box b) {
1359192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang        return b.mAnimationStartTime == NO_ANIMATION
1360192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                ? b.mCurrentScale : b.mToScale;
1361ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1362ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1363b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1364b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Animatable: an thing which can do animation.
1365b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1366b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private abstract static class Animatable {
1367b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public long mAnimationStartTime;
1368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationKind;
1369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationDuration;
1370b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
13718b930828c7a33d668876972c22a515e9163592f7Pin Ting        // This should be overridden in subclass to change the animation values
1372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // give the progress value in [0, 1].
1373b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected abstract boolean interpolate(float progress);
1374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public abstract boolean startSnapback();
1375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1376b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Returns true if the animation values changes, so things need to be
1377b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // redrawn.
1378b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean advanceAnimation() {
1379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == NO_ANIMATION) {
1380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1382b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == LAST_ANIMATION) {
1383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = NO_ANIMATION;
1384b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return startSnapback();
1385b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1386b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1387b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float progress;
1388b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationDuration == 0) {
1389b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1390b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1391b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                long now = AnimationTime.get();
1392b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress =
1393b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    (float) (now - mAnimationStartTime) / mAnimationDuration;
1394b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1395b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1396b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1397b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1398b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1399b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = applyInterpolationCurve(mAnimationKind, progress);
1400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1401ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1402b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            boolean done = interpolate(progress);
1403ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1404b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (done) {
1405b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = LAST_ANIMATION;
1406b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1407ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1409ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1410ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1411b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private static float applyInterpolationCurve(int kind, float progress) {
1412b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float f = 1 - progress;
1413b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            switch (kind) {
1414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCROLL:
1415b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_FLING:
14166b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                case ANIM_KIND_FLING_X:
14176b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                case ANIM_KIND_DELETE:
14182c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                case ANIM_KIND_CAPTURE:
1419b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f;  // linear
1420b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
142102ef48574ef7099ebaa0386bd6fd676bd2fd5489Bobby Georgescu                case ANIM_KIND_OPENING:
1422b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCALE:
1423b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f;  // quadratic
1424b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1425b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SNAPBACK:
1426b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_ZOOM:
1427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SLIDE:
1428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f * f * f * f; // x^5
1429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress;
1432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1433ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1434ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1435b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1436bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //  Platform: captures the global X/Y movement.
1437b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1438b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Platform extends Animatable {
1439bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentX, mFromX, mToX, mDefaultX;
1440bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentY, mFromY, mToY, mDefaultY;
1441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mFlingOffset;
1442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
14476b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    && mListener.isHoldingDown()) return false;
1448192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang            if (mInScale) return false;
1449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMin = mExtraScalingRange ?
1452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMax = mExtraScalingRange ?
1454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int x = mCurrentX;
1457bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int y = mDefaultY;
1458b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mFilmMode) {
14599b270f4f2e20fb0f0455529c2e20990173a75470Bobby Georgescu                x = mDefaultX;
1460b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1461b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                calculateStableBound(scale, HORIZONTAL_SLACK);
1462192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // If the picture is zoomed-in, we want to keep the focus point
1463192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // stay in the same position on screen, so we need to adjust
1464192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // target mCurrentX (which is the center of the focused
1465192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // box). The position of the focus point on screen (relative the
1466192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // the center of the view) is:
1467192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                //
1468192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // mCurrentX + scale * mFocusX = mCurrentX' + scale' * mFocusX
1469192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                // => mCurrentX' = mCurrentX + (scale - scale') * mFocusX
1470192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                //
1471192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                if (!viewWiderThanScaledImage(scale)) {
1472192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    float scaleDiff = b.mCurrentScale - scale;
1473192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    x += (int) (mFocusX * scaleDiff + 0.5f);
1474192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                }
1475b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                x = Utils.clamp(x, mBoundLeft, mBoundRight);
1476b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1477bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX != x || mCurrentY != y) {
1478bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return doAnimation(x, y, ANIM_KIND_SNAPBACK);
1479b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1480b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
1481b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1482b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1483bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // The updateDefaultXY() should be called whenever these variables
1484bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1485bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // mFilmMode
1486bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateDefaultXY() {
1487bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // We don't check mFilmMode and return 0 for mDefaultX. Because
1488bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // otherwise if we decide to leave film mode because we are
1489bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // centered, we will immediately back into film mode because we find
1490bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // we are not centered.
1491bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mConstrained && !mConstrainedFrame.isEmpty()) {
1492bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1493bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = mFilmMode ? 0 :
1494bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        mConstrainedFrame.centerY() - mViewH / 2;
1495bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
1496bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = 0;
1497bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = 0;
1498bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1499bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1500bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1501b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for the platform.
1502bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        private boolean doAnimation(int targetX, int targetY, int kind) {
1503bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX == targetX && mCurrentY == targetY) return false;
1504b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1505b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromX = mCurrentX;
1506bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mFromY = mCurrentY;
1507b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToX = targetX;
1508bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mToY = targetY;
1509b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1510b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1511b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFlingOffset = 0;
1512b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1513b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1514b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1515b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1516b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1517b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1518b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
15196b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                return interpolateFlingPage(progress);
15206b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            } else if (mAnimationKind == ANIM_KIND_FLING_X) {
15216b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                return interpolateFlingFilm(progress);
1522b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1523b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1524b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1526b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1527b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingFilm(float progress) {
1528b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFilmScroller.computeScrollOffset();
1529b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1530b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1531b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int dir = EdgeView.INVALID_DIRECTION;
1532bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX < mDefaultX) {
1533fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasNext) {
1534b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.RIGHT;
1535b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1536bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else if (mCurrentX > mDefaultX) {
1537fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasPrev) {
1538b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.LEFT;
1539b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1540b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1541b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (dir != EdgeView.INVALID_DIRECTION) {
1542b27df4650459068b409924493bbadaf25bb7e191Bobby Georgescu                // TODO: restore this onAbsorb call
1543b27df4650459068b409924493bbadaf25bb7e191Bobby Georgescu                //int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1544b27df4650459068b409924493bbadaf25bb7e191Bobby Georgescu                //mListener.onAbsorb(v, dir);
1545b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mFilmScroller.forceFinished(true);
1546bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = mDefaultX;
1547b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1548b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return mFilmScroller.isFinished();
1549b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1550b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1551b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1552b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1553b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1554b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(b.mCurrentScale);
1555b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1556b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldX = mCurrentX;
1557b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mPageScroller.getCurrX();
1558b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1559b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1560b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1561b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1562b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.RIGHT);
1563b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1564b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1565b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.LEFT);
1566b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1567b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1568b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1569b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1570b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1571b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1572b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Other animations
1573b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1574b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentX = mToX;
1575bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = mToY;
1576b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1577b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
15782c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
15792c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    progress = CaptureAnimation.calculateSlide(progress);
1580bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                }
1581bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1582bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1583bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
15842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
15852c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
1586bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    return (mCurrentX == mToX && mCurrentY == mToY);
15872c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1588b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1589b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1590ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1591532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
1592b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1593b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box: represents a rectangular area which shows a picture.
1594b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1595b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Box extends Animatable {
1596b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Size of the bitmap
1597b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mImageW, mImageH;
1598b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1599b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This is true if we assume the image size is the same as view size
1600b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // until we know the actual size of image. This is also used to
1601b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // determine if there is an image ready to show.
1602b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean mUseViewSize;
1603b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1604b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The minimum and maximum scale we allow for this box.
1605b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mScaleMin, mScaleMax;
1606b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1607b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The X/Y value indicates where the center of the box is on the view
1608b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1609b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // actual values used currently. Note that the X values are implicitly
1610b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // defined by Platform and Gaps.
1611b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentY, mFromY, mToY;
1612b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mCurrentScale, mFromScale, mToScale;
1613b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1614b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The absolute X coordinate of the center of the box. This is only used
1615b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // during moveBox().
1616b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAbsoluteX;
1617b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1618b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1619b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1620b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1621b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
16226b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    && mListener.isHoldingDown()) return false;
16236b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang            if (mAnimationKind == ANIM_KIND_DELETE
16246b891c6a3739f8c49d42f9db6fc76cb92c7c5f25Chih-Chung Chang                    && mListener.isHoldingDelete()) return false;
1625b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mInScale && this == mBoxes.get(0)) return false;
1626b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1627192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang            int y = mCurrentY;
1628b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale;
1629b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1630b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (this == mBoxes.get(0)) {
1631b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMin = mExtraScalingRange ?
1632b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1633b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMax = mExtraScalingRange ?
1634b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1635b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (mFilmMode) {
1637bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    y = 0;
1638b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                } else {
1639b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    calculateStableBound(scale, HORIZONTAL_SLACK);
1640192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    // If the picture is zoomed-in, we want to keep the focus
1641192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    // point stay in the same position on screen. See the
1642192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    // comment in Platform.startSnapback for details.
1643192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    if (!viewTallerThanScaledImage(scale)) {
1644192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                        float scaleDiff = mCurrentScale - scale;
1645192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                        y += (int) (mFocusY * scaleDiff + 0.5f);
1646192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    }
1647192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    y = Utils.clamp(y, mBoundTop, mBoundBottom);
1648b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1649b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1650bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                y = 0;
1651b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = mScaleMin;
1652b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1653b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1654b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mCurrentY != y || mCurrentScale != scale) {
1655b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1656b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1657b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
16582ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1659b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1660b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean doAnimation(int targetY, float targetScale, int kind) {
1661192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang            targetScale = clampScale(targetScale);
1662b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
16632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentY == targetY && mCurrentScale == targetScale
16642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && kind != ANIM_KIND_CAPTURE) {
1665b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1666b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1667b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1668b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Now starts an animation for the box.
1669b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1670b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromY = mCurrentY;
1671b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromScale = mCurrentScale;
1672b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToY = targetY;
1673b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToScale = targetScale;
1674b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1675b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1676b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1677b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
16782ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1679b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1680192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang        // Clamps the input scale to the range that doAnimation() can reach.
1681192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang        public float clampScale(float s) {
1682192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang            return Utils.clamp(s,
1683192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    SCALE_MIN_EXTRA * mScaleMin,
1684192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang                    SCALE_MAX_EXTRA * mScaleMax);
1685192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang        }
1686192b7f1ddf08e7bfa0c65dbf61edb81ebb5a0998Chih-Chung Chang
1687b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1688b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1689b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
1690b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateFlingPage(progress);
1691b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1692b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1693b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
16942ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1695b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1696b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1697b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1698b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(mCurrentScale);
1699b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1700b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldY = mCurrentY;
1701b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentY = mPageScroller.getCurrY();
1702b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1703b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1704b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1705b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.BOTTOM);
1707b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1708b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1709b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.TOP);
1710b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1711b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1712b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1713b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1714b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1715b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1716b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1717b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = mToY;
1718b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mToScale;
1719b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1720b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1721b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1722b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
17232c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
17242c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
17252c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentScale *= f;
17262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
17272c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
17282c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentY == mToY && mCurrentScale == mToScale);
17292c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1730b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1731b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1732b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1733b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1734b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1735b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Gap: represents a rectangular area which is between two boxes.
1736b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1737b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Gap extends Animatable {
1738b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The default gap size between two boxes. The value may vary for
1739b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // different image size of the boxes and for different modes (page or
1740b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // film).
1741b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mDefaultSize;
1742b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1743b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The gap size between the two boxes.
1744b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentGap, mFromGap, mToGap;
1745b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1746b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1747b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1748b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
17492c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
1750b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1751b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1752b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for a gap.
17532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean doAnimation(int targetSize, int kind) {
17542c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
17552c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                return false;
17562c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            }
17572c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mAnimationKind = kind;
1758b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromGap = mCurrentGap;
1759b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToGap = targetSize;
1760b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1761b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
1762b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1763b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1765b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1766b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = mToGap;
1770b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1771b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1772b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
17732c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
17742c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
17752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentGap = (int) (mCurrentGap * f);
17762c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
17772c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
17782c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentGap == mToGap);
17792c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1780b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
17812ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1782532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
1783ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1784ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1785ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    //  FilmRatio: represents the progress of film mode change.
1786ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1787ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private class FilmRatio extends Animatable {
1788ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // The film ratio: 1 means switching to film mode is complete, 0 means
1789ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // switching to page mode is complete.
1790ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        public float mCurrentRatio, mFromRatio, mToRatio;
1791ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1792ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        @Override
1793ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        public boolean startSnapback() {
1794ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float target = mFilmMode ? 1f : 0f;
1795ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            if (target == mToRatio) return false;
1796ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            return doAnimation(target, ANIM_KIND_SNAPBACK);
1797ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1798ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1799ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Starts an animation for the film ratio.
1800ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        private boolean doAnimation(float targetRatio, int kind) {
1801ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationKind = kind;
1802ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mFromRatio = mCurrentRatio;
1803ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mToRatio = targetRatio;
1804ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1805ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
1806ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            advanceAnimation();
1807ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            return true;
1808ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1809ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1810ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        @Override
1811ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        protected boolean interpolate(float progress) {
1812ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            if (progress >= 1) {
1813ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                mCurrentRatio = mToRatio;
1814ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                return true;
1815ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            } else {
1816ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1817ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                return (mCurrentRatio == mToRatio);
1818ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            }
1819ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1820ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1821ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang}
1822