PositionController.java revision b56ff734d0117cd5813cd2328edc57c4630ff1c3
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;
93b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int mViewW = 640;
94b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int mViewH = 480;;
95ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
96b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // A scaling guesture is in progress.
97b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mInScale;
98b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The focus point of the scaling gesture, relative to the center of the
99b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // picture in bitmap pixels.
100b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float mFocusX, mFocusY;
101ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
102fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    // whether there is a previous/next picture.
103fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private boolean mHasPrev, mHasNext;
104fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang
105b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (page mode).
106b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private FlingScroller mPageScroller;
107ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
108b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This is used by the fling animation (film mode).
109d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang    private OverScroller mFilmScroller;
110ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
111b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The bound of the stable region that the focused box can stay, see the
112b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // comments above calculateStableBound() for details.
113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
114ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
115bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Constrained frame is a rectangle that the focused box should fit into if
116bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // it is constrained. It has two effects:
117bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
118bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (1) In page mode, if the focused box is constrained, scaling for the
119bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box is adjusted to fit into the constrained frame, instead of the
120bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // whole view.
121bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
122bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // (2) In page mode, if the focused box is constrained, the mPlatform's
123bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // default center (mDefaultX/Y) is moved to the center of the constrained
124bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // frame, instead of the view center.
125bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
126bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mConstrainedFrame = new Rect();
127bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
128bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Whether the focused box is constrained.
129bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
130bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Our current program's first call to moveBox() sets constrained = true, so
131bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // we set the initial value of this variable to true, and we will not see
132bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // see unwanted transition animation.
133bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private boolean mConstrained = true;
134bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________________________________________________________
137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |   _____       _____       _____       _____       _____   |
138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |     |     |     |     |     |     |     |     |     |  |
139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  | Box |     | Box |     | Box*|     | Box |     | Box |  |
140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  |_____|.....|_____|.....|_____|.....|_____|.....|_____|  |
141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |          Gap         Gap         Gap         Gap          |
142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________________________________________________________|
143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                       <--  Platform  -->
145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
146bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
147ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
148b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private Platform mPlatform = new Platform();
149b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The gap at the right of a Box i is at index i. The gap at the left of a
151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Box i is at index i - 1.
152b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
153ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // These are only used during moveBox().
155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
156fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private RangeArray<Gap> mTempGaps =
157fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
158ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
159b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The output of the PositionController. Available throught getPosition().
160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
161b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
162b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public interface Listener {
163b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void invalidate();
1642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        boolean isHolding();
165ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
166b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // EdgeView
167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onPull(int offset, int direction);
168b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onRelease();
169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void onAbsorb(int velocity, int direction);
170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
171ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public PositionController(Context context, Listener listener) {
173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener = listener;
174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller = new FlingScroller();
175bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mFilmScroller = new OverScroller(context,
176bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                null /* default interpolator */, false /* no flywheel */);
177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
178b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Initialize the areas.
179b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
180b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, new Box());
182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(i);
183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mRects.put(i, new Rect());
184b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, new Gap());
187b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initGap(i);
188b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
189ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
190ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setOpenAnimationRect(Rect r) {
192b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = r;
193b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
194676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setViewSize(int viewW, int viewH) {
196b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewW == mViewW && viewH == mViewH) return;
197ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewW = viewW;
199b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mViewH = viewH;
200b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        initPlatform();
201ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            setBoxSize(i, viewW, viewH, true);
204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
205ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
208ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
209ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
210bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setConstrainedFrame(Rect f) {
211bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mConstrainedFrame.equals(f)) return;
212bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mConstrainedFrame.set(f);
213bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
214bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        updateScaleAndGapLimit();
215bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        snapAndRedraw();
216bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
217bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
218bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setImageSize(int index, int width, int height, boolean force) {
219bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (force) {
220bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            Box b = mBoxes.get(index);
221bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mImageW = width;
222bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mImageH = height;
223bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            return;
224bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
225bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
226b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (width == 0 || height == 0) {
227b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(index);
2282c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (!setBoxSize(index, width, height, false)) {
2292c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return;
230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
231ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
232b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
233b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startOpeningAnimationIfNeeded();
234b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
235ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
236ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
2372c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Returns false if the box size doesn't change.
2382c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
239b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
240d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        boolean wasViewSize = b.mUseViewSize;
241d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang
242d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // If we already have an image size, we don't want to use the view size.
2432c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (!wasViewSize && isViewSize) return false;
244ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
245b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = isViewSize;
246ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
247b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (width == b.mImageW && height == b.mImageH) {
2482c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return false;
249676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        }
250676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
251b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The ratio of the old size and the new size.
252b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float ratio = Math.min(
253b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                (float) b.mImageW / width, (float) b.mImageH / height);
254b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
255bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageW = width;
256bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mImageH = height;
257bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
258d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // If this is the first time we receive an image size, we change the
259d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // scale directly. Otherwise adjust the scales by a ratio, and snapback
260d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        // will animate the scale into the min/max bounds if necessary.
261d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        if (wasViewSize && !isViewSize) {
262bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mCurrentScale = getMinimalScale(b);
263d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
264d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        } else {
265d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mCurrentScale *= ratio;
266d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mFromScale *= ratio;
267d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang            b.mToScale *= ratio;
268d848862c1dd0689df8df5d684476cfd3897557d6Chih-Chung Chang        }
269676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
270b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
271b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusX /= ratio;
272b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFocusY /= ratio;
273ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
2742c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
2752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        return true;
276ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
277ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
278b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void startOpeningAnimationIfNeeded() {
279b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mOpenAnimationRect == null) return;
280b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
281b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mUseViewSize) return;
282b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
283b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Start animation from the saved rectangle if we have one.
284b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mOpenAnimationRect;
285b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mOpenAnimationRect = null;
286bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = r.centerX() - mViewW / 2;
287bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = r.centerY() - mViewH / 2;
288b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
289b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                r.height() / (float) b.mImageH);
290bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
291bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_OPENING);
292ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
293ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
294b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setFilmMode(boolean enabled) {
295b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (enabled == mFilmMode) return;
296b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmMode = enabled;
297b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
298bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
299b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        updateScaleAndGapLimit();
300b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        stopAnimation();
301b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
302ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
303ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setExtraScalingRange(boolean enabled) {
305b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mExtraScalingRange == enabled) return;
306b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mExtraScalingRange = enabled;
307b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!enabled) {
308b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            snapAndRedraw();
309b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
310ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
311ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // This should be called whenever the scale range of boxes or the default
313b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // gap size may change. Currently this can happen due to change of view
314bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void updateScaleAndGapLimit() {
316b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
317b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
318bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMin = getMinimalScale(b);
319bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mScaleMax = getMaximalScale(b);
320b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
321ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
322b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
323b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
324b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mDefaultSize = getDefaultGapSize(i);
325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
327676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the default gap size according the the size of the boxes around
329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // the gap and the current mode.
330b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getDefaultGapSize(int i) {
331b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return IMAGE_GAP;
332b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box a = mBoxes.get(i);
333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i + 1);
334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
335ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
336ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
337b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Here is how we layout the boxes in the page mode.
338b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
339b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   previous             current             next
340b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  ___________       ________________     __________
341b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |  _______  |     |   __________   |   |  ______  |
342b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       | |     |  |   right->|  |   | |      | |
343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |       |<-------->|<--left   |  |   | |      | |
344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // | |_______| |  |  |  |__________|  |   | |______| |
345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // |___________|  |  |________________|   |__________|
346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |  <--> gapToSide()
347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //                |
348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int gapToSide(Box b) {
350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
351ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
352ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Stop all animations at where they are now.
354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void stopAnimation() {
355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
356b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
357b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
361534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang        }
362534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    }
363534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
364b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void skipAnimation() {
365b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
366b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mCurrentX = mPlatform.mToX;
367bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.mCurrentY = mPlatform.mToY;
368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPlatform.mAnimationStartTime = NO_ANIMATION;
369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
370b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
371b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (b.mAnimationStartTime == NO_ANIMATION) continue;
373b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentY = b.mToY;
374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mCurrentScale = b.mToScale;
375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            b.mAnimationStartTime = NO_ANIMATION;
376b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
377b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
378b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (g.mAnimationStartTime == NO_ANIMATION) continue;
380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mCurrentGap = g.mToGap;
381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            g.mAnimationStartTime = NO_ANIMATION;
382b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
384ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
385ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
3862c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void snapback() {
387b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
388ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
389ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
390b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
391b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Start an animations for the focused box
392b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
393b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
394b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void zoomIn(float tapX, float tapY, float targetScale) {
395bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapX -= mViewW / 2;
396bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        tapY -= mViewH / 2;
397b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
398b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
399b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Convert the tap position to distance to center in bitmap coordinates
400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
401b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
402b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
403bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int x = (int) (-tempX * targetScale + 0.5f);
404bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = (int) (-tempY * targetScale + 0.5f);
405b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
406b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(targetScale);
407b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
409b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
410b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
411b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
412ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
413ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void resetToFullView() {
415b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
416bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
417b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
418b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
419b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void beginScale(float focusX, float focusY) {
420bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
421bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
422b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
423b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
424b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = true;
425b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
426b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Scales the image by the given factor.
430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns an out-of-range indicator:
431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   1 if the intended scale is too large for the stable range.
432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //   0 if the intended scale is in the stable range.
433b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  -1 if the intended scale is too small for the stable range.
434b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int scaleBy(float s, float focusX, float focusY) {
435bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusX -= mViewW / 2;
436bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        focusY -= mViewH / 2;
437b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
438b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
440b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // We want to keep the focus point (on the bitmap) the same as when we
441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // begin the scale guesture, that is,
442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // (focusX' - currentX') / scale' = (focusX - currentX) / scale
444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        s *= getTargetScale(b);
446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
447b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, s, ANIM_KIND_SCALE);
449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s < b.mScaleMin) return -1;
450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (s > b.mScaleMax) return 1;
451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return 0;
452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void endScale() {
455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mInScale = false;
456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
457ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
458ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
4592c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view.
4602c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startHorizontalSlide() {
461b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
462bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
4632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
4642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
4652c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // Slide the focused box to the center of the view with the capture
4662c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // animation. In addition to the sliding, the animation will also scale the
4672c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // the focused box, the specified neighbor box, and the gap between the
4682c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // two. The specified offset should be 1 or -1.
4692c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public void startCaptureAnimationSlide(int offset) {
4702c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box b = mBoxes.get(0);
4712c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Box n = mBoxes.get(offset);  // the neighbor box
4722c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        Gap g = mGaps.get(offset);  // the gap between the two boxes
4732c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
474bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
475bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                ANIM_KIND_CAPTURE);
476bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
477bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
4782c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
4792c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        redraw();
480ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
481ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
482b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void startScroll(float dx, float dy) {
483b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
484b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
485b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
486b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int x = getTargetX(p) + (int) (dx + 0.5f);
487b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int y = getTargetY(b) + (int) (dy + 0.5f);
488b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
489b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) {
490fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang            scrollToFilm(x, y);
491b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else {
492fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang            scrollToPage(x, y);
493b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
494676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    }
495676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
496fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private void scrollToPage(int x, int y) {
497b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
498532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
499b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
500532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
501532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Vertical direction: If we have space to move in the vertical
502532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // direction, we show the edge effect when scrolling reaches the edge.
503532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (mBoundTop != mBoundBottom) {
504532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            if (y < mBoundTop) {
505b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
506532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            } else if (y > mBoundBottom) {
507b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onPull(y - mBoundBottom, EdgeView.TOP);
508532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            }
509532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
510532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
511532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        y = Utils.clamp(y, mBoundTop, mBoundBottom);
512532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
513532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
514532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // tries to go left of the first image or go right of the last image.
515fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        if (!mHasPrev && x > mBoundRight) {
516b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = x - mBoundRight;
517b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.LEFT);
518532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            x = mBoundRight;
519fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        } else if (!mHasNext && x < mBoundLeft) {
520b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int pixels = mBoundLeft - x;
521b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mListener.onPull(pixels, EdgeView.RIGHT);
522b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            x = mBoundLeft;
523b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
524b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
526b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
527b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
528fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang    private void scrollToFilm(int x, int y) {
529b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
530b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
531b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
532b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // tries to go left of the first image or go right of the last image.
533bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x -= mPlatform.mDefaultX;
534bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (!mHasPrev && x > 0) {
535bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(x, EdgeView.LEFT);
536bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
537bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else if (!mHasNext && x < 0) {
538bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mListener.onPull(-x, EdgeView.RIGHT);
539bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            x = 0;
540532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
541bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        x += mPlatform.mDefaultX;
542b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
543532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
544532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
545b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    public boolean fling(float velocityX, float velocityY) {
546b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int vx = (int) (velocityX + 0.5f);
547b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int vy = (int) (velocityY + 0.5f);
548b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
549b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
550b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
551b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean flingPage(int velocityX, int velocityY) {
552b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
553b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
554b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
555b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // We only want to do fling when the picture is zoomed-in.
556b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewWiderThanScaledImage(b.mCurrentScale) &&
557b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            viewTallerThanScaledImage(b.mCurrentScale)) {
5582ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            return false;
5592ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
5602ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
5612ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // We only allow flinging in the directions where it won't go over the
5622ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        // picture.
5632ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        int edges = getImageAtEdges();
5642ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
5652ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
5662ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityX = 0;
5672ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
5682ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
5692ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
5702ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            velocityY = 0;
5712ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
572b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
573b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX == 0 && velocityY == 0) return false;
574b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
575b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
576b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
577b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mPageScroller.getFinalX();
578b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetY = mPageScroller.getFinalY();
579b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
580b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
581b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return true;
582b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
583b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
584b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean flingFilm(int velocityX, int velocityY) {
585b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
586b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
587b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
588b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we are already at the edge, don't start the fling.
589bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int defaultX = p.mDefaultX;
590bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if ((!mHasPrev && p.mCurrentX >= defaultX)
591bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                || (!mHasNext && p.mCurrentX <= defaultX)) {
592b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
5936068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang        }
594ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
595b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX == 0) return false;
596ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
597b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
598b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
599b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int targetX = mFilmScroller.getFinalX();
600c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // This value doesn't matter because we use mFilmScroller.isFinished()
601c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // to decide when to stop. We set this to 0 so it's faster for
602c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Animatable.advanceAnimation() to calculate the progress (always 1).
603c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        ANIM_TIME[ANIM_KIND_FLING] = 0;
604b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
605b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
606b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
607ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
608b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
609b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Redraw
610b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
611b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method changes box positions directly, redraw()
612b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  should be called.
613b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
614b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method may also cause a snapback to happen, snapAndRedraw() should
615b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
616b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
617b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If a method starts an animation to change the position of focused box,
618b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  startAnimation() should be called.
619b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
620b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  If time advances to change the box position, advanceAnimation() should
621b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  be called.
622b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
623b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void redraw() {
624b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
625b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mListener.invalidate();
626b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
627b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
628b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void snapAndRedraw() {
629b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.startSnapback();
630b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
631b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(i).startSnapback();
632b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
633b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
634b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.get(i).startSnapback();
635ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        redraw();
637b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
638ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
639b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void startAnimation(int targetX, int targetY, float targetScale,
640b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int kind) {
641b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
642bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
643b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
644b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
645b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
646b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
647b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang    public void advanceAnimation() {
648b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
649b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        changed |= mPlatform.advanceAnimation();
650b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
651b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mBoxes.get(i).advanceAnimation();
652b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
653b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
654b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            changed |= mGaps.get(i).advanceAnimation();
655b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
656b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (changed) redraw();
657ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
658ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
659b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
660b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Layout
661b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
662b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
663b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box.
664b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b) {
665b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * b.mCurrentScale + 0.5f);
666b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
667b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
668b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box.
669b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b) {
670b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * b.mCurrentScale + 0.5f);
671b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
672b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
673b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display width of this box, using the given scale.
674b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int widthOf(Box b, float scale) {
675b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageW * scale + 0.5f);
676b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
677b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
678b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the display height of this box, using the given scale.
679b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int heightOf(Box b, float scale) {
680b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (int) (b.mImageH * scale + 0.5f);
681b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
682b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
683b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Convert the information in mPlatform and mBoxes to mRects, so the user
684b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // can get the position of each box by getPosition().
685b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
686b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Note the loop index goes from inside-out because each box's X coordinate
687b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // is relative to its anchor box (except the focused box).
688b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void layoutAndSetPosition() {
689b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // layout box 0 (focused box)
690b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        convertBoxToRect(0);
691b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 1; i <= BOX_MAX; i++) {
692b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // layout box i and -i
693b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            convertBoxToRect(i);
694b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            convertBoxToRect(-i);
695ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
696b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //dumpState();
697b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
698ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
699b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpState() {
700b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
701b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
702ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
703ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
704b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        dumpRect(0);
705b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 1; i <= BOX_MAX; i++) {
706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            dumpRect(i);
707b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            dumpRect(-i);
708b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
709b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
710b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
711b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            for (int j = i + 1; j <= BOX_MAX; j++) {
712b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (Rect.intersects(mRects.get(i), mRects.get(j))) {
713b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
714b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
715ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            }
716b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
717b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
718b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
719b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void dumpRect(int i) {
720b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder sb = new StringBuilder();
721b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
722b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("Rect " + i + ":");
723b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("(");
724b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerX());
725b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(",");
726b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.centerY());
727b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(") [");
728b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.width());
729b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("x");
730b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append(r.height());
731b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        sb.append("]");
732b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, sb.toString());
733b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
734b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
735b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void convertBoxToRect(int i) {
736b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(i);
737b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mRects.get(i);
738bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
739b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b);
740b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b);
741b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (i == 0) {
742bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int x = mPlatform.mCurrentX + mViewW / 2;
743b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = x - w / 2;
744b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
745b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (i > 0) {
746b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i - 1);
747b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i - 1);
748b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = a.right + g.mCurrentGap;
749b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = r.left + w;
750b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else {  // i < 0
751b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect a = mRects.get(i + 1);
752b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Gap g = mGaps.get(i);
753b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.right = a.left - g.mCurrentGap;
754b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            r.left = r.right - w;
755b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
756b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.top = y - h / 2;
757b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        r.bottom = r.top + h;
758b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
759b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
760b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns the position of a box.
761b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public Rect getPosition(int index) {
762b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mRects.get(index);
763b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
765b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
766b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box management
767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize the platform to be at the view center.
770b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initPlatform() {
771bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.updateDefaultXY();
772bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentX = mPlatform.mDefaultX;
773bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPlatform.mCurrentY = mPlatform.mDefaultY;
774b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mAnimationStartTime = NO_ANIMATION;
775b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
776b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
777b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a box to have the size of the view.
778b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initBox(int index) {
779b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(index);
780b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageW = mViewW;
781b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mImageH = mViewH;
782b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mUseViewSize = true;
783bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMin = getMinimalScale(b);
784bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mScaleMax = getMaximalScale(b);
785bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        b.mCurrentY = 0;
786b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mCurrentScale = b.mScaleMin;
787b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        b.mAnimationStartTime = NO_ANIMATION;
788b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
789b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
790b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Initialize a gap. This can only be called after the boxes around the gap
791b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // has been initialized.
792b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index) {
793b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
794b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
795b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = g.mDefaultSize;
796b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
797b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
798b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
799b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void initGap(int index, int size) {
800b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Gap g = mGaps.get(index);
801b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mDefaultSize = getDefaultGapSize(index);
802b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mCurrentGap = size;
803b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        g.mAnimationStartTime = NO_ANIMATION;
804b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
805b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
806b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void debugMoveBox(int fromIndex[]) {
807b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        StringBuilder s = new StringBuilder("moveBox:");
808b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < fromIndex.length; i++) {
809b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = fromIndex[i];
810b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) {
811b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" N");
812b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else {
813b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(" ");
814b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                s.append(fromIndex[i]);
815b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            }
816ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
817b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Log.d(TAG, s.toString());
8186068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang    }
8196068de20f4f19ca82ca01bef3b6dda796a79f724Yuli Huang
820b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Move the boxes: it may indicate focus change, box deleted, box appearing,
821b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // box reordered, etc.
822b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
823b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Each element in the fromIndex array indicates where each box was in the
824b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
825b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // means the box is new.
826b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
827b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // For example:
828b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // N N N N N N N -- all new boxes
829b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 0 1 2 3 -- nothing changed
830b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -2 -1 0 1 2 3 N -- focus goes to the next box
8312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
832b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // -3 -2 -1 1 2 3 N -- the focused box was deleted.
833bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
834bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // hasPrev/hasNext indicates if there are previous/next boxes for the
835bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // focused box. constrained indicates whether the focused box should be put
836bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // into the constrained frame.
837bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
838bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean constrained) {
839b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //debugMoveBox(fromIndex);
840fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasPrev = hasPrev;
841fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang        mHasNext = hasNext;
842bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
843b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
844b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
845b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 1. Get the absolute X coordiates for the boxes.
846b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        layoutAndSetPosition();
847b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
848b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i);
849b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mRects.get(i);
850bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            b.mAbsoluteX = r.centerX() - mViewW / 2;
851ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
852ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
853b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 2. copy boxes and gaps to temporary storage.
854b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
855b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(i, mBoxes.get(i));
856b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, null);
857b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
858b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
859b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempGaps.put(i, mGaps.get(i));
860b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, null);
861b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
862b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
863b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 3. move back boxes that are used in the new array.
864b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
865b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
866b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
867b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(j));
868b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTempBoxes.put(j, null);
869b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
870ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
871b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 4. move back gaps if both boxes around it are kept together.
872b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
873b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j = from.get(i);
874b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j == Integer.MAX_VALUE) continue;
875b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int k = from.get(i + 1);
876b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (k == Integer.MAX_VALUE) continue;
877b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (j + 1 == k) {
878b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mGaps.put(i, mTempGaps.get(j));
879b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mTempGaps.put(j, null);
880b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
881b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
882534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang
883b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 5. recycle the boxes that are not used in the new array.
884b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int k = -BOX_MAX;
885b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
886b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mBoxes.get(i) != null) continue;
887b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempBoxes.get(k) == null) {
888b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
889b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
890b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.put(i, mTempBoxes.get(k++));
891b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            initBox(i);
892ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
893ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
894b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 6. Now give the recycled box a reasonable absolute X position.
895b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        //
896b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // First try to find the first and the last box which the absolute X
897b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // position is known.
898b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int first, last;
899b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (first = -BOX_MAX; first <= BOX_MAX; first++) {
900b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(first) != Integer.MAX_VALUE) break;
901b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
902b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (last = BOX_MAX; last >= -BOX_MAX; last--) {
903b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(last) != Integer.MAX_VALUE) break;
904b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
905b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If there is no box has known X position at all, make the focused one
906b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // as known.
907b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (first > BOX_MAX) {
908b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
909b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            first = last = 0;
910b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
911b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Now for those boxes between first and last, just assign the same
9122c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        // position as the next box. (We can do better, but this should be
913b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // rare). For the boxes before first or after last, we will use a new
914b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // default gap size below.
9152c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        for (int i = last - 1; i > first; i--) {
916b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (from.get(i) != Integer.MAX_VALUE) continue;
9172c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
918b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
919ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
920b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 7. recycle the gaps that are not used in the new array.
921b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        k = -BOX_MAX;
922b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -BOX_MAX; i < BOX_MAX; i++) {
923b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mGaps.get(i) != null) continue;
924b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            while (mTempGaps.get(k) == null) {
925b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                k++;
926b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
927b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mGaps.put(i, mTempGaps.get(k++));
928b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box a = mBoxes.get(i);
929b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(i + 1);
930b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wa = widthOf(a);
931b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int wb = widthOf(b);
932b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (i >= first && i < last) {
933b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
934b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i, g);
935b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
936b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                initGap(i);
937b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
938ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
939ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
940b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // 8. offset the Platform position
941b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
942b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mCurrentX += dx;
943b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFromX += dx;
944b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mToX += dx;
945b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPlatform.mFlingOffset += dx;
946b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
947bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mConstrained != constrained) {
948bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mConstrained = constrained;
949bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPlatform.updateDefaultXY();
950bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateScaleAndGapLimit();
951bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
952bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
953b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        snapAndRedraw();
954b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
955b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
956b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
957b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Public utilities
958b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
959b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
960b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public boolean isAtMinimalScale() {
961b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
962b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
963b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
964b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
965bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public boolean isCenter() {
966bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        Box b = mBoxes.get(0);
967bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mPlatform.mCurrentX == mPlatform.mDefaultX
968bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            && b.mCurrentY == 0;
969bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
970bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
971b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageWidth() {
972b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
973b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageW;
974b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
975b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
976b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageHeight() {
977b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
978b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mImageH;
979b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
980b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
981b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public float getImageScale() {
982b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
983b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return b.mCurrentScale;
984b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
985b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
986b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public int getImageAtEdges() {
987b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
988b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Platform p = mPlatform;
989b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(b.mCurrentScale);
990b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int edges = 0;
991b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX <= mBoundLeft) {
992b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_RIGHT_EDGE;
993ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
994b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (p.mCurrentX >= mBoundRight) {
995b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_LEFT_EDGE;
996b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
997b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY <= mBoundTop) {
998b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_BOTTOM_EDGE;
999b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1000b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (b.mCurrentY >= mBoundBottom) {
1001b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            edges |= IMAGE_AT_TOP_EDGE;
1002b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1003b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return edges;
1004b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1005b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1006b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1007b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Private utilities
1008b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1009b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1010b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getMinimalScale(Box b) {
1011bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float wFactor = 1.0f;
1012bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float hFactor = 1.0f;
1013bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int viewW, viewH;
1014bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1015b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang        if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
1016b56ff734d0117cd5813cd2328edc57c4630ff1c3Chih-Chung Chang                && b == mBoxes.get(0)) {
1017bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mConstrainedFrame.width();
1018bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mConstrainedFrame.height();
1019bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        } else {
1020bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewW = mViewW;
1021bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            viewH = mViewH;
1022bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1023bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1024bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (mFilmMode) {
1025bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mViewH > mViewW) {  // portrait
1026bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_PORTRAIT_WIDTH;
1027bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_PORTRAIT_HEIGHT;
1028bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {  // landscape
1029bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                wFactor = FILM_MODE_LANDSCAPE_WIDTH;
1030bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
1031bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1032bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1033bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1034bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        float s = Math.min(wFactor * viewW / b.mImageW,
1035bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                hFactor * viewH / b.mImageH);
1036bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return Math.min(SCALE_LIMIT, s);
1037b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1038ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1039bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private float getMaximalScale(Box b) {
1040bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mFilmMode ? getMinimalScale(b) : SCALE_LIMIT;
1041b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1042b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1043b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static boolean isAlmostEqual(float a, float b) {
1044b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        float diff = a - b;
1045b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (diff < 0 ? -diff : diff) < 0.02f;
1046ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1047ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1048bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Calculates the stable region of mPlatform.mCurrentX and
1049bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // mBoxes.get(0).mCurrentY, where "stable" means
1050b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1051b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (1) If the dimension of scaled image >= view dimension, we will not
1052b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // see black region outside the image (at that dimension).
1053b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (2) If the dimension of scaled image < view dimension, we will center
1054b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // the scaled image.
1055b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1056b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // We might temporarily go out of this stable during user interaction,
1057b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // but will "snap back" after user stops interaction.
1058b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
1059b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // The results are stored in mBound{Left/Right/Top/Bottom}.
1060b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
10618f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // An extra parameter "horizontalSlack" (which has the value of 0 usually)
10628f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // is used to extend the stable region by some pixels on each side
10638f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang    // horizontally.
1064b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale, int horizontalSlack) {
1065b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Box b = mBoxes.get(0);
10668f568da373699781beb11cfa46a46c5871288353Chih-Chung Chang
1067b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The width and height of the box in number of view pixels
1068b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int w = widthOf(b, scale);
1069b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int h = heightOf(b, scale);
1070b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1071b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // When the edge of the view is aligned with the edge of the box
1072bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
1073bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
1074bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
1075bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mBoundBottom = h / 2 - mViewH / 2;
1076b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1077b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // If the scaled height is smaller than the view height,
1078b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // force it to be in the center.
1079b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewTallerThanScaledImage(scale)) {
1080bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundTop = mBoundBottom = 0;
1081b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
1082b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1083b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // Same for width
10842ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        if (viewWiderThanScaledImage(scale)) {
1085bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mBoundLeft = mBoundRight = mPlatform.mDefaultX;
1086b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1087b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1088b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1089b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void calculateStableBound(float scale) {
1090b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        calculateStableBound(scale, 0);
1091b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1092b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1093b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean viewTallerThanScaledImage(float scale) {
1094b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewH >= heightOf(mBoxes.get(0), scale);
10952ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
10962ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
10972ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    private boolean viewWiderThanScaledImage(float scale) {
1098b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return mViewW >= widthOf(mBoxes.get(0), scale);
10992ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang    }
11002ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang
1101b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private float getTargetScale(Box b) {
1102b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
1103b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
1104b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
1105b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getTargetX(Platform p) {
1106b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
1107ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1108ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1109b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int getTargetY(Box b) {
1110b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
1111ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1112ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean useCurrentValueAsTarget(Animatable a) {
1114b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return a.mAnimationStartTime == NO_ANIMATION ||
1115b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                a.mAnimationKind == ANIM_KIND_SNAPBACK ||
1116b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                a.mAnimationKind == ANIM_KIND_FLING;
1117ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1118ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1119b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1120b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Animatable: an thing which can do animation.
1121b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1122b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private abstract static class Animatable {
1123b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public long mAnimationStartTime;
1124b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationKind;
1125b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAnimationDuration;
1126b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This should be overidden in subclass to change the animation values
1128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // give the progress value in [0, 1].
1129b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected abstract boolean interpolate(float progress);
1130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public abstract boolean startSnapback();
1131b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1132b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Returns true if the animation values changes, so things need to be
1133b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // redrawn.
1134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean advanceAnimation() {
1135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == NO_ANIMATION) {
1136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime == LAST_ANIMATION) {
1139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = NO_ANIMATION;
1140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return startSnapback();
1141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float progress;
1144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationDuration == 0) {
1145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1146b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1147b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                long now = AnimationTime.get();
1148b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress =
1149b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    (float) (now - mAnimationStartTime) / mAnimationDuration;
1150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1152b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1153b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = 1;
1154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = applyInterpolationCurve(mAnimationKind, progress);
1156b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1157ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1158b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            boolean done = interpolate(progress);
1159ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (done) {
1161b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mAnimationStartTime = LAST_ANIMATION;
1162b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1163ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1164b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1165ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
1166ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private static float applyInterpolationCurve(int kind, float progress) {
1168b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float f = 1 - progress;
1169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            switch (kind) {
1170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCROLL:
1171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_FLING:
11722c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                case ANIM_KIND_CAPTURE:
1173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f;  // linear
1174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1175b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SCALE:
1176b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f;  // quadratic
1177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1178b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SNAPBACK:
1179b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_ZOOM:
1180b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_SLIDE:
1181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case ANIM_KIND_OPENING:
1182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    progress = 1 - f * f * f * f * f; // x^5
1183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
1184b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress;
1186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1187ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1188ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1190bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //  Platform: captures the global X/Y movement.
1191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1192b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Platform extends Animatable {
1193bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentX, mFromX, mToX, mDefaultX;
1194bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int mCurrentY, mFromY, mToY, mDefaultY;
1195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mFlingOffset;
1196b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1197b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1199b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1200b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
12012c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && mListener.isHolding()) return false;
1202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMin = mExtraScalingRange ?
1205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
1206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scaleMax = mExtraScalingRange ?
1207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
1208b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
1209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int x = mCurrentX;
1210bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int y = mDefaultY;
1211b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mFilmMode) {
1212bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                int defaultX = mDefaultX;
1213bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (!mHasNext) x = Math.max(x, defaultX);
1214bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (!mHasPrev) x = Math.min(x, defaultX);
1215b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                calculateStableBound(scale, HORIZONTAL_SLACK);
1217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                x = Utils.clamp(x, mBoundLeft, mBoundRight);
1218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1219bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX != x || mCurrentY != y) {
1220bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return doAnimation(x, y, ANIM_KIND_SNAPBACK);
1221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
1223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1225bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // The updateDefaultXY() should be called whenever these variables
1226bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
1227bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // mFilmMode
1228bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateDefaultXY() {
1229bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // We don't check mFilmMode and return 0 for mDefaultX. Because
1230bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // otherwise if we decide to leave film mode because we are
1231bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // centered, we will immediately back into film mode because we find
1232bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // we are not centered.
1233bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mConstrained && !mConstrainedFrame.isEmpty()) {
1234bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
1235bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = mFilmMode ? 0 :
1236bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        mConstrainedFrame.centerY() - mViewH / 2;
1237bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
1238bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultX = 0;
1239bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mDefaultY = 0;
1240bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1241bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1242bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1243b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for the platform.
1244bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        private boolean doAnimation(int targetX, int targetY, int kind) {
1245bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX == targetX && mCurrentY == targetY) return false;
1246b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1247b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromX = mCurrentX;
1248bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mFromY = mCurrentY;
1249b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToX = targetX;
1250bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mToY = targetY;
1251b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1252b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1253b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFlingOffset = 0;
1254b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1255b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1256b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1257b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1258b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1259b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1260b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
1261b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return mFilmMode
1262b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        ? interpolateFlingFilm(progress)
1263b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        : interpolateFlingPage(progress);
1264b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1265b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1266b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1267b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1268b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1269b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingFilm(float progress) {
1270b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFilmScroller.computeScrollOffset();
1271b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
1272b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1273b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int dir = EdgeView.INVALID_DIRECTION;
1274bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mCurrentX < mDefaultX) {
1275fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasNext) {
1276b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.RIGHT;
1277b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1278bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else if (mCurrentX > mDefaultX) {
1279fb1a15559bb2a0a1c8a41efd3e0420a2a2d70590Chih-Chung Chang                if (!mHasPrev) {
1280b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    dir = EdgeView.LEFT;
1281b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1282b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1283b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (dir != EdgeView.INVALID_DIRECTION) {
1284b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
1285b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, dir);
1286b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mFilmScroller.forceFinished(true);
1287bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = mDefaultX;
1288b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1289b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return mFilmScroller.isFinished();
1290b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1291b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1292b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1293b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1294b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Box b = mBoxes.get(0);
1295b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(b.mCurrentScale);
1296b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1297b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldX = mCurrentX;
1298b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentX = mPageScroller.getCurrX();
1299b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1300b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1301b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
1302b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
1303b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.RIGHT);
1304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
1305b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
1306b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.LEFT);
1307b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1308b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1309b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1310b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1311b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1313b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Other animations
1314b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentX = mToX;
1316bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = mToY;
1317b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
13192c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
13202c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    progress = CaptureAnimation.calculateSlide(progress);
1321bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                }
1322bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
1323bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1324bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
13252c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
13262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
1327bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    return (mCurrentX == mToX && mCurrentY == mToY);
13282c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1330b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1331ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1332532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
1333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Box: represents a rectangular area which shows a picture.
1335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Box extends Animatable {
1337b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Size of the bitmap
1338b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mImageW, mImageH;
1339b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1340b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This is true if we assume the image size is the same as view size
1341b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // until we know the actual size of image. This is also used to
1342b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // determine if there is an image ready to show.
1343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean mUseViewSize;
1344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The minimum and maximum scale we allow for this box.
1346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mScaleMin, mScaleMax;
1347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The X/Y value indicates where the center of the box is on the view
1349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
1350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // actual values used currently. Note that the X values are implicitly
1351b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // defined by Platform and Gaps.
1352b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentY, mFromY, mToY;
1353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public float mCurrentScale, mFromScale, mToScale;
1354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The absolute X coordinate of the center of the box. This is only used
1356b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // during moveBox().
1357b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mAbsoluteX;
1358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1361b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
1362b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_SCROLL
13632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && mListener.isHolding()) return false;
1364b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mInScale && this == mBoxes.get(0)) return false;
1365b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1366b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int y;
1367b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale;
1368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (this == mBoxes.get(0)) {
1370b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMin = mExtraScalingRange ?
1371b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
1372b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scaleMax = mExtraScalingRange ?
1373b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
1374b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
1375b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (mFilmMode) {
1376bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    y = 0;
1377b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                } else {
1378b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    calculateStableBound(scale, HORIZONTAL_SLACK);
1379b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
1380b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
1381b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1382bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                y = 0;
1383b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                scale = mScaleMin;
1384b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1385b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1386b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mCurrentY != y || mCurrentScale != scale) {
1387b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
1388b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1389b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return false;
13902ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1391b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1392b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean doAnimation(int targetY, float targetScale, int kind) {
1393b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            targetScale = Utils.clamp(targetScale,
1394b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    SCALE_MIN_EXTRA * mScaleMin,
1395b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    SCALE_MAX_EXTRA * mScaleMax);
1396b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1397b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // If the scaled height is smaller than the view height, force it to be
1398b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // in the center.  (We do this for height only, not width, because the
1399b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // user may want to scroll to the previous/next image.)
1400b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (!mInScale && viewTallerThanScaledImage(targetScale)) {
1401bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                targetY = 0;
1402b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1403b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
14042c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentY == targetY && mCurrentScale == targetScale
14052c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    && kind != ANIM_KIND_CAPTURE) {
1406b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1407b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1408b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1409b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Now starts an animation for the box.
1410b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationKind = kind;
1411b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromY = mCurrentY;
1412b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromScale = mCurrentScale;
1413b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToY = targetY;
1414b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToScale = targetScale;
1415b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1416b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[kind];
1417b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1418b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
14192ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1420b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1421b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1422b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1423b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
1424b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // Currently a Box can only be flung in page mode.
1425b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateFlingPage(progress);
1426b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return interpolateLinear(progress);
1428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
14292ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateFlingPage(float progress) {
1432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPageScroller.computeScrollOffset(progress);
1433b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            calculateStableBound(mCurrentScale);
1434b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1435b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int oldY = mCurrentY;
1436b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mCurrentY = mPageScroller.getCurrY();
1437b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1438b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Check if we hit the edges; show edge effects if we do.
1439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (oldY > mBoundTop && mCurrentY == mBoundTop) {
1440b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
1441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.BOTTOM);
1442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
1443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
1444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mListener.onAbsorb(v, EdgeView.TOP);
1445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1446b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1447b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return progress >= 1;
1448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean interpolateLinear(float progress) {
1451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = mToY;
1453b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mToScale;
1454b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1455b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
1457b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
14582c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
14592c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
14602c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentScale *= f;
14612c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
14622c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
14632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentY == mToY && mCurrentScale == mToScale);
14642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1465b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1466b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1467b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1468b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1469b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1470b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Gap: represents a rectangular area which is between two boxes.
1471b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1472b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class Gap extends Animatable {
1473b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The default gap size between two boxes. The value may vary for
1474b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // different image size of the boxes and for different modes (page or
1475b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // film).
1476b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mDefaultSize;
1477b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1478b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // The gap size between the two boxes.
1479b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public int mCurrentGap, mFromGap, mToGap;
1480b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1481b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1482b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public boolean startSnapback() {
1483b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mAnimationStartTime != NO_ANIMATION) return false;
14842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
1485b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1486b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1487b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Starts an animation for a gap.
14882c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean doAnimation(int targetSize, int kind) {
14892c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
14902c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                return false;
14912c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            }
14922c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mAnimationKind = kind;
1493b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromGap = mCurrentGap;
1494b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mToGap = targetSize;
1495b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationStartTime = AnimationTime.startTime();
1496b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
1497b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            advanceAnimation();
1498b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return true;
1499b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1500b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1501b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
1502b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        protected boolean interpolate(float progress) {
1503b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (progress >= 1) {
1504b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = mToGap;
1505b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
1506b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else {
1507b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
15082c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                if (mAnimationKind == ANIM_KIND_CAPTURE) {
15092c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    float f = CaptureAnimation.calculateScale(progress);
15102c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    mCurrentGap = (int) (mCurrentGap * f);
15112c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return false;
15122c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                } else {
15132c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    return (mCurrentGap == mToGap);
15142c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
1515b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
15162ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang        }
1517532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
1518ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang}
1519