PositionController.java revision ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3
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;
22d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Changimport android.widget.OverScroller;
23ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
242b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.common.Utils;
252b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.GalleryUtils;
26b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeArray;
27b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeIntArray;
282b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin
29ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changclass PositionController {
30b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final String TAG = "PositionController";
31b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
322ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_LEFT_EDGE = 1;
332ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_RIGHT_EDGE = 2;
342ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_TOP_EDGE = 4;
352ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    public static final int IMAGE_AT_BOTTOM_EDGE = 8;
362ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
37b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Special values for animation time.
38ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long NO_ANIMATION = -1;
39ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long LAST_ANIMATION = -2;
40ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
41b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SCROLL = 0;
42b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SCALE = 1;
43b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SNAPBACK = 2;
44b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_SLIDE = 3;
45b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_ZOOM = 4;
46b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_OPENING = 5;
47b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_KIND_FLING = 6;
482c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private static final int ANIM_KIND_CAPTURE = 7;
49ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
50676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Animation time in milliseconds. The order must match ANIM_KIND_* above.
51b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int ANIM_TIME[] = {
52676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        0,    // ANIM_KIND_SCROLL
53676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        50,   // ANIM_KIND_SCALE
54676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        600,  // ANIM_KIND_SNAPBACK
55676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        400,  // ANIM_KIND_SLIDE
56676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        300,  // ANIM_KIND_ZOOM
57b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        600,  // ANIM_KIND_OPENING
58b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        0,    // ANIM_KIND_FLING (the duration is calculated dynamically)
592c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        800,  // ANIM_KIND_CAPTURE
60676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    };
61676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
62ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // We try to scale up the image to fill the screen. But in order not to
63ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // scale too much for small icons, we limit the max up-scaling factor here.
64ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final float SCALE_LIMIT = 4;
65ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
66b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // For user's gestures, we give a temporary extra scaling range which goes
67b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // above or below the usual scaling limits.
68b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final float SCALE_MIN_EXTRA = 0.7f;
69534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private static final float SCALE_MAX_EXTRA = 1.4f;
70534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
71b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Setting this true makes the extra scaling range permanent (until this is
72b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // set to false again).
73534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private boolean mExtraScalingRange = false;
74676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
75b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
76b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mFilmMode = false;
77b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
78642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    // These are the limits for width / height of the picture in film mode.
79642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
80642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
81642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
82642561dd3177f9511c8bbeb90839066ecc451054Chih-Chung Chang    private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
83b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
84b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // In addition to the focused box (index == 0). We also keep information
85b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // about this many boxes on each side.
86b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
87ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
88fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
89b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
90ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
91b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private Listener mListener;
92b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private volatile Rect mOpenAnimationRect;
93ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
94ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // Use a large enough value, so we won't see the gray shadown in the beginning.
95ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private int mViewW = 1200;
96ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private int mViewH = 1200;
97ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
98b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // A scaling guesture is in progress.
99b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mInScale;
100b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The focus point of the scaling gesture, relative to the center of the
101b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // picture in bitmap pixels.
102b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float mFocusX, mFocusY;
103ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
104fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    // whether there is a previous/next picture.
105fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private boolean mHasPrev, mHasNext;
106fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang
107b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (page mode).
108b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private FlingScroller mPageScroller;
109ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
110b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (film mode).
111d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang    private OverScroller mFilmScroller;
112ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The bound of the stable region that the focused box can stay, see the
114b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // comments above calculateStableBound() for details.
115b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
116ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
117bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Constrained frame is a rectangle that the focused box should fit into if
118bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // it is constrained. It has two effects:
119bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
120bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (1) In page mode, if the focused box is constrained, scaling for the
121bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box is adjusted to fit into the constrained frame, instead of the
122bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // whole view.
123bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
124bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (2) In page mode, if the focused box is constrained, the mPlatform's
125bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // default center (mDefaultX/Y) is moved to the center of the constrained
126bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // frame, instead of the view center.
127bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
128bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mConstrainedFrame = new Rect();
129bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
130bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Whether the focused box is constrained.
131bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
132bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Our current program's first call to moveBox() sets constrained = true, so
133bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // we set the initial value of this variable to true, and we will not see
134bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // see unwanted transition animation.
135bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private boolean mConstrained = true;
136bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________________________________________________________
139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |   _____       _____       _____       _____       _____   |
140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |     |     |     |     |     |     |     |     |     |  |
141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  | Box |     | Box |     | Box*|     | Box |     | Box |  |
142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |_____|.....|_____|.....|_____|.....|_____|.....|_____|  |
143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |          Gap         Gap         Gap         Gap          |
144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________________________________________________________|
145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
146b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                       <--  Platform  -->
147b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
148bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
149ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private Platform mPlatform = new Platform();
151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
152b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The gap at the right of a Box i is at index i. The gap at the left of a
153b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Box i is at index i - 1.
154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
155ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private FilmRatio mFilmRatio = new FilmRatio();
156ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
157b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // These are only used during moveBox().
158b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
159fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private RangeArray<Gap> mTempGaps =
160fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
161ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
162b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The output of the PositionController. Available throught getPosition().
163b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
164b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
165b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public interface Listener {
166b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void invalidate();
1672c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        boolean isHolding();
168ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // EdgeView
170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onPull(int offset, int direction);
171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onRelease();
172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onAbsorb(int velocity, int direction);
173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
174ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
175b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public PositionController(Context context, Listener listener) {
176b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener = listener;
177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller = new FlingScroller();
178bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mFilmScroller = new OverScroller(context,
179bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                null /* default interpolator */, false /* no flywheel */);
180b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Initialize the areas.
182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
184b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, new Box());
185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(i);
186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mRects.put(i, new Rect());
187b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
188b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, new Gap());
190b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initGap(i);
191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
192ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
193ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
194b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setOpenAnimationRect(Rect r) {
195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = r;
196b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
197676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setViewSize(int viewW, int viewH) {
199b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewW == mViewW && viewH == mViewH) return;
200ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
201b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewW = viewW;
202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewH = viewH;
203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
204ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            setBoxSize(i, viewW, viewH, true);
207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
208ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
211ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
212ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
213bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setConstrainedFrame(Rect f) {
214bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mConstrainedFrame.equals(f)) return;
215bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mConstrainedFrame.set(f);
216bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
217bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        updateScaleAndGapLimit();
218bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        snapAndRedraw();
219bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
220bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
221bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setImageSize(int index, int width, int height, boolean force) {
222bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (force) {
223bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            Box b = mBoxes.get(index);
224bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mImageW = width;
225bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mImageH = height;
226bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            return;
227bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
228bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (width == 0 || height == 0) {
230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(index);
2312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (!setBoxSize(index, width, height, false)) {
2322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return;
233b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
234ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
235b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
236b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startOpeningAnimationIfNeeded();
237b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
238ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
239ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2402c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Returns false if the box size doesn't change.
2412c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
242b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
243d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        boolean wasViewSize = b.mUseViewSize;
244d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang
245d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // If we already have an image size, we don't want to use the view size.
2462c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (!wasViewSize && isViewSize) return false;
247ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
248b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = isViewSize;
249ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
250b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (width == b.mImageW && height == b.mImageH) {
2512c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return false;
252676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        }
253676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
254b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The ratio of the old size and the new size.
255b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float ratio = Math.min(
256b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                (float) b.mImageW / width, (float) b.mImageH / height);
257b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
258bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageW = width;
259bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageH = height;
260bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
261d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // If this is the first time we receive an image size, we change the
262d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // scale directly. Otherwise adjust the scales by a ratio, and snapback
263d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // will animate the scale into the min/max bounds if necessary.
264d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        if (wasViewSize && !isViewSize) {
265bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mCurrentScale = getMinimalScale(b);
266d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
267d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        } else {
268d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mCurrentScale *= ratio;
269d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mFromScale *= ratio;
270d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mToScale *= ratio;
271d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        }
272676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
273b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
274b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusX /= ratio;
275b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusY /= ratio;
276ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
2772c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
2782c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        return true;
279ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
280ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
281b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void startOpeningAnimationIfNeeded() {
282b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mOpenAnimationRect == null) return;
283b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
284b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mUseViewSize) return;
285b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
286b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Start animation from the saved rectangle if we have one.
287b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mOpenAnimationRect;
288b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = null;
289bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = r.centerX() - mViewW / 2;
290bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = r.centerY() - mViewH / 2;
291b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
292b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                r.height() / (float) b.mImageH);
293bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
294bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_OPENING);
295ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
296ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
297b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setFilmMode(boolean enabled) {
298b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (enabled == mFilmMode) return;
299b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmMode = enabled;
300b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
301bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
302b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
303b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        stopAnimation();
304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
305ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
306ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
307b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setExtraScalingRange(boolean enabled) {
308b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mExtraScalingRange == enabled) return;
309b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mExtraScalingRange = enabled;
310b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!enabled) {
311b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            snapAndRedraw();
312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
313ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
314ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This should be called whenever the scale range of boxes or the default
316b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // gap size may change. Currently this can happen due to change of view
317bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void updateScaleAndGapLimit() {
319b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
320b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
321bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMin = getMinimalScale(b);
322bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMax = getMaximalScale(b);
323b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
324ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
327b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mDefaultSize = getDefaultGapSize(i);
328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
330676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
331b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the default gap size according the the size of the boxes around
332b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // the gap and the current mode.
333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getDefaultGapSize(int i) {
334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return IMAGE_GAP;
335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box a = mBoxes.get(i);
336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i + 1);
337b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
338ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
339ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
340b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Here is how we layout the boxes in the page mode.
341b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
342b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   previous             current             next
343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________       ________________     __________
344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  _______  |     |   __________   |   |  ______  |
345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       | |     |  |   right->|  |   | |      | |
346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       |<-------->|<--left   |  |   | |      | |
347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |_______| |  |  |  |__________|  |   | |______| |
348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________|  |  |________________|   |__________|
349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |  <--> gapToSide()
350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |
351b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
352b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int gapToSide(Box b) {
353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
354ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
355ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
356b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Stop all animations at where they are now.
357b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void stopAnimation() {
358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
361b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
362b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
363b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
364534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang        }
365534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    }
366534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
367b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void skipAnimation() {
368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mCurrentX = mPlatform.mToX;
370bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.mCurrentY = mPlatform.mToY;
371b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mAnimationStartTime = NO_ANIMATION;
372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
373b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (b.mAnimationStartTime == NO_ANIMATION) continue;
376b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentY = b.mToY;
377b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentScale = b.mToScale;
378b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
382b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (g.mAnimationStartTime == NO_ANIMATION) continue;
383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mCurrentGap = g.mToGap;
384b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mAnimationStartTime = NO_ANIMATION;
385b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
386b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
387ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
388ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
3892c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void snapback() {
390b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
391ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
392ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
393b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
394b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Start an animations for the focused box
395b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
396b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
397b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void zoomIn(float tapX, float tapY, float targetScale) {
398bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapX -= mViewW / 2;
399bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapY -= mViewH / 2;
400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
401b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
402b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Convert the tap position to distance to center in bitmap coordinates
403b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
404b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
405b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
406bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int x = (int) (-tempX * targetScale + 0.5f);
407bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = (int) (-tempY * targetScale + 0.5f);
408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
409b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(targetScale);
410b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
411b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
412b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
413b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
415ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
416ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
417b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void resetToFullView() {
418b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
419bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
420b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
421b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
422b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void beginScale(float focusX, float focusY) {
423bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
424bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
425b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
426b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = true;
428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Scales the image by the given factor.
433b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns an out-of-range indicator:
434b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   1 if the intended scale is too large for the stable range.
435b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   0 if the intended scale is in the stable range.
436b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  -1 if the intended scale is too small for the stable range.
437b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int scaleBy(float s, float focusX, float focusY) {
438bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
439bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
440b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // We want to keep the focus point (on the bitmap) the same as when we
444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // begin the scale guesture, that is,
445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // (focusX' - currentX') / scale' = (focusX - currentX) / scale
447b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        s *= getTargetScale(b);
449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, s, ANIM_KIND_SCALE);
452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s < b.mScaleMin) return -1;
453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s > b.mScaleMax) return 1;
454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return 0;
455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
457b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void endScale() {
458b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = false;
459b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
460ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
461ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
4622c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view.
4632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startHorizontalSlide() {
464b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
465bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
4662c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
4672c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
4682c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view with the capture
4692c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // animation. In addition to the sliding, the animation will also scale the
4702c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // the focused box, the specified neighbor box, and the gap between the
4712c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // two. The specified offset should be 1 or -1.
4722c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startCaptureAnimationSlide(int offset) {
4732c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box b = mBoxes.get(0);
4742c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box n = mBoxes.get(offset);  // the neighbor box
4752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Gap g = mGaps.get(offset);  // the gap between the two boxes
4762c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
477bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
478bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_CAPTURE);
479bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
480bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
4812c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
4822c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        redraw();
483ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
484ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
485b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void startScroll(float dx, float dy) {
486b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
487b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
488b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
489b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int x = getTargetX(p) + (int) (dx + 0.5f);
490b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int y = getTargetY(b) + (int) (dy + 0.5f);
491b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
492b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) {
493fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang            scrollToFilm(x, y);
494b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else {
495fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang            scrollToPage(x, y);
496b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
497676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    }
498676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
499fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private void scrollToPage(int x, int y) {
500b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
501532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
502b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
503532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
504532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Vertical direction: If we have space to move in the vertical
505532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // direction, we show the edge effect when scrolling reaches the edge.
506532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (mBoundTop != mBoundBottom) {
507532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            if (y < mBoundTop) {
508b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
509532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            } else if (y > mBoundBottom) {
510b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(y - mBoundBottom, EdgeView.TOP);
511532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            }
512532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
513532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
514532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        y = Utils.clamp(y, mBoundTop, mBoundBottom);
515532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
516532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
517532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // tries to go left of the first image or go right of the last image.
518fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        if (!mHasPrev && x > mBoundRight) {
519b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = x - mBoundRight;
520b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.LEFT);
521532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            x = mBoundRight;
522fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        } else if (!mHasNext && x < mBoundLeft) {
523b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = mBoundLeft - x;
524b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.RIGHT);
525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            x = mBoundLeft;
526b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
527b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
528b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
529b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
530b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
531fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private void scrollToFilm(int x, int y) {
532b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
533b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
534b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
535b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // tries to go left of the first image or go right of the last image.
536bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x -= mPlatform.mDefaultX;
537bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (!mHasPrev && x > 0) {
538bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(x, EdgeView.LEFT);
539bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
540bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else if (!mHasNext && x < 0) {
541bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(-x, EdgeView.RIGHT);
542bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
543532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
544bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x += mPlatform.mDefaultX;
545b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
546532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
547532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
548b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    public boolean fling(float velocityX, float velocityY) {
549b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int vx = (int) (velocityX + 0.5f);
550b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int vy = (int) (velocityY + 0.5f);
551b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
552b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
553b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
554b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean flingPage(int velocityX, int velocityY) {
555b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
556b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
557b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
558b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // We only want to do fling when the picture is zoomed-in.
559b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewWiderThanScaledImage(b.mCurrentScale) &&
560b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            viewTallerThanScaledImage(b.mCurrentScale)) {
5612ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            return false;
5622ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
5632ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
5642ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // We only allow flinging in the directions where it won't go over the
5652ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // picture.
5662ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        int edges = getImageAtEdges();
5672ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
5682ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
5692ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityX = 0;
5702ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
5712ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
5722ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
5732ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityY = 0;
5742ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
575b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
576b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX == 0 && velocityY == 0) return false;
577b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
578b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
579b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
580b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mPageScroller.getFinalX();
581b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = mPageScroller.getFinalY();
582b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
583b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
584b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return true;
585b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
586b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
587b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean flingFilm(int velocityX, int velocityY) {
588b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
589b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
590b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
591b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we are already at the edge, don't start the fling.
592bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int defaultX = p.mDefaultX;
593bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if ((!mHasPrev && p.mCurrentX >= defaultX)
594bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                || (!mHasNext && p.mCurrentX <= defaultX)) {
595b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
5966068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang        }
597ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
598b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX == 0) return false;
599ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
600b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
601b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
602b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mFilmScroller.getFinalX();
603c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // This value doesn't matter because we use mFilmScroller.isFinished()
604c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // to decide when to stop. We set this to 0 so it's faster for
605c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Animatable.advanceAnimation() to calculate the progress (always 1).
606c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        ANIM_TIME[ANIM_KIND_FLING] = 0;
607b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
608b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
609b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
610ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
611b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
612b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Redraw
613b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
614b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method changes box positions directly, redraw()
615b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  should be called.
616b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
617b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method may also cause a snapback to happen, snapAndRedraw() should
618b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
619b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
620b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method starts an animation to change the position of focused box,
621b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  startAnimation() should be called.
622b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
623b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If time advances to change the box position, advanceAnimation() should
624b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
625b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
626b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void redraw() {
627b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
628b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener.invalidate();
629b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
630b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
631b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void snapAndRedraw() {
632b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.startSnapback();
633b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
634b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).startSnapback();
635b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
637b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).startSnapback();
638ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
639ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        mFilmRatio.startSnapback();
640b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
641b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
642ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
643b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void startAnimation(int targetX, int targetY, float targetScale,
644b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int kind) {
645b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
646bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
647b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
648b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
649b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
650b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
651b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang    public void advanceAnimation() {
652b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
653b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mPlatform.advanceAnimation();
654b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
655b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mBoxes.get(i).advanceAnimation();
656b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
657b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
658b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mGaps.get(i).advanceAnimation();
659b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
660ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        changed |= mFilmRatio.advanceAnimation();
661b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
662ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
663ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
664b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
665b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Layout
666b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
667b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
668b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box.
669b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b) {
670b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * b.mCurrentScale + 0.5f);
671b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
672b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
673b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box.
674b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b) {
675b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * b.mCurrentScale + 0.5f);
676b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
677b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
678b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box, using the given scale.
679b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b, float scale) {
680b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * scale + 0.5f);
681b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
682b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
683b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box, using the given scale.
684b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b, float scale) {
685b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * scale + 0.5f);
686b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
687b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
688b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Convert the information in mPlatform and mBoxes to mRects, so the user
689b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // can get the position of each box by getPosition().
690b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
691b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Note the loop index goes from inside-out because each box's X coordinate
692b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // is relative to its anchor box (except the focused box).
693b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void layoutAndSetPosition() {
694b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // layout box 0 (focused box)
695b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        convertBoxToRect(0);
696b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 1; i <= BOX_MAX; i++) {
697b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // layout box i and -i
698b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            convertBoxToRect(i);
699b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            convertBoxToRect(-i);
700ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
701b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //dumpState();
702b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
703ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
704b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpState() {
705b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
707ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
708ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
709b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        dumpRect(0);
710b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 1; i <= BOX_MAX; i++) {
711b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            dumpRect(i);
712b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            dumpRect(-i);
713b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
714b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
715b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
716b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            for (int j = i + 1; j <= BOX_MAX; j++) {
717b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (Rect.intersects(mRects.get(i), mRects.get(j))) {
718b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
719b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
720ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            }
721b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
722b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
723b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
724b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpRect(int i) {
725b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder sb = new StringBuilder();
726b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
727b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("Rect " + i + ":");
728b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("(");
729b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerX());
730b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(",");
731b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerY());
732b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(") [");
733b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.width());
734b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("x");
735b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.height());
736b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("]");
737b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, sb.toString());
738b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
739b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
740b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void convertBoxToRect(int i) {
741b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
742b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
743bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
744b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b);
745b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b);
746b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
747bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int x = mPlatform.mCurrentX + mViewW / 2;
748b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = x - w / 2;
749b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
750b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (i > 0) {
751b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i - 1);
752b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i - 1);
753b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = a.right + g.mCurrentGap;
754b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
755b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else {  // i < 0
756b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i + 1);
757b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
758b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = a.left - g.mCurrentGap;
759b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = r.right - w;
760b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
761b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.top = y - h / 2;
762b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.bottom = r.top + h;
763b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
765b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the position of a box.
766b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public Rect getPosition(int index) {
767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mRects.get(index);
768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
770b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
771b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box management
772b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
773b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
774b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize the platform to be at the view center.
775b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initPlatform() {
776bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
777bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = mPlatform.mDefaultX;
778bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentY = mPlatform.mDefaultY;
779b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
780b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
781b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
782b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a box to have the size of the view.
783b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initBox(int index) {
784b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(index);
785b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageW = mViewW;
786b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageH = mViewH;
787b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = true;
788bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMin = getMinimalScale(b);
789bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMax = getMaximalScale(b);
790bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = 0;
791b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = b.mScaleMin;
792b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mAnimationStartTime = NO_ANIMATION;
793b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
794b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
795b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a gap. This can only be called after the boxes around the gap
796b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // has been initialized.
797b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index) {
798b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
799b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
800b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = g.mDefaultSize;
801b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
802b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
803b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
804b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index, int size) {
805b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
806b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
807b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = size;
808b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
809b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
810b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
811b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void debugMoveBox(int fromIndex[]) {
812b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder s = new StringBuilder("moveBox:");
813b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < fromIndex.length; i++) {
814b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = fromIndex[i];
815b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) {
816b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" N");
817b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else {
818b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" ");
819b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(fromIndex[i]);
820b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            }
821ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
822b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, s.toString());
8236068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang    }
8246068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang
825b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Move the boxes: it may indicate focus change, box deleted, box appearing,
826b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // box reordered, etc.
827b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
828b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Each element in the fromIndex array indicates where each box was in the
829b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
830b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // means the box is new.
831b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
832b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // For example:
833b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // N N N N N N N -- all new boxes
834b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 0 1 2 3 -- nothing changed
835b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -2 -1 0 1 2 3 N -- focus goes to the next box
8362c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
837b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 1 2 3 N -- the focused box was deleted.
838bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
839bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // hasPrev/hasNext indicates if there are previous/next boxes for the
840bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box. constrained indicates whether the focused box should be put
841bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // into the constrained frame.
842bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
843bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean constrained) {
844b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //debugMoveBox(fromIndex);
845fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasPrev = hasPrev;
846fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasNext = hasNext;
847bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
848b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
849b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
850b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 1. Get the absolute X coordiates for the boxes.
851b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
852b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
853b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
854b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mRects.get(i);
855bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mAbsoluteX = r.centerX() - mViewW / 2;
856ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
857ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
858b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 2. copy boxes and gaps to temporary storage.
859b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
860b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(i, mBoxes.get(i));
861b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, null);
862b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
863b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
864b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempGaps.put(i, mGaps.get(i));
865b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, null);
866b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
867b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
868b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 3. move back boxes that are used in the new array.
869b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
870b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
871b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
872b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(j));
873b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(j, null);
874b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
875ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
876b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 4. move back gaps if both boxes around it are kept together.
877b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
878b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
879b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
880b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int k = from.get(i + 1);
881b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (k == Integer.MAX_VALUE) continue;
882b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j + 1 == k) {
883b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mGaps.put(i, mTempGaps.get(j));
884b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mTempGaps.put(j, null);
885b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
886b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
887534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
888b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 5. recycle the boxes that are not used in the new array.
889b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int k = -BOX_MAX;
890b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
891b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mBoxes.get(i) != null) continue;
892b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempBoxes.get(k) == null) {
893b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
894b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
895b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(k++));
896b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(i);
897ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
898ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
899b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 6. Now give the recycled box a reasonable absolute X position.
900b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
901b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // First try to find the first and the last box which the absolute X
902b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // position is known.
903b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int first, last;
904b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (first = -BOX_MAX; first <= BOX_MAX; first++) {
905b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(first) != Integer.MAX_VALUE) break;
906b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
907b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (last = BOX_MAX; last >= -BOX_MAX; last--) {
908b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(last) != Integer.MAX_VALUE) break;
909b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
910b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If there is no box has known X position at all, make the focused one
911b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // as known.
912b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (first > BOX_MAX) {
913b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
914b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            first = last = 0;
915b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
916b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Now for those boxes between first and last, just assign the same
9172c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        // position as the next box. (We can do better, but this should be
918b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // rare). For the boxes before first or after last, we will use a new
919b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // default gap size below.
9202c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        for (int i = last - 1; i > first; i--) {
921b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(i) != Integer.MAX_VALUE) continue;
9222c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
923b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
924ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
925b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 7. recycle the gaps that are not used in the new array.
926b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        k = -BOX_MAX;
927b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
928b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mGaps.get(i) != null) continue;
929b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempGaps.get(k) == null) {
930b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
931b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
932b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, mTempGaps.get(k++));
933b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box a = mBoxes.get(i);
934b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i + 1);
935b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wa = widthOf(a);
936b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wb = widthOf(b);
937b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (i >= first && i < last) {
938b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
939b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i, g);
940b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
941b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i);
942b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
943ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
944ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
945b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 8. offset the Platform position
946b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
947b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mCurrentX += dx;
948b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFromX += dx;
949b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mToX += dx;
950b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFlingOffset += dx;
951b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
952bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mConstrained != constrained) {
953bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mConstrained = constrained;
954bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.updateDefaultXY();
955bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateScaleAndGapLimit();
956bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
957bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
958b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
959b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
960b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
961b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
962b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Public utilities
963b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
964b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
965b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public boolean isAtMinimalScale() {
966b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
967b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
968b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
969b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
970bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public boolean isCenter() {
971bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        Box b = mBoxes.get(0);
972bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mPlatform.mCurrentX == mPlatform.mDefaultX
973bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            && b.mCurrentY == 0;
974bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
975bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
976b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageWidth() {
977b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
978b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageW;
979b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
980b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
981b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageHeight() {
982b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
983b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageH;
984b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
985b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
986b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public float getImageScale() {
987b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
988b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mCurrentScale;
989b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
990b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
991b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageAtEdges() {
992b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
993b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
994b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
995b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int edges = 0;
996b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX <= mBoundLeft) {
997b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_RIGHT_EDGE;
998ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
999b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX >= mBoundRight) {
1000b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_LEFT_EDGE;
1001b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1002b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY <= mBoundTop) {
1003b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_BOTTOM_EDGE;
1004b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1005b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY >= mBoundBottom) {
1006b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_TOP_EDGE;
1007b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1008b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return edges;
1009b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1010b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
101117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    public boolean isScrolling() {
101217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        return mPlatform.mAnimationStartTime != NO_ANIMATION
101317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                && mPlatform.mCurrentX != mPlatform.mToX;
101417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
101517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
101617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    public void stopScrolling() {
101717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
101817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
101917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
102017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
1021ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    public float getFilmRatio() {
1022ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return mFilmRatio.mCurrentRatio;
1023ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1024ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1025b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1026b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Private utilities
1027b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1028b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1029b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getMinimalScale(Box b) {
1030bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float wFactor = 1.0f;
1031bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float hFactor = 1.0f;
1032bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int viewW, viewH;
1033bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1034b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang        if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1035b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang                && b == mBoxes.get(0)) {
1036bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mConstrainedFrame.width();
1037bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mConstrainedFrame.height();
1038bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else {
1039bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mViewW;
1040bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mViewH;
1041bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1042bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1043bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mFilmMode) {
1044bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mViewH > mViewW) {  // portrait
1045bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_PORTRAIT_WIDTH;
1046bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1047bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {  // landscape
1048bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1049bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1050bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1051bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1052bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1053bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float s = Math.min(wFactor * viewW / b.mImageW,
1054bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor * viewH / b.mImageH);
1055bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return Math.min(SCALE_LIMIT, s);
1056b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1057ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1058bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private float getMaximalScale(Box b) {
1059ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        if (mFilmMode) return getMinimalScale(b);
1060ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
1061ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return SCALE_LIMIT;
1062b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1063b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1064b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static boolean isAlmostEqual(float a, float b) {
1065b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float diff = a - b;
1066b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (diff < 0 ? -diff : diff) < 0.02f;
1067ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1068ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1069bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Calculates the stable region of mPlatform.mCurrentX and
1070bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // mBoxes.get(0).mCurrentY, where "stable" means
1071b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1072b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (1) If the dimension of scaled image >= view dimension, we will not
1073b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // see black region outside the image (at that dimension).
1074b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (2) If the dimension of scaled image < view dimension, we will center
1075b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // the scaled image.
1076b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1077b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // We might temporarily go out of this stable during user interaction,
1078b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // but will "snap back" after user stops interaction.
1079b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1080b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // The results are stored in mBound{Left/Right/Top/Bottom}.
1081b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
10828f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // An extra parameter "horizontalSlack" (which has the value of 0 usually)
10838f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // is used to extend the stable region by some pixels on each side
10848f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // horizontally.
1085b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale, int horizontalSlack) {
1086b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
10878f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang
1088b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The width and height of the box in number of view pixels
1089b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b, scale);
1090b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b, scale);
1091b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1092b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // When the edge of the view is aligned with the edge of the box
1093bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1094bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1095bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1096bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundBottom = h / 2 - mViewH / 2;
1097b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1098b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // If the scaled height is smaller than the view height,
1099b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // force it to be in the center.
1100b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewTallerThanScaledImage(scale)) {
1101bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundTop = mBoundBottom = 0;
1102b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
1103b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1104b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // Same for width
11052ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if (viewWiderThanScaledImage(scale)) {
1106bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundLeft = mBoundRight = mPlatform.mDefaultX;
1107b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1108b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1109b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1110b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale) {
1111b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(scale, 0);
1112b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1114b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean viewTallerThanScaledImage(float scale) {
1115b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewH >= heightOf(mBoxes.get(0), scale);
11162ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
11172ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
11182ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    private boolean viewWiderThanScaledImage(float scale) {
1119b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewW >= widthOf(mBoxes.get(0), scale);
11202ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
11212ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
1122b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getTargetScale(Box b) {
1123b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
1124b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
1125b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1126b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getTargetX(Platform p) {
1127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
1128ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1129ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getTargetY(Box b) {
1131b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
1132ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1133ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean useCurrentValueAsTarget(Animatable a) {
1135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return a.mAnimationStartTime == NO_ANIMATION ||
1136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                a.mAnimationKind == ANIM_KIND_FLING;
1138ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1139ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Animatable: an thing which can do animation.
1142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private abstract static class Animatable {
1144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public long mAnimationStartTime;
1145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationKind;
1146b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationDuration;
1147b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1148b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This should be overidden in subclass to change the animation values
1149b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // give the progress value in [0, 1].
1150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected abstract boolean interpolate(float progress);
1151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public abstract boolean startSnapback();
1152b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1153b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Returns true if the animation values changes, so things need to be
1154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // redrawn.
1155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean advanceAnimation() {
1156b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == NO_ANIMATION) {
1157b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1158b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1159b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == LAST_ANIMATION) {
1160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = NO_ANIMATION;
1161b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return startSnapback();
1162b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1163b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1164b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float progress;
1165b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationDuration == 0) {
1166b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1168b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                long now = AnimationTime.get();
1169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress =
1170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    (float) (now - mAnimationStartTime) / mAnimationDuration;
1171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1175b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1176b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = applyInterpolationCurve(mAnimationKind, progress);
1177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1178ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1179b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            boolean done = interpolate(progress);
1180ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (done) {
1182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = LAST_ANIMATION;
1183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1184ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1186ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1187ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1188b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private static float applyInterpolationCurve(int kind, float progress) {
1189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float f = 1 - progress;
1190b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            switch (kind) {
1191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCROLL:
1192b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_FLING:
11932c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                case ANIM_KIND_CAPTURE:
1194b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f;  // linear
1195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1196b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCALE:
1197b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f;  // quadratic
1198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1199b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SNAPBACK:
1200b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_ZOOM:
1201b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SLIDE:
1202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_OPENING:
1203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f * f * f * f; // x^5
1204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress;
1207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1208ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1209ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1211bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //  Platform: captures the global X/Y movement.
1212b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1213b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Platform extends Animatable {
1214bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentX, mFromX, mToX, mDefaultX;
1215bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentY, mFromY, mToY, mDefaultY;
1216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mFlingOffset;
1217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1219b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1220b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
12222c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && mListener.isHolding()) return false;
1223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1225b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMin = mExtraScalingRange ?
1226b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1227b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMax = mExtraScalingRange ?
1228b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int x = mCurrentX;
1231bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int y = mDefaultY;
1232b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mFilmMode) {
1233bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                int defaultX = mDefaultX;
1234bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (!mHasNext) x = Math.max(x, defaultX);
1235bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (!mHasPrev) x = Math.min(x, defaultX);
1236b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1237b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                calculateStableBound(scale, HORIZONTAL_SLACK);
1238b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                x = Utils.clamp(x, mBoundLeft, mBoundRight);
1239b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1240bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX != x || mCurrentY != y) {
1241bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return doAnimation(x, y, ANIM_KIND_SNAPBACK);
1242b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1243b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
1244b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1245b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1246bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // The updateDefaultXY() should be called whenever these variables
1247bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1248bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // mFilmMode
1249bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateDefaultXY() {
1250bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // We don't check mFilmMode and return 0 for mDefaultX. Because
1251bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // otherwise if we decide to leave film mode because we are
1252bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // centered, we will immediately back into film mode because we find
1253bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // we are not centered.
1254bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mConstrained && !mConstrainedFrame.isEmpty()) {
1255bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1256bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = mFilmMode ? 0 :
1257bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        mConstrainedFrame.centerY() - mViewH / 2;
1258bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
1259bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = 0;
1260bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = 0;
1261bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1262bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1263bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1264b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for the platform.
1265bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        private boolean doAnimation(int targetX, int targetY, int kind) {
1266bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX == targetX && mCurrentY == targetY) return false;
1267b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1268b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromX = mCurrentX;
1269bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mFromY = mCurrentY;
1270b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToX = targetX;
1271bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mToY = targetY;
1272b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1273b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1274b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFlingOffset = 0;
1275b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1276b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1277b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1278b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1279b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1280b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1281b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
1282b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return mFilmMode
1283b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        ? interpolateFlingFilm(progress)
1284b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        : interpolateFlingPage(progress);
1285b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1286b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1287b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1288b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1289b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1290b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingFilm(float progress) {
1291b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFilmScroller.computeScrollOffset();
1292b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1293b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1294b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int dir = EdgeView.INVALID_DIRECTION;
1295bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX < mDefaultX) {
1296fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasNext) {
1297b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.RIGHT;
1298b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1299bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else if (mCurrentX > mDefaultX) {
1300fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasPrev) {
1301b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.LEFT;
1302b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1303b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (dir != EdgeView.INVALID_DIRECTION) {
1305b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1306b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, dir);
1307b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mFilmScroller.forceFinished(true);
1308bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = mDefaultX;
1309b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1310b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return mFilmScroller.isFinished();
1311b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1313b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1314b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1316b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(b.mCurrentScale);
1317b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldX = mCurrentX;
1319b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mPageScroller.getCurrX();
1320b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1321b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1322b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1323b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1324b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.RIGHT);
1325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1327b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.LEFT);
1328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1330b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1331b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1332b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Other animations
1335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentX = mToX;
1337bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = mToY;
1338b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1339b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
13402c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
13412c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    progress = CaptureAnimation.calculateSlide(progress);
1342bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                }
1343bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1344bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1345bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
13462c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
13472c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
1348bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    return (mCurrentX == mToX && mCurrentY == mToY);
13492c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1351b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1352ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1353532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
1354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box: represents a rectangular area which shows a picture.
1356b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1357b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Box extends Animatable {
1358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Size of the bitmap
1359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mImageW, mImageH;
1360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1361b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This is true if we assume the image size is the same as view size
1362b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // until we know the actual size of image. This is also used to
1363b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // determine if there is an image ready to show.
1364b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean mUseViewSize;
1365b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1366b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The minimum and maximum scale we allow for this box.
1367b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mScaleMin, mScaleMax;
1368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The X/Y value indicates where the center of the box is on the view
1370b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1371b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // actual values used currently. Note that the X values are implicitly
1372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // defined by Platform and Gaps.
1373b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentY, mFromY, mToY;
1374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mCurrentScale, mFromScale, mToScale;
1375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1376b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The absolute X coordinate of the center of the box. This is only used
1377b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // during moveBox().
1378b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAbsoluteX;
1379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1382b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
13842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && mListener.isHolding()) return false;
1385b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mInScale && this == mBoxes.get(0)) return false;
1386b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1387b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int y;
1388b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale;
1389b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1390b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (this == mBoxes.get(0)) {
1391b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMin = mExtraScalingRange ?
1392b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1393b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMax = mExtraScalingRange ?
1394b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1395b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1396b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (mFilmMode) {
1397bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    y = 0;
1398b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                } else {
1399b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    calculateStableBound(scale, HORIZONTAL_SLACK);
1400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1401b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1402b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1403bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                y = 0;
1404b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = mScaleMin;
1405b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1406b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1407b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mCurrentY != y || mCurrentScale != scale) {
1408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1409b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1410b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
14112ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1412b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1413b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean doAnimation(int targetY, float targetScale, int kind) {
1414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            targetScale = Utils.clamp(targetScale,
1415b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    SCALE_MIN_EXTRA * mScaleMin,
1416b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    SCALE_MAX_EXTRA * mScaleMax);
1417b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1418b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // If the scaled height is smaller than the view height, force it to be
1419b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // in the center.  (We do this for height only, not width, because the
1420b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // user may want to scroll to the previous/next image.)
1421b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (!mInScale && viewTallerThanScaledImage(targetScale)) {
1422bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                targetY = 0;
1423b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1424b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
14252c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentY == targetY && mCurrentScale == targetScale
14262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && kind != ANIM_KIND_CAPTURE) {
1427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Now starts an animation for the box.
1431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromY = mCurrentY;
1433b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromScale = mCurrentScale;
1434b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToY = targetY;
1435b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToScale = targetScale;
1436b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1437b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1438b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
14402ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
1445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // Currently a Box can only be flung in page mode.
1446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateFlingPage(progress);
1447b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
14502ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(mCurrentScale);
1455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldY = mCurrentY;
1457b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentY = mPageScroller.getCurrY();
1458b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1459b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1460b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1461b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1462b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.BOTTOM);
1463b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1464b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1465b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.TOP);
1466b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1467b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1468b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1469b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1470b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1471b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1472b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1473b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = mToY;
1474b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mToScale;
1475b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1476b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1477b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1478b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
14792c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
14802c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
14812c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentScale *= f;
14822c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
14832c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
14842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentY == mToY && mCurrentScale == mToScale);
14852c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1486b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1487b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1488b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1489b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1490b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1491b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Gap: represents a rectangular area which is between two boxes.
1492b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1493b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Gap extends Animatable {
1494b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The default gap size between two boxes. The value may vary for
1495b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // different image size of the boxes and for different modes (page or
1496b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // film).
1497b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mDefaultSize;
1498b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1499b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The gap size between the two boxes.
1500b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentGap, mFromGap, mToGap;
1501b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1502b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1503b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1504b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
15052c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
1506b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1507b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1508b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for a gap.
15092c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean doAnimation(int targetSize, int kind) {
15102c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
15112c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                return false;
15122c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            }
15132c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mAnimationKind = kind;
1514b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromGap = mCurrentGap;
1515b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToGap = targetSize;
1516b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1517b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
1518b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1519b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1520b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1521b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1522b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1523b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1524b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = mToGap;
1526b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1527b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1528b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
15292c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
15302c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
15312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentGap = (int) (mCurrentGap * f);
15322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
15332c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
15342c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentGap == mToGap);
15352c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1536b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
15372ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1538532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
1539ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1540ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1541ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    //  FilmRatio: represents the progress of film mode change.
1542ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1543ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private class FilmRatio extends Animatable {
1544ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // The film ratio: 1 means switching to film mode is complete, 0 means
1545ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // switching to page mode is complete.
1546ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        public float mCurrentRatio, mFromRatio, mToRatio;
1547ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1548ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        @Override
1549ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        public boolean startSnapback() {
1550ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float target = mFilmMode ? 1f : 0f;
1551ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            if (target == mToRatio) return false;
1552ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            return doAnimation(target, ANIM_KIND_SNAPBACK);
1553ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1554ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1555ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Starts an animation for the film ratio.
1556ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        private boolean doAnimation(float targetRatio, int kind) {
1557ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationKind = kind;
1558ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mFromRatio = mCurrentRatio;
1559ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mToRatio = targetRatio;
1560ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1561ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
1562ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            advanceAnimation();
1563ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            return true;
1564ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1565ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1566ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        @Override
1567ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        protected boolean interpolate(float progress) {
1568ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            if (progress >= 1) {
1569ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                mCurrentRatio = mToRatio;
1570ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                return true;
1571ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            } else {
1572ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
1573ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                return (mCurrentRatio == mToRatio);
1574ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            }
1575ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        }
1576ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1577ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang}
1578