PositionController.java revision 532d93caddc91a7aa33ca113adbc0b8255d498eb
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 com.android.gallery3d.R;
20ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport com.android.gallery3d.app.GalleryActivity;
21ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport com.android.gallery3d.common.Utils;
22ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport com.android.gallery3d.data.Path;
23ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport com.android.gallery3d.ui.PositionRepository.Position;
24ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
25ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.content.Context;
26ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.graphics.Bitmap;
27ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.graphics.Color;
28ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.graphics.RectF;
29ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.os.Message;
30ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.os.SystemClock;
31ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.view.GestureDetector;
32ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.view.MotionEvent;
33ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changimport android.view.ScaleGestureDetector;
34b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Changimport android.widget.Scroller;
35ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
36ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Changclass PositionController {
37b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private static final String TAG = "PositionController";
38ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private long mAnimationStartTime = NO_ANIMATION;
39ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long NO_ANIMATION = -1;
40ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final long LAST_ANIMATION = -2;
41ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
42ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int mAnimationKind;
43b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private float mAnimationDuration;
44ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private final static int ANIM_KIND_SCROLL = 0;
45ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private final static int ANIM_KIND_SCALE = 1;
46ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private final static int ANIM_KIND_SNAPBACK = 2;
47ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private final static int ANIM_KIND_SLIDE = 3;
48ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private final static int ANIM_KIND_ZOOM = 4;
49b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private final static int ANIM_KIND_FLING = 5;
50ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
51676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Animation time in milliseconds. The order must match ANIM_KIND_* above.
52676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    private final static int ANIM_TIME[] = {
53676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        0,    // ANIM_KIND_SCROLL
54676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        50,   // ANIM_KIND_SCALE
55676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        600,  // ANIM_KIND_SNAPBACK
56676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        400,  // ANIM_KIND_SLIDE
57676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        300,  // ANIM_KIND_ZOOM
58b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        0,    // ANIM_KIND_FLING (the duration is calculated dynamically)
59676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    };
60676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
61ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // We try to scale up the image to fill the screen. But in order not to
62ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // scale too much for small icons, we limit the max up-scaling factor here.
63ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static final float SCALE_LIMIT = 4;
64ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
65ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private PhotoView mViewer;
66532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    private EdgeView mEdgeView;
67ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int mImageW, mImageH;
68ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int mViewW, mViewH;
69ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
70ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // The X, Y are the coordinate on bitmap which shows on the center of
71676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
72ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // values used currently.
73ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int mCurrentX, mFromX, mToX;
74ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int mCurrentY, mFromY, mToY;
75ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private float mCurrentScale, mFromScale, mToScale;
76ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
77676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // The focus point of the scaling gesture (in bitmap coordinates).
78b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private int mFocusBitmapX;
79b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private int mFocusBitmapY;
80ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private boolean mInScale;
81ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
82676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // The minimum and maximum scale we allow.
83676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    private float mScaleMin, mScaleMax = SCALE_LIMIT;
84676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
85b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // This is used by the fling animation
86b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private FlingScroller mScroller;
87b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
88b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // The bound of the stable region, see the comments above
89b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // calculateStableBound() for details.
90b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
91b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
92676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Assume the image size is the same as view size before we know the actual
93676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // size of image.
94676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    private boolean mUseViewSize = true;
95ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
96ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private RectF mTempRect = new RectF();
97ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private float[] mTempPoints = new float[8];
98ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
99532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    public PositionController(PhotoView viewer, Context context,
100532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            EdgeView edgeView) {
101ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mViewer = viewer;
102532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        mEdgeView = edgeView;
103b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mScroller = new FlingScroller();
104ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
105ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
106ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void setImageSize(int width, int height) {
107ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
108ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        // If no image available, use view size.
109ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (width == 0 || height == 0) {
110ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mUseViewSize = true;
111ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mImageW = mViewW;
112ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mImageH = mViewH;
113ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentX = mImageW / 2;
114ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentY = mImageH / 2;
115ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentScale = 1;
116ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mScaleMin = 1;
117ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
118ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            return;
119ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
120ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
121ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mUseViewSize = false;
122ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
123ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float ratio = Math.min(
124ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                (float) mImageW / width, (float) mImageH / height);
125ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
126676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // See the comment above translate() for details.
127ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentX = translate(mCurrentX, mImageW, width, ratio);
128ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentY = translate(mCurrentY, mImageH, height, ratio);
129ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentScale = mCurrentScale * ratio;
130ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
131ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromX = translate(mFromX, mImageW, width, ratio);
132ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromY = translate(mFromY, mImageH, height, ratio);
133ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromScale = mFromScale * ratio;
134ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
135ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mToX = translate(mToX, mImageW, width, ratio);
136ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mToY = translate(mToY, mImageH, height, ratio);
137ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mToScale = mToScale * ratio;
138ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
139b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
140b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
141b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
142ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mImageW = width;
143ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mImageH = height;
144ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
145676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        mScaleMin = getMinimalScale(mImageW, mImageH);
146ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
147676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // Start animation from the saved position if we have one.
148676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        Position position = mViewer.retrieveSavedPosition();
149ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (position != null) {
150676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            // The animation starts from 240 pixels and centers at the image
151676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            // at the saved position.
152ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            float scale = 240f / Math.min(width, height);
153ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2;
154ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2;
155ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentScale = scale;
156ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mViewer.openAnimationStarted();
157ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            startSnapback();
158ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        } else if (mAnimationStartTime == NO_ANIMATION) {
159ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
160ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
161ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
162ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
163ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
164ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void zoomIn(float tapX, float tapY, float targetScale) {
165ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (targetScale > mScaleMax) targetScale = mScaleMax;
166676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
167676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // Convert the tap position to image coordinate
168b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
169b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
170ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
171b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        calculateStableBound(targetScale);
172b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
173b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
174ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
175ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
176ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
177ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
178ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void resetToFullView() {
179ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
180ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
181ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
182676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    public float getMinimalScale(int w, int h) {
183676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        return Math.min(SCALE_LIMIT,
184676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                Math.min((float) mViewW / w, (float) mViewH / h));
185ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
186ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
187b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // Translate a coordinate on bitmap if the bitmap size changes.
188b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // If the aspect ratio doesn't change, it's easy:
189b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
190b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         r  = w / w' (= h / h')
191b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         x' = x / r
192b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         y' = y / r
193b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
194b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // However the aspect ratio may change. That happens when the user slides
195b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // a image before it's loaded, we don't know the actual aspect ratio, so
196b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // we will assume one. When we receive the actual bitmap size, we need to
197b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // translate the coordinate from the old bitmap into the new bitmap.
198b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
199b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // What we want to do is center the bitmap at the original position.
200b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
201b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         ...+--+...
202b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         .  |  |  .
203b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         .  |  |  .
204b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         ...+--+...
205676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    //
206b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // First we scale down the new bitmap by a factor r = min(w/w', h/h').
207b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
208b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
209b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // the old bitmap maps to (x', y') in the new bitmap, where
210b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
211b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //         y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
212676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    private static int translate(int value, int size, int newSize, float ratio) {
213676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        return Math.round(newSize / 2f + (value - size / 2f) / ratio);
214ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
215ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
216ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void setViewSize(int viewW, int viewH) {
217ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        boolean needLayout = mViewW == 0 || mViewH == 0;
218ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
219ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mViewW = viewW;
220ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mViewH = viewH;
221ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
222ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mUseViewSize) {
223ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mImageW = viewW;
224ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mImageH = viewH;
225ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentX = mImageW / 2;
226ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentY = mImageH / 2;
227ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentScale = 1;
228676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            mScaleMin = 1;
229676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
230676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            return;
231676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        }
232676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
233676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // In most cases we want to keep the scaling factor intact when the
234676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // view size changes. The cases we want to reset the scaling factor
235676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (to fit the view if possible) are (1) the scaling factor is too
236676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // small for the new view size (2) the scaling factor has not been
237676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // changed by the user.
238676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        boolean wasMinScale = (mCurrentScale == mScaleMin);
239676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        mScaleMin = getMinimalScale(mImageW, mImageH);
240676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
241676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
242676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            mCurrentX = mImageW / 2;
243676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            mCurrentY = mImageH / 2;
244676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            mCurrentScale = mScaleMin;
245ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
246ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
247ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
248ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
249ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void stopAnimation() {
250ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mAnimationStartTime = NO_ANIMATION;
251ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
252ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
253ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void skipAnimation() {
254ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mAnimationStartTime == NO_ANIMATION) return;
255ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mAnimationStartTime = NO_ANIMATION;
256ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentX = mToX;
257ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentY = mToY;
258ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentScale = mToScale;
259ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
260ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
261ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void beginScale(float focusX, float focusY) {
262ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mInScale = true;
263b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mFocusBitmapX = Math.round(mCurrentX +
264b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                (focusX - mViewW / 2f) / mCurrentScale);
265b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mFocusBitmapY = Math.round(mCurrentY +
266b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                (focusY - mViewH / 2f) / mCurrentScale);
267ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
268ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
269ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void scaleBy(float s, float focusX, float focusY) {
270ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
271676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // We want to keep the focus point (on the bitmap) the same as when
272676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // we begin the scale guesture, that is,
273676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //
274676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
275676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //
276676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        s *= getTargetScale();
277676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
278676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
279676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
280676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        startAnimation(x, y, s, ANIM_KIND_SCALE);
281ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
282ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
283ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void endScale() {
284ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mInScale = false;
285ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        startSnapbackIfNeeded();
286ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
287ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
288ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public float getCurrentScale() {
289ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return mCurrentScale;
290ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
291ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
292ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public boolean isAtMinimalScale() {
293ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return isAlmostEquals(mCurrentScale, mScaleMin);
294ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
295ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
296ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private static boolean isAlmostEquals(float a, float b) {
297ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float diff = a - b;
298ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return (diff < 0 ? -diff : diff) < 0.02f;
299ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
300ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
301ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void up() {
302ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        startSnapback();
303ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
304ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
305676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    //             |<--| (1/2) * mImageW
306676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // +-------+-------+-------+
307676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // |       |       |       |
308676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // |       |   o   |       |
309676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // |       |       |       |
310676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // +-------+-------+-------+
311676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // |<----------| (3/2) * mImageW
312676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Slide in the image from left or right.
313676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
314676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Sliding from left:  mCurrentX = (1/2) * mImageW
315676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    //              right: mCurrentX = (3/2) * mImageW
316ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void startSlideInAnimation(int direction) {
317ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
318676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                mImageW / 2 : 3 * mImageW / 2;
319676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        mFromX = Math.round(fromX);
320ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromY = Math.round(mImageH / 2f);
321ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentX = mFromX;
322ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentY = mFromY;
323676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        startAnimation(
324676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
325ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
326ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
327ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public void startHorizontalSlide(int distance) {
328ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        scrollBy(distance, 0, ANIM_KIND_SLIDE);
329ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
330ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
331676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    private void scrollBy(float dx, float dy, int type) {
332676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
333676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                getTargetY() + Math.round(dy / mCurrentScale),
334676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                mCurrentScale, type);
335676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    }
336676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
337532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    public void startScroll(float dx, float dy, boolean hasNext,
338532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            boolean hasPrev) {
339532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        int x = getTargetX() + Math.round(dx / mCurrentScale);
340532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        int y = getTargetY() + Math.round(dy / mCurrentScale);
341532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
342532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        calculateStableBound(mCurrentScale);
343532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
344532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Vertical direction: If we have space to move in the vertical
345532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // direction, we show the edge effect when scrolling reaches the edge.
346532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (mBoundTop != mBoundBottom) {
347532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            if (y < mBoundTop) {
348532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang                mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
349532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            } else if (y > mBoundBottom) {
350532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang                mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
351532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            }
352532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
353532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
354532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        y = Utils.clamp(y, mBoundTop, mBoundBottom);
355532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
356532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Horizontal direction: we show the edge effect when the scrolling
357532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // tries to go left of the first image or go right of the last image.
358532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (!hasPrev && x < mBoundLeft) {
359532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
360532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onPull(pixels, EdgeView.LEFT);
361532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            x = mBoundLeft;
362532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        } else if (!hasNext && x > mBoundRight) {
363532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int pixels = Math.round((x - mBoundRight) * mCurrentScale);
364532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onPull(pixels, EdgeView.RIGHT);
365532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            x = mBoundRight;
366532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
367532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
368532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
369532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
370532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
371b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    public boolean fling(float velocityX, float velocityY) {
372b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // We only want to do fling when the picture is zoomed-in.
373b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (mImageW * mCurrentScale <= mViewW &&
374b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            mImageH * mCurrentScale <= mViewH) {
375b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            return false;
376b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
377b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
378b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        calculateStableBound(mCurrentScale);
379b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mScroller.fling(mCurrentX, mCurrentY,
380b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                Math.round(-velocityX / mCurrentScale),
381b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                Math.round(-velocityY / mCurrentScale),
382b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
383b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int targetX = mScroller.getFinalX();
384b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int targetY = mScroller.getFinalY();
385b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mAnimationDuration = mScroller.getDuration();
386b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
387b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return true;
388b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
389b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
390ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private void startAnimation(
391b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            int targetX, int targetY, float scale, int kind) {
392b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (targetX == mCurrentX && targetY == mCurrentY
393ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                && scale == mCurrentScale) return;
394ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
395ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromX = mCurrentX;
396ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromY = mCurrentY;
397ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mFromScale = mCurrentScale;
398ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
399b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mToX = targetX;
400b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mToY = targetY;
401ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax);
402ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
403b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // If the scaled height is smaller than the view height,
404ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        // force it to be in the center.
405b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // (We do for height only, not width, because the user may
406b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // want to scroll to the previous/next image.)
407ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (Math.floor(mImageH * mToScale) <= mViewH) {
408ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mToY = mImageH / 2;
409ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
410ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
411ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mAnimationStartTime = SystemClock.uptimeMillis();
412ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mAnimationKind = kind;
413b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (mAnimationKind != ANIM_KIND_FLING) {
414b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            mAnimationDuration = ANIM_TIME[mAnimationKind];
415b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
416ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (advanceAnimation()) mViewer.invalidate();
417ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
418ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
419ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // Returns true if redraw is needed.
420ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public boolean advanceAnimation() {
421ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mAnimationStartTime == NO_ANIMATION) {
422ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            return false;
423ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        } else if (mAnimationStartTime == LAST_ANIMATION) {
424ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mAnimationStartTime = NO_ANIMATION;
425ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            if (mViewer.isInTransition()) {
426ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                mViewer.notifyTransitionComplete();
427ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                return false;
428ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            } else {
429ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                return startSnapbackIfNeeded();
430ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            }
431ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
432ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
433b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        long now = SystemClock.uptimeMillis();
434ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float progress;
435b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (mAnimationDuration == 0) {
436ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            progress = 1;
437ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        } else {
438b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            progress = (now - mAnimationStartTime) / mAnimationDuration;
439ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
440ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
441ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (progress >= 1) {
442ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            progress = 1;
443ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentX = mToX;
444ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentY = mToY;
445ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mCurrentScale = mToScale;
446ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            mAnimationStartTime = LAST_ANIMATION;
447ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        } else {
448ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            float f = 1 - progress;
449676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang            switch (mAnimationKind) {
450676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                case ANIM_KIND_SCROLL:
451b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                case ANIM_KIND_FLING:
452676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    progress = 1 - f;  // linear
453676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    break;
454676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                case ANIM_KIND_SCALE:
455676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    progress = 1 - f * f;  // quadratic
456676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    break;
457676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                case ANIM_KIND_SNAPBACK:
458676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                case ANIM_KIND_ZOOM:
459676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                case ANIM_KIND_SLIDE:
460676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    progress = 1 - f * f * f * f * f; // x^5
461676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang                    break;
462ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            }
463b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            if (mAnimationKind == ANIM_KIND_FLING) {
464b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                flingInterpolate(progress);
465b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else {
466b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                linearInterpolate(progress);
467b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            }
468ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
469ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
470ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return true;
471ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
472ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
473b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private void flingInterpolate(float progress) {
474b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mScroller.computeScrollOffset(progress);
475532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        int oldX = mCurrentX;
476532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        int oldY = mCurrentY;
477b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mCurrentX = mScroller.getCurrX();
478b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mCurrentY = mScroller.getCurrY();
479532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
480532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        // Check if we hit the edges; show edge effects if we do.
481532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
482532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
483532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onAbsorb(v, EdgeView.LEFT);
484532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
485532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
486532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onAbsorb(v, EdgeView.RIGHT);
487532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
488532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
489532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        if (oldY > mBoundTop && mCurrentY == mBoundTop) {
490532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
491532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onAbsorb(v, EdgeView.TOP);
492532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
493532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
494532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang            mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
495532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        }
496b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
497b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
498676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang    // Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
499ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private void linearInterpolate(float progress) {
500676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // To linearly interpolate the position on view coordinates, we do the
501676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // following steps:
502676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (1) convert a bitmap position (x, y) to view coordinates:
503676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     from: (x - mFromX) * mFromScale + mViewW / 2
504676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     to: (x - mToX) * mToScale + mViewW / 2
505676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (2) interpolate between the "from" and "to" coordinates:
506676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
507676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     + mViewW / 2
508676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     should be equal to
509676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     (x - mCurrentX) * mCurrentScale + mViewW / 2
510676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (3) The x-related terms in the above equation can be removed because
511676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        //     mFromScale * (1 - p) + ToScale * p = mCurrentScale
512676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (4) Solve for mCurrentX, we have mCurrentX =
513676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        // (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
514676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        float fromX = mFromX * mFromScale;
515676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        float toX = mToX * mToScale;
516ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float currentX = fromX + progress * (toX - fromX);
517ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
518676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        float fromY = mFromY * mFromScale;
519676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        float toY = mToY * mToScale;
520ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float currentY = fromY + progress * (toY - fromY);
521ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
522ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
523676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        mCurrentX = Math.round(currentX / mCurrentScale);
524676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang        mCurrentY = Math.round(currentY / mCurrentScale);
525ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
526ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
527ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    // Returns true if redraw is needed.
528ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private boolean startSnapbackIfNeeded() {
529ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mAnimationStartTime != NO_ANIMATION) return false;
530ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mInScale) return false;
531ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
532ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            return false;
533ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
534ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return startSnapback();
535ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
536ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
537ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public boolean startSnapback() {
538ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        boolean needAnimation = false;
539ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float scale = mCurrentScale;
540ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
541ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) {
542ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            needAnimation = true;
543ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
544ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
545ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
546b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        calculateStableBound(scale);
547b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
548b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
549ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
550b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
551ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            needAnimation = true;
552ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
553ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
554ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        if (needAnimation) {
555ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
556ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
557ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
558ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return needAnimation;
559ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
560ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
561b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // Calculates the stable region of mCurrent{X/Y}, where "stable" means
562b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
563b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (1) If the dimension of scaled image >= view dimension, we will not
564b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // see black region outside the image (at that dimension).
565b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // (2) If the dimension of scaled image < view dimension, we will center
566b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // the scaled image.
567b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
568b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // We might temporarily go out of this stable during user interaction,
569b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // but will "snap back" after user stops interaction.
570b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
571b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    // The results are stored in mBound{Left/Right/Top/Bottom}.
572b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    //
573b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private void calculateStableBound(float scale) {
574b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // The number of pixels between the center of the view
575b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // and the edge when the edge is aligned.
576b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mBoundLeft = (int) Math.ceil(mViewW / (2 * scale));
577b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mBoundRight = mImageW - mBoundLeft;
578b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mBoundTop = (int) Math.ceil(mViewH / (2 * scale));
579b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        mBoundBottom = mImageH - mBoundTop;
580b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
581b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // If the scaled height is smaller than the view height,
582b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // force it to be in the center.
583b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (Math.floor(mImageH * scale) <= mViewH) {
584b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            mBoundTop = mBoundBottom = mImageH / 2;
585b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
586b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
587b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        // Same for width
588b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        if (Math.floor(mImageW * scale) <= mViewW) {
589b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            mBoundLeft = mBoundRight = mImageW / 2;
590b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        }
591b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
592b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
593b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    private boolean useCurrentValueAsTarget() {
594b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return mAnimationStartTime == NO_ANIMATION ||
595b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mAnimationKind == ANIM_KIND_SNAPBACK ||
596b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mAnimationKind == ANIM_KIND_FLING;
597b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang    }
598b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang
599ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private float getTargetScale() {
600b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
601ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
602ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
603ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int getTargetX() {
604b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return useCurrentValueAsTarget() ? mCurrentX : mToX;
605ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
606ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
607ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    private int getTargetY() {
608b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang        return useCurrentValueAsTarget() ? mCurrentY : mToY;
609ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
610ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
611ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public RectF getImageBounds() {
612ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float points[] = mTempPoints;
613ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
614ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        /*
615ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang         * (p0,p1)----------(p2,p3)
616ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang         *   |                  |
617ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang         *   |                  |
618ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang         * (p4,p5)----------(p6,p7)
619ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang         */
620ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        points[0] = points[4] = -mCurrentX;
621ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        points[1] = points[3] = -mCurrentY;
622ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        points[2] = points[6] = mImageW - mCurrentX;
623ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        points[5] = points[7] = mImageH - mCurrentY;
624ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
625ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        RectF rect = mTempRect;
626ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
627ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang                Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
628ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
629ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float scale = mCurrentScale;
630ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float offsetX = mViewW / 2;
631ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        float offsetY = mViewH / 2;
632ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        for (int i = 0; i < 4; ++i) {
633ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            float x = points[i + i] * scale + offsetX;
634ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            float y = points[i + i + 1] * scale + offsetY;
635ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            if (x < rect.left) rect.left = x;
636ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            if (x > rect.right) rect.right = x;
637ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            if (y < rect.top) rect.top = y;
638ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang            if (y > rect.bottom) rect.bottom = y;
639ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        }
640ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return rect;
641ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
642ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
643ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public int getImageWidth() {
644ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return mImageW;
645ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
646ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
647ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    public int getImageHeight() {
648ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang        return mImageH;
649ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
650532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
651532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    public boolean isAtLeftEdge() {
652532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        calculateStableBound(mCurrentScale);
653532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        return mCurrentX <= mBoundLeft;
654532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
655532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
656532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    public boolean isAtRightEdge() {
657532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        calculateStableBound(mCurrentScale);
658532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        return mCurrentX >= mBoundRight;
659532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    }
660ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang}
661