1f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/*
2f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Copyright (C) 2011 Google Inc.
3f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed to The Android Open Source Project.
4f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
5f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
6f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * you may not use this file except in compliance with the License.
7f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * You may obtain a copy of the License at
8f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
9f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
10f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein *
11f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Unless required by applicable law or agreed to in writing, software
12f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
13f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * See the License for the specific language governing permissions and
15f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * limitations under the License.
16f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */
17f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
18f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinpackage com.android.ex.photo.views;
19f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
20f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.content.Context;
21f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.content.res.Resources;
22f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Bitmap;
23f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Canvas;
24f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Matrix;
25f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Paint;
26f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Paint.Style;
27f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.Rect;
28f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.RectF;
29f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.graphics.drawable.BitmapDrawable;
304f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerianimport android.graphics.drawable.Drawable;
31bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrookimport android.support.v4.view.GestureDetectorCompat;
3269d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpiniimport android.support.v4.view.ScaleGestureDetectorCompat;
33f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.util.AttributeSet;
34bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrookimport android.view.GestureDetector.OnDoubleTapListener;
357887d9694aef23e4a0165b338726e6a9690a709aAndrew Sappersteinimport android.view.GestureDetector.OnGestureListener;
36f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.view.MotionEvent;
37f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.view.ScaleGestureDetector;
38f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.view.View;
39f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereiraimport android.view.ViewConfiguration;
40f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
41f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport com.android.ex.photo.R;
42f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport com.android.ex.photo.fragments.PhotoViewFragment.HorizontallyScrollable;
43f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
44f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/**
45f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Layout for the photo list view header.
46f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */
47bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrookpublic class PhotoView extends View implements OnGestureListener,
48bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrook        OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener,
49f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        HorizontallyScrollable {
50b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha
51b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha    public static final int TRANSLATE_NONE = 0;
52b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha    public static final int TRANSLATE_X_ONLY = 1;
53b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha    public static final int TRANSLATE_Y_ONLY = 2;
54b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha    public static final int TRANSLATE_BOTH = 3;
55b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha
56f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Zoom animation duration; in milliseconds */
570ea7a6f43890bddfff61db47074a805f7effec1fkhoaha    private final static long ZOOM_ANIMATION_DURATION = 200L;
58ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha    /** Amount of time to wait after over-zooming before the zoom out animation; in milliseconds */
59ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha    private static final long ZOOM_CORRECTION_DELAY = 600L;
60f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Rotate animation duration; in milliseconds */
61f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private final static long ROTATE_ANIMATION_DURATION = 500L;
62f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Snap animation duration; in milliseconds */
63f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static final long SNAP_DURATION = 100L;
64f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Amount of time to wait before starting snap animation; in milliseconds */
65f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static final long SNAP_DELAY = 250L;
66f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** By how much to scale the image when double click occurs */
670ea7a6f43890bddfff61db47074a805f7effec1fkhoaha    private final static float DOUBLE_TAP_SCALE_FACTOR = 2.0f;
68c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha    /** Amount which can be zoomed in past the maximum scale, and then scaled back */
69c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha    private final static float SCALE_OVERZOOM_FACTOR = 1.5f;
70f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Amount of translation needed before starting a snap animation */
71f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private final static float SNAP_THRESHOLD = 20.0f;
72f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The width & height of the bitmap returned by {@link #getCroppedPhoto()} */
73f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private final static float CROPPED_SIZE = 256.0f;
74f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
75f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    /**
76f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * Touch slop used to determine if this double tap is valid for starting a scale or should be
77f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * ignored.
78f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     */
79f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    private static int sTouchSlopSquare;
80f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
81f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** If {@code true}, the static values have been initialized */
82f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static boolean sInitialized;
83f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
84f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // Various dimensions
85f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Width & height of the crop region */
86f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static int sCropSize;
87f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
88f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // Bitmaps
89f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Video icon */
90f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static Bitmap sVideoImage;
91f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Video icon */
92f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static Bitmap sVideoNotReadyImage;
93f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
94f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // Paints
95f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Paint to partially dim the photo during crop */
96f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static Paint sCropDimPaint;
97f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Paint to highlight the cropped portion of the photo */
98f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static Paint sCropPaint;
99f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
100f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The photo to display */
1014f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    private Drawable mDrawable;
102f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The matrix used for drawing; this may be {@code null} */
103f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private Matrix mDrawMatrix;
104f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** A matrix to apply the scaling of the photo */
105f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private Matrix mMatrix = new Matrix();
106f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The original matrix for this image; used to reset any transformations applied by the user */
107f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private Matrix mOriginalMatrix = new Matrix();
108f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
109f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The fixed height of this view. If {@code -1}, calculate the height */
110f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private int mFixedHeight = -1;
111f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** When {@code true}, the view has been laid out */
112f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mHaveLayout;
113f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Whether or not the photo is full-screen */
114f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mFullScreen;
115f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Whether or not this is a still image of a video */
116f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private byte[] mVideoBlob;
117f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Whether or not this is a still image of a video */
118f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mVideoReady;
119f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
120f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Whether or not crop is allowed */
121f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mAllowCrop;
122f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The crop region */
123f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private Rect mCropRect = new Rect();
124f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Actual crop size; may differ from {@link #sCropSize} if the screen is smaller */
125f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private int mCropSize;
12691dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp    /** The maximum amount of scaling to apply to images */
12791dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp    private float mMaxInitialScaleFactor;
128f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
129f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Gesture detector */
130bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrook    private GestureDetectorCompat mGestureDetector;
131f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Gesture detector that detects pinch gestures */
132f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private ScaleGestureDetector mScaleGetureDetector;
133f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** An external click listener */
134f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private OnClickListener mExternalClickListener;
135f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** When {@code true}, allows gestures to scale / pan the image */
136f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mTransformsEnabled;
137f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
138f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // To support zooming
139f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** When {@code true}, a double tap scales the image by {@link #DOUBLE_TAP_SCALE_FACTOR} */
140f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mDoubleTapToZoomEnabled = true;
141f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** When {@code true}, prevents scale end gesture from falsely triggering a double click. */
142f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mDoubleTapDebounce;
143f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** When {@code false}, event is a scale gesture. Otherwise, event is a double touch. */
144f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private boolean mIsDoubleTouch;
145f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Runnable that scales the image */
146f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private ScaleRunnable mScaleRunnable;
147f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Minimum scale the image can have. */
148f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mMinScale;
149f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Maximum scale to limit scaling to, 0 means no limit. */
150f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mMaxScale;
151f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
152f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // To support translation [i.e. panning]
153f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Runnable that can move the image */
154f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private TranslateRunnable mTranslateRunnable;
155f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private SnapRunnable mSnapRunnable;
156f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
157f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // To support rotation
158f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The rotate runnable used to animate rotations of the image */
159f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private RotateRunnable mRotateRunnable;
160f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** The current rotation amount, in degrees */
161f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float mRotation;
162f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
163f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // Convenience fields
164f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // These are declared here not because they are important properties of the view. Rather, we
165f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // declare them here to avoid object allocation during critical graphics operations; such as
166f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    // layout or drawing.
167f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Source (i.e. the photo size) bounds */
168f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private RectF mTempSrc = new RectF();
169f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Destination (i.e. the display) bounds. The image is scaled to this size. */
170f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private RectF mTempDst = new RectF();
171f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Rectangle to handle translations */
172f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private RectF mTranslateRect = new RectF();
173f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /** Array to store a copy of the matrix values */
174f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float[] mValues = new float[9];
175f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
176f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    /**
177f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * Track whether a double tap event occurred.
178f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     */
179f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    private boolean mDoubleTapOccurred;
180f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
181f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    /**
182f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * X and Y coordinates for the current down event. Since mDoubleTapOccurred only contains the
183f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * information that there was a double tap event, use these to get the secondary tap
184f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     * information to determine if a user has moved beyond touch slop.
185f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira     */
186f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    private float mDownFocusX;
187f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira    private float mDownFocusY;
188f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
18969d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini    /**
19069d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini     * Whether the QuickSale gesture is enabled.
19169d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini     */
19269d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini    private boolean mQuickScaleEnabled;
19369d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini
194f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public PhotoView(Context context) {
195f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super(context);
196f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        initialize();
197f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
198f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
199f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public PhotoView(Context context, AttributeSet attrs) {
200f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super(context, attrs);
201f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        initialize();
202f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
203f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
204f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public PhotoView(Context context, AttributeSet attrs, int defStyle) {
205f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super(context, attrs, defStyle);
206f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        initialize();
207f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
208f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
209f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
210f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onTouchEvent(MotionEvent event) {
211f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mScaleGetureDetector == null || mGestureDetector == null) {
212f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // We're being destroyed; ignore any touch events
213f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
214f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
215f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
216f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleGetureDetector.onTouchEvent(event);
217f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mGestureDetector.onTouchEvent(event);
218f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int action = event.getAction();
219f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
220f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        switch (action) {
221f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            case MotionEvent.ACTION_UP:
222f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            case MotionEvent.ACTION_CANCEL:
223f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (!mTranslateRunnable.mRunning) {
224f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    snap();
225f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
226f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                break;
227f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
228f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
229f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
230f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
231f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
232f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
233f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onDoubleTap(MotionEvent e) {
234f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        mDoubleTapOccurred = true;
23569d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        if (!mQuickScaleEnabled) {
23669d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini            return scale(e);
23769d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        }
238f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        return false;
239f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
240f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
241f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
242f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onDoubleTapEvent(MotionEvent e) {
243f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        final int action = e.getAction();
244f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        boolean handled = false;
245f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
246f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        switch (action) {
247f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            case MotionEvent.ACTION_DOWN:
24869d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                if (mQuickScaleEnabled) {
24969d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                    mDownFocusX = e.getX();
25069d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                    mDownFocusY = e.getY();
25169d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                }
252f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                break;
253f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            case MotionEvent.ACTION_UP:
25469d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                if (mQuickScaleEnabled) {
25569d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                    handled = scale(e);
256f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                }
257f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                break;
258f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            case MotionEvent.ACTION_MOVE:
25969d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                if (mQuickScaleEnabled && mDoubleTapOccurred) {
260f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                    final int deltaX = (int) (e.getX() - mDownFocusX);
261f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                    final int deltaY = (int) (e.getY() - mDownFocusY);
262f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                    int distance = (deltaX * deltaX) + (deltaY * deltaY);
263f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                    if (distance > sTouchSlopSquare) {
264f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                        mDoubleTapOccurred = false;
265f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                    }
266f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                }
267f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira                break;
268f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
269f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        }
270f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        return handled;
271f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
272f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
27369d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini    private boolean scale(MotionEvent e) {
27469d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        boolean handled = false;
27569d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        if (mDoubleTapToZoomEnabled && mTransformsEnabled && mDoubleTapOccurred) {
27669d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini            if (!mDoubleTapDebounce) {
27769d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                float currentScale = getScale();
2780ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                float targetScale;
2790ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                float centerX, centerY;
2800ea7a6f43890bddfff61db47074a805f7effec1fkhoaha
2810ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                // Zoom out if not default scale, otherwise zoom in
2820ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                if (currentScale > mMinScale) {
2830ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                    targetScale = mMinScale;
2840ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                    float relativeScale = targetScale / currentScale;
2850ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                    // Find the apparent origin for scaling that equals this scale and translate
2860ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                    centerX = (getWidth() / 2 - relativeScale * mTranslateRect.centerX()) /
2870ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                            (1 - relativeScale);
2880ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                    centerY = (getHeight() / 2 - relativeScale * mTranslateRect.centerY()) /
2890ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                            (1 - relativeScale);
2900ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                } else {
2910ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     targetScale = currentScale * DOUBLE_TAP_SCALE_FACTOR;
2920ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     // Ensure the target scale is within our bounds
2930ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     targetScale = Math.max(mMinScale, targetScale);
2940ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     targetScale = Math.min(mMaxScale, targetScale);
2950ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     float relativeScale = targetScale / currentScale;
2960ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     float widthBuffer = (getWidth() - mTranslateRect.width()) / relativeScale;
2970ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     float heightBuffer = (getHeight() - mTranslateRect.height()) / relativeScale;
2980ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     // Clamp the center if it would result in uneven borders
2990ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     if (mTranslateRect.width() <= widthBuffer * 2) {
3000ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                         centerX = mTranslateRect.centerX();
3010ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     } else {
3020ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                         centerX = Math.min(Math.max(mTranslateRect.left + widthBuffer,
3030ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                                 e.getX()), mTranslateRect.right - widthBuffer);
3040ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     }
3050ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     if (mTranslateRect.height() <= heightBuffer * 2) {
3060ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                         centerY = mTranslateRect.centerY();
3070ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     } else {
3080ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                         centerY = Math.min(Math.max(mTranslateRect.top + heightBuffer,
3090ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                                 e.getY()), mTranslateRect.bottom - heightBuffer);
3100ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                     }
3110ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                }
31269d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini
3130ea7a6f43890bddfff61db47074a805f7effec1fkhoaha                mScaleRunnable.start(currentScale, targetScale, centerX, centerY);
31469d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini                handled = true;
31569d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini            }
31669d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini            mDoubleTapDebounce = false;
31769d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        }
31869d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        mDoubleTapOccurred = false;
31969d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        return handled;
32069d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini    }
32169d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini
322f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
323f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onSingleTapConfirmed(MotionEvent e) {
324f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mExternalClickListener != null && !mIsDoubleTouch) {
325f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mExternalClickListener.onClick(this);
326f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
327f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mIsDoubleTouch = false;
328f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
329f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
330f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
331f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
332f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onSingleTapUp(MotionEvent e) {
333f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return false;
334f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
335f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
336f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
337f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void onLongPress(MotionEvent e) {
338f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
339f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
340f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
341f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void onShowPress(MotionEvent e) {
342f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
343f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
344f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
345f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
346c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha        if (mTransformsEnabled && !mScaleRunnable.mRunning) {
347190f6bd9a66ab01364451e22cb7992f86227a4c6Paul Westbrook            translate(-distanceX, -distanceY);
348f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
349f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
350f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
351f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
352f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
353f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onDown(MotionEvent e) {
354f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mTransformsEnabled) {
355f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateRunnable.stop();
356f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mSnapRunnable.stop();
357f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
358f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
359f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
360f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
361f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
362f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
363c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha        if (mTransformsEnabled && !mScaleRunnable.mRunning) {
364bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrook            mTranslateRunnable.start(velocityX, velocityY);
365f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
366f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
367f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
368f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
369f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
370f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onScale(ScaleGestureDetector detector) {
371ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        if (mTransformsEnabled && !mScaleRunnable.mRunning) {
372f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mIsDoubleTouch = false;
373f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float currentScale = getScale();
374f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float newScale = currentScale * detector.getScaleFactor();
375f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            scale(newScale, detector.getFocusX(), detector.getFocusY());
376f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
377f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
378f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
379f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
380f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
381f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean onScaleBegin(ScaleGestureDetector detector) {
382ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        if (mTransformsEnabled && !mScaleRunnable.mRunning) {
383f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mScaleRunnable.stop();
384f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mIsDoubleTouch = true;
385f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
386f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return true;
387f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
388f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
389f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
390f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void onScaleEnd(ScaleGestureDetector detector) {
391f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mTransformsEnabled && mIsDoubleTouch) {
392f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDoubleTapDebounce = true;
393f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            resetTransformations();
394f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
395f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
396f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
397f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
398f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void setOnClickListener(OnClickListener listener) {
399f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mExternalClickListener = listener;
400f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
401f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
402f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
403f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean interceptMoveLeft(float origX, float origY) {
404f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!mTransformsEnabled) {
405f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Allow intercept if we're not in transform mode
406f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return false;
407f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (mTranslateRunnable.mRunning) {
408f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Don't allow touch intercept until we've stopped flinging
409f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
410f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
411f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.getValues(mValues);
412f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateRect.set(mTempSrc);
413f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.mapRect(mTranslateRect);
414f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
415f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float viewWidth = getWidth();
416f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float transX = mValues[Matrix.MTRANS_X];
417f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float drawWidth = mTranslateRect.right - mTranslateRect.left;
418f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
419f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (!mTransformsEnabled || drawWidth <= viewWidth) {
420f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // Allow intercept if not in transform mode or the image is smaller than the view
421f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
422f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else if (transX == 0) {
423f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're at the left-side of the image; allow intercepting movements to the right
424f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
425f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else if (viewWidth >= drawWidth + transX) {
426f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're at the right-side of the image; allow intercepting movements to the left
427f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return true;
428f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
429f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're in the middle of the image; don't allow touch intercept
430f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return true;
431f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
432f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
433f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
434f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
435f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
436f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean interceptMoveRight(float origX, float origY) {
437f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!mTransformsEnabled) {
438f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Allow intercept if we're not in transform mode
439f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return false;
440f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (mTranslateRunnable.mRunning) {
441f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Don't allow touch intercept until we've stopped flinging
442f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
443f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
444f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.getValues(mValues);
445f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateRect.set(mTempSrc);
446f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.mapRect(mTranslateRect);
447f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
448f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float viewWidth = getWidth();
449f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float transX = mValues[Matrix.MTRANS_X];
450f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final float drawWidth = mTranslateRect.right - mTranslateRect.left;
451f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
452f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (!mTransformsEnabled || drawWidth <= viewWidth) {
453f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // Allow intercept if not in transform mode or the image is smaller than the view
454f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
455f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else if (transX == 0) {
456f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're at the left-side of the image; allow intercepting movements to the right
457f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return true;
458f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else if (viewWidth >= drawWidth + transX) {
459f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're at the right-side of the image; allow intercepting movements to the left
460f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
461f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
462f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // We're in the middle of the image; don't allow touch intercept
463f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return true;
464f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
465f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
466f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
467f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
468f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
469f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Free all resources held by this view.
470f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * The view is on its way to be collected and will not be reused.
471f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
472f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void clear() {
473f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mGestureDetector = null;
474f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleGetureDetector = null;
475f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mDrawable = null;
476f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleRunnable.stop();
477f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleRunnable = null;
478f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTranslateRunnable.stop();
479f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTranslateRunnable = null;
480f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mSnapRunnable.stop();
481f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mSnapRunnable = null;
482f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mRotateRunnable.stop();
483f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mRotateRunnable = null;
484f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        setOnClickListener(null);
485f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mExternalClickListener = null;
486f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira        mDoubleTapOccurred = false;
487f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
488f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
4894f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    public void bindDrawable(Drawable drawable) {
49061be90be69b3489471a8f6927737e9c850ed8051Andrew Sapperstein        boolean changed = false;
4914f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        if (drawable != null && drawable != mDrawable) {
4924f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            // Clear previous state.
4934f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            if (mDrawable != null) {
4944f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian                mDrawable.setCallback(null);
4954f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            }
4964f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
4974f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            mDrawable = drawable;
4984f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
4994f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            // Reset mMinScale to ensure the bounds / matrix are recalculated
5004f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            mMinScale = 0f;
5014f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
5024f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            // Set a callback?
5034f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            mDrawable.setCallback(this);
5044f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
50561be90be69b3489471a8f6927737e9c850ed8051Andrew Sapperstein            changed = true;
5064f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        }
50761be90be69b3489471a8f6927737e9c850ed8051Andrew Sapperstein
50861be90be69b3489471a8f6927737e9c850ed8051Andrew Sapperstein        configureBounds(changed);
50961be90be69b3489471a8f6927737e9c850ed8051Andrew Sapperstein        invalidate();
5104f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    }
5114f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
512f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
513f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Binds a bitmap to the view.
514f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
515f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param photoBitmap the bitmap to bind.
516f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
517f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void bindPhoto(Bitmap photoBitmap) {
5184f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        boolean currentDrawableIsBitmapDrawable = mDrawable instanceof BitmapDrawable;
5194f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        boolean changed = !(currentDrawableIsBitmapDrawable);
5204f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        if (mDrawable != null && currentDrawableIsBitmapDrawable) {
5214f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            final Bitmap drawableBitmap = ((BitmapDrawable) mDrawable).getBitmap();
522f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (photoBitmap == drawableBitmap) {
523f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                // setting the same bitmap; do nothing
524f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
525f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
526f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
527f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            changed = photoBitmap != null &&
528f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    (mDrawable.getIntrinsicWidth() != photoBitmap.getWidth() ||
529f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    mDrawable.getIntrinsicHeight() != photoBitmap.getHeight());
530f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
531f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Reset mMinScale to ensure the bounds / matrix are recalculated
532f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMinScale = 0f;
533f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawable = null;
534f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
535f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
536f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mDrawable == null && photoBitmap != null) {
537f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawable = new BitmapDrawable(getResources(), photoBitmap);
538f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
539f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
540f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        configureBounds(changed);
541f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        invalidate();
542f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
543f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
544f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
545f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Returns the bound photo data if set. Otherwise, {@code null}.
546f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
547f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public Bitmap getPhoto() {
5484f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        if (mDrawable != null && mDrawable instanceof BitmapDrawable) {
5494f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            return ((BitmapDrawable) mDrawable).getBitmap();
550f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
551f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return null;
552f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
553f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
554f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
5558728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian     * Returns the bound drawable. May be {@code null} if no drawable is bound.
5568728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian     */
5578728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian    public Drawable getDrawable() {
5588728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian        return mDrawable;
5598728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian    }
5608728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian
5618728a3dbb1bc7b31790d1de866ab149e0d1f77b4Matt Keoshkerian    /**
562f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Gets video data associated with this item. Returns {@code null} if this is not a video.
563f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
564f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public byte[] getVideoData() {
565f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mVideoBlob;
566f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
567f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
568f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
569f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Returns {@code true} if the photo represents a video. Otherwise, {@code false}.
570f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
571f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean isVideo() {
572f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mVideoBlob != null;
573f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
574f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
575f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
576f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Returns {@code true} if the video is ready to play. Otherwise, {@code false}.
577f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
578f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean isVideoReady() {
579f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mVideoBlob != null && mVideoReady;
580f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
581f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
582f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
583f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Returns {@code true} if a photo has been bound. Otherwise, {@code false}.
584f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
585f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public boolean isPhotoBound() {
586f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mDrawable != null;
587f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
588f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
589f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
590f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Hides the photo info portion of the header. As a side effect, this automatically enables
591f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * or disables image transformations [eg zoom, pan, etc...] depending upon the value of
592f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * fullScreen. If this is not desirable, enable / disable image transformations manually.
593f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
594f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void setFullScreen(boolean fullScreen, boolean animate) {
595f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (fullScreen != mFullScreen) {
596f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mFullScreen = fullScreen;
597f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            requestLayout();
598f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            invalidate();
599f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
600f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
601f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
602f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
603f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Enable or disable cropping of the displayed image. Cropping can only be enabled
604f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * <em>before</em> the view has been laid out. Additionally, once cropping has been
605f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * enabled, it cannot be disabled.
606f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
607f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void enableAllowCrop(boolean allowCrop) {
608f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (allowCrop && mHaveLayout) {
609f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            throw new IllegalArgumentException("Cannot set crop after view has been laid out");
610f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
611f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!allowCrop && mAllowCrop) {
612f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            throw new IllegalArgumentException("Cannot unset crop mode");
613f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
614f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mAllowCrop = allowCrop;
615f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
616f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
617f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
618f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Gets a bitmap of the cropped region. If cropping is not enabled, returns {@code null}.
619f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
620f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public Bitmap getCroppedPhoto() {
621f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!mAllowCrop) {
622f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return null;
623f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
624f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
625f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final Bitmap croppedBitmap = Bitmap.createBitmap(
626f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                (int) CROPPED_SIZE, (int) CROPPED_SIZE, Bitmap.Config.ARGB_8888);
627f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final Canvas croppedCanvas = new Canvas(croppedBitmap);
628f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
629f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // scale for the final dimensions
630f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int cropWidth = mCropRect.right - mCropRect.left;
631f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float scaleWidth = CROPPED_SIZE / cropWidth;
632f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float scaleHeight = CROPPED_SIZE / cropWidth;
633f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
634f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // translate to the origin & scale
635f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final Matrix matrix = new Matrix(mDrawMatrix);
636f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        matrix.postTranslate(-mCropRect.left, -mCropRect.top);
637f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        matrix.postScale(scaleWidth, scaleHeight);
638f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
639f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // draw the photo
640f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mDrawable != null) {
641f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            croppedCanvas.concat(matrix);
642f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawable.draw(croppedCanvas);
643f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
644f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return croppedBitmap;
645f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
646f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
647f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
648f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Resets the image transformation to its original value.
649f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
650f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void resetTransformations() {
651f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // snap transformations; we don't animate
652f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.set(mOriginalMatrix);
653f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
654928a39fb533255b34145285fabbd4f51961df63aAndrew Sapperstein        // Invalidate the view because if you move off this PhotoView
655f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // to another one and come back, you want it to draw from scratch
656f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // in case you were zoomed in or translated (since those settings
657f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // are not preserved and probably shouldn't be).
658f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        invalidate();
659f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
660f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
661f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
662f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Rotates the image 90 degrees, clockwise.
663f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
664f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void rotateClockwise() {
665f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        rotate(90, true);
666f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
667f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
668f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
669f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Rotates the image 90 degrees, counter clockwise.
670f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
671f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void rotateCounterClockwise() {
672f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        rotate(-90, true);
673f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
674f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
675f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
676f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    protected void onDraw(Canvas canvas) {
677f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super.onDraw(canvas);
678f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
679f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // draw the photo
680f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mDrawable != null) {
681f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            int saveCount = canvas.getSaveCount();
682f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            canvas.save();
683f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
684f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mDrawMatrix != null) {
685f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.concat(mDrawMatrix);
686f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
687f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawable.draw(canvas);
688f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
689f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            canvas.restoreToCount(saveCount);
690f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
691f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mVideoBlob != null) {
692f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                final Bitmap videoImage = (mVideoReady ? sVideoImage : sVideoNotReadyImage);
693f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                final int drawLeft = (getWidth() - videoImage.getWidth()) / 2;
694f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                final int drawTop = (getHeight() - videoImage.getHeight()) / 2;
695f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.drawBitmap(videoImage, drawLeft, drawTop, null);
696f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
697f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
698f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Extract the drawable's bounds (in our own copy, to not alter the image)
699f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateRect.set(mDrawable.getBounds());
700f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mDrawMatrix != null) {
701f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mDrawMatrix.mapRect(mTranslateRect);
702f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
703f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
704f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mAllowCrop) {
705f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                int previousSaveCount = canvas.getSaveCount();
706f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.drawRect(0, 0, getWidth(), getHeight(), sCropDimPaint);
707f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.save();
708f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.clipRect(mCropRect);
709f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
710f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (mDrawMatrix != null) {
711f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    canvas.concat(mDrawMatrix);
712f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
713f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
714f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mDrawable.draw(canvas);
715f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.restoreToCount(previousSaveCount);
716f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                canvas.drawRect(mCropRect, sCropPaint);
717f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
718f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
719f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
720f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
721f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
722f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
723f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        super.onLayout(changed, left, top, right, bottom);
724f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mHaveLayout = true;
725f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int layoutWidth = getWidth();
726f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int layoutHeight = getHeight();
727f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
728f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mAllowCrop) {
729f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mCropSize = Math.min(sCropSize, Math.min(layoutWidth, layoutHeight));
730f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final int cropLeft = (layoutWidth - mCropSize) / 2;
731f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final int cropTop = (layoutHeight - mCropSize) / 2;
732f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final int cropRight = cropLeft + mCropSize;
733f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            final int cropBottom =  cropTop + mCropSize;
734f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
735f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Create a crop region overlay. We need a separate canvas to be able to "punch
736f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // a hole" through to the underlying image.
737f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mCropRect.set(cropLeft, cropTop, cropRight, cropBottom);
738f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
739f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        configureBounds(changed);
740f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
741f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
742f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    @Override
743f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
744f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mFixedHeight != -1) {
745f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mFixedHeight,
746f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    MeasureSpec.AT_MOST));
747f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            setMeasuredDimension(getMeasuredWidth(), mFixedHeight);
748f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
749f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
750f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
751f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
752f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
7534f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    @Override
7544f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    public boolean verifyDrawable(Drawable drawable) {
7554f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        return mDrawable == drawable || super.verifyDrawable(drawable);
7564f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    }
7574f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
7584f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    @Override
7594f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    /**
7604f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian     * {@inheritDoc}
7614f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian     */
7624f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    public void invalidateDrawable(Drawable drawable) {
7634f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        // Only invalidate this view if the passed in drawable is displayed within this view. If
7644f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        // another drawable is passed in, have the parent view handle invalidation.
7654f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        if (mDrawable == drawable) {
7664f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            invalidate();
7674f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        } else {
7684f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian            super.invalidateDrawable(drawable);
7694f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian        }
7704f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian    }
7714f49ea6da326194dffa0348a83d8110ac30d92e4Matt Keoshkerian
772f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
773f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Forces a fixed height for this view.
774f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
775f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param fixedHeight The height. If {@code -1}, use the measured height.
776f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
777f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void setFixedHeight(int fixedHeight) {
778f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final boolean adjustBounds = (fixedHeight != mFixedHeight);
779f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mFixedHeight = fixedHeight;
780f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        setMeasuredDimension(getMeasuredWidth(), mFixedHeight);
781f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (adjustBounds) {
782f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            configureBounds(true);
783f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            requestLayout();
784f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
785f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
786f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
787f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
788f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Enable or disable image transformations. When transformations are enabled, this view
789f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * consumes all touch events.
790f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
791f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    public void enableImageTransforms(boolean enable) {
792f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTransformsEnabled = enable;
793f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!mTransformsEnabled) {
794f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            resetTransformations();
795f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
796f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
797f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
798f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
799f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Configures the bounds of the photo. The photo will always be scaled to fit center.
800f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
801f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void configureBounds(boolean changed) {
802f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mDrawable == null || !mHaveLayout) {
803f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return;
804f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
805f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
806f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
807f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
808f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vwidth = getWidth();
809f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vheight = getHeight();
810f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
811f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final boolean fits = (dwidth < 0 || vwidth == dwidth) &&
812f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                (dheight < 0 || vheight == dheight);
813f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
814f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // We need to do the scaling ourself, so have the drawable use its native size.
815f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mDrawable.setBounds(0, 0, dwidth, dheight);
816f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
817f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // Create a matrix with the proper transforms
818f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (changed || (mMinScale == 0 && mDrawable != null && mHaveLayout)) {
819f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            generateMatrix();
820f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            generateScale();
821f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
822f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
823f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (fits || mMatrix.isIdentity()) {
824f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // The bitmap fits exactly, no transform needed.
825f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawMatrix = null;
826f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
827f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mDrawMatrix = mMatrix;
828f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
829f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
830f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
831f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
832f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Generates the initial transformation matrix for drawing. Additionally, it sets the
833f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * minimum and maximum scale values.
834f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
835f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void generateMatrix() {
836f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
837f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
838f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
839f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vwidth = mAllowCrop ? sCropSize : getWidth();
840f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vheight = mAllowCrop ? sCropSize : getHeight();
841f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
842f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final boolean fits = (dwidth < 0 || vwidth == dwidth) &&
843f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                (dheight < 0 || vheight == dheight);
844f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
845f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (fits && !mAllowCrop) {
846f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.reset();
847f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
848f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Generate the required transforms for the photo
849f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTempSrc.set(0, 0, dwidth, dheight);
850f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mAllowCrop) {
851f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mTempDst.set(mCropRect);
852f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
853f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mTempDst.set(0, 0, vwidth, vheight);
854f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
85591dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp            RectF scaledDestination = new RectF(
85691dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp                    (vwidth / 2) - (dwidth * mMaxInitialScaleFactor / 2),
85791dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp                    (vheight / 2) - (dheight * mMaxInitialScaleFactor / 2),
85891dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp                    (vwidth / 2) + (dwidth * mMaxInitialScaleFactor / 2),
85991dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp                    (vheight / 2) + (dheight * mMaxInitialScaleFactor / 2));
86091dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp            if(mTempDst.contains(scaledDestination)) {
86191dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp                mMatrix.setRectToRect(mTempSrc, scaledDestination, Matrix.ScaleToFit.CENTER);
8623a14814364ff1e48109d32a06bd063c66914889bMark Wei            } else {
8633a14814364ff1e48109d32a06bd063c66914889bMark Wei                mMatrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);
8643a14814364ff1e48109d32a06bd063c66914889bMark Wei            }
865f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
866f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mOriginalMatrix.set(mMatrix);
867f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
868f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
869f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void generateScale() {
870f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
871f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
872f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
873f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vwidth = mAllowCrop ? getCropSize() : getWidth();
874f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final int vheight = mAllowCrop ? getCropSize() : getHeight();
875f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
876f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (dwidth < vwidth && dheight < vheight && !mAllowCrop) {
877f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMinScale = 1.0f;
878f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
879f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMinScale = getScale();
880f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
881c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha        mMaxScale = Math.max(mMinScale * 4, 4);
882f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
883f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
884f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
885f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @return the size of the crop regions
886f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
887f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private int getCropSize() {
888f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mCropSize > 0 ? mCropSize : sCropSize;
889f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
890f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
891f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
892f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Returns the currently applied scale factor for the image.
893f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * <p>
894f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * NOTE: This method overwrites any values stored in {@link #mValues}.
895f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
896f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private float getScale() {
897f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.getValues(mValues);
898f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        return mValues[Matrix.MSCALE_X];
899f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
900f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
901f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
902f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Scales the image while keeping the aspect ratio.
903f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
904f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * The given scale is capped so that the resulting scale of the image always remains
905f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * between {@link #mMinScale} and {@link #mMaxScale}.
906f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
9070ea7a6f43890bddfff61db47074a805f7effec1fkhoaha     * If the image is smaller than the viewable area, it will be centered.
908f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
909f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param newScale the new scale
910f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param centerX the center horizontal point around which to scale
911f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param centerY the center vertical point around which to scale
912f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
913f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void scale(float newScale, float centerX, float centerY) {
914ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        // Rotate back to the original orientation
915f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.postRotate(-mRotation, getWidth() / 2, getHeight() / 2);
916f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
917ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        // Ensure that mMinScale <= newScale <= mMaxScale
918f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        newScale = Math.max(newScale, mMinScale);
919c9d25c87e10cc773f167b60a3d7b4b05f0c819eckhoaha        newScale = Math.min(newScale, mMaxScale * SCALE_OVERZOOM_FACTOR);
920f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
921f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float currentScale = getScale();
922ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha
923ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        // Prepare to animate zoom out if over-zooming
924ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        if (newScale > mMaxScale && currentScale <= mMaxScale) {
925ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha            Runnable zoomBackRunnable = new Runnable() {
926ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                @Override
927ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                public void run() {
928ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                    // Scale back to the maximum if over-zoomed
929ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                    float currentScale = getScale();
930ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                    if (currentScale > mMaxScale) {
931ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // The number of times the crop amount pulled in can fit on the screen
932ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float marginFit = 1 / (1 - mMaxScale / currentScale);
933ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // The (negative) relative maximum distance from an image edge such that
934ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // when scaled this far from the edge, all of the image off-screen in that
935ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // direction is pulled in
936ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float relativeDistance = 1 - marginFit;
937ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float finalCenterX = getWidth() / 2;
938ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float finalCenterY = getHeight() / 2;
939ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // This center will pull all of the margin from the lesser side, over will
940ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // expose trim
941ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float maxX = mTranslateRect.left * relativeDistance;
942ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float maxY = mTranslateRect.top * relativeDistance;
943ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // This center will pull all of the margin from the greater side, over will
944ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // expose trim
945ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float minX = getWidth() * marginFit + mTranslateRect.right *
946ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                                relativeDistance;
947ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        float minY = getHeight() * marginFit + mTranslateRect.bottom *
948ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                                relativeDistance;
949ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        // Adjust center according to bounds to avoid bad crop
950ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        if (minX > maxX) {
951ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            // Border is inevitable due to small image size, so we split the crop
952ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            finalCenterX = (minX + maxX) / 2;
953ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        } else {
954ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            finalCenterX = Math.min(Math.max(minX, finalCenterX), maxX);
955ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        }
956ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        if (minY > maxY) {
957ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            // Border is inevitable due to small image size, so we split the crop
958ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            finalCenterY = (minY + maxY) / 2;
959ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        } else {
960ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                            finalCenterY = Math.min(Math.max(minY, finalCenterY), maxY);
961ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        }
962ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                        mScaleRunnable.start(currentScale, mMaxScale, finalCenterX, finalCenterY);
963ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                    }
964ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha                }
965ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha            };
966ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha            postDelayed(zoomBackRunnable, ZOOM_CORRECTION_DELAY);
967ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        }
968ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha
969f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float factor = newScale / currentScale;
970f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
971ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        // Apply the scale factor
972f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.postScale(factor, factor, centerX, centerY);
973f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
974ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        // Re-apply any rotation
975f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.postRotate(mRotation, getWidth() / 2, getHeight() / 2);
976f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
977f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        invalidate();
978f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
979f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
980f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
981f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Translates the image.
982f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
983f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * This method will not allow the image to be translated outside of the visible area.
984f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
985f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param tx how many pixels to translate horizontally
986f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param ty how many pixels to translate vertically
987b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha     * @return result of the translation, represented as either {@link TRANSLATE_NONE},
988b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha     * {@link TRANSLATE_X_ONLY}, {@link TRANSLATE_Y_ONLY}, or {@link TRANSLATE_BOTH}
989f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
990b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha    private int translate(float tx, float ty) {
991f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTranslateRect.set(mTempSrc);
992f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.mapRect(mTranslateRect);
993f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
994f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float maxLeft = mAllowCrop ? mCropRect.left : 0.0f;
995f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float maxRight = mAllowCrop ? mCropRect.right : getWidth();
996f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float l = mTranslateRect.left;
997f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float r = mTranslateRect.right;
998f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
999f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float translateX;
1000f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mAllowCrop) {
1001f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // If we're cropping, allow the image to scroll off the edge of the screen
1002f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateX = Math.max(maxLeft - mTranslateRect.right,
1003f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    Math.min(maxRight - mTranslateRect.left, tx));
1004f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1005f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Otherwise, ensure the image never leaves the screen
1006f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (r - l < maxRight - maxLeft) {
1007f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2;
1008f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
1009f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                translateX = Math.max(maxRight - r, Math.min(maxLeft - l, tx));
1010f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1011f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1012f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1013f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxTop = mAllowCrop ? mCropRect.top: 0.0f;
1014f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight();
1015f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float t = mTranslateRect.top;
1016f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float b = mTranslateRect.bottom;
1017f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1018f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float translateY;
1019f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (mAllowCrop) {
1020f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // If we're cropping, allow the image to scroll off the edge of the screen
1021f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateY = Math.max(maxTop - mTranslateRect.bottom,
1022f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    Math.min(maxBottom - mTranslateRect.top, ty));
1023f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1024f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Otherwise, ensure the image never leaves the screen
1025f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (b - t < maxBottom - maxTop) {
1026f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2;
1027f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
1028f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                translateY = Math.max(maxBottom - b, Math.min(maxTop - t, ty));
1029f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1030f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1031f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1032f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // Do the translation
1033f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.postTranslate(translateX, translateY);
1034f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        invalidate();
1035f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1036b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        boolean didTranslateX = translateX == tx;
1037b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        boolean didTranslateY = translateY == ty;
1038b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        if (didTranslateX && didTranslateY) {
1039b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            return TRANSLATE_BOTH;
1040b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        } else if (didTranslateX) {
1041b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            return TRANSLATE_X_ONLY;
1042b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        } else if (didTranslateY) {
1043b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            return TRANSLATE_Y_ONLY;
1044b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        }
1045b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        return TRANSLATE_NONE;
1046f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1047f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1048f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1049f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Snaps the image so it touches all edges of the view.
1050f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1051f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void snap() {
1052f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTranslateRect.set(mTempSrc);
1053f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mMatrix.mapRect(mTranslateRect);
1054f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1055f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // Determine how much to snap in the horizontal direction [if any]
1056f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxLeft = mAllowCrop ? mCropRect.left : 0.0f;
1057f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxRight = mAllowCrop ? mCropRect.right : getWidth();
1058f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float l = mTranslateRect.left;
1059f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float r = mTranslateRect.right;
1060f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1061f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float translateX;
1062f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (r - l < maxRight - maxLeft) {
1063f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is narrower than view; translate to the center of the view
1064f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2;
1065f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (l > maxLeft) {
1066f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is off right-edge of screen; bring it into view
1067f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateX = maxLeft - l;
1068f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (r < maxRight) {
1069f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is off left-edge of screen; bring it into view
1070f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateX = maxRight - r;
1071f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1072f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateX = 0.0f;
1073f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1074f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1075f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        // Determine how much to snap in the vertical direction [if any]
1076f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxTop = mAllowCrop ? mCropRect.top : 0.0f;
1077f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight();
1078f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float t = mTranslateRect.top;
1079f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        float b = mTranslateRect.bottom;
1080f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1081f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        final float translateY;
1082f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (b - t < maxBottom - maxTop) {
1083f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is shorter than view; translate to the bottom edge of the view
1084f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2;
1085f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (t > maxTop) {
1086f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is off bottom-edge of screen; bring it into view
1087f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateY = maxTop - t;
1088f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else if (b < maxBottom) {
1089f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Image is off top-edge of screen; bring it into view
1090f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateY = maxBottom - b;
1091f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1092f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            translateY = 0.0f;
1093f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1094f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1095f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (Math.abs(translateX) > SNAP_THRESHOLD || Math.abs(translateY) > SNAP_THRESHOLD) {
1096f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mSnapRunnable.start(translateX, translateY);
1097f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1098f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.postTranslate(translateX, translateY);
1099f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            invalidate();
1100f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1101f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1102f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1103f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1104f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Rotates the image, either instantly or gradually
1105f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     *
1106f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param degrees how many degrees to rotate the image, positive rotates clockwise
1107f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * @param animate if {@code true}, animate during the rotation. Otherwise, snap rotate.
1108f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1109f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void rotate(float degrees, boolean animate) {
1110f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (animate) {
1111f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRotateRunnable.start(degrees);
1112f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        } else {
1113f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRotation += degrees;
1114f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mMatrix.postRotate(degrees, getWidth() / 2, getHeight() / 2);
1115f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            invalidate();
1116f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1117f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1118f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1119f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1120f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Initializes the header and any static values
1121f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1122f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private void initialize() {
1123f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        Context context = getContext();
1124f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1125f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        if (!sInitialized) {
1126f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sInitialized = true;
1127f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1128f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            Resources resources = context.getApplicationContext().getResources();
1129f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1130f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropSize = resources.getDimensionPixelSize(R.dimen.photo_crop_width);
1131f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1132f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropDimPaint = new Paint();
1133f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropDimPaint.setAntiAlias(true);
1134f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropDimPaint.setColor(resources.getColor(R.color.photo_crop_dim_color));
1135f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropDimPaint.setStyle(Style.FILL);
1136f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1137f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropPaint = new Paint();
1138f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropPaint.setAntiAlias(true);
1139f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropPaint.setColor(resources.getColor(R.color.photo_crop_highlight_color));
1140f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropPaint.setStyle(Style.STROKE);
1141f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            sCropPaint.setStrokeWidth(resources.getDimension(R.dimen.photo_crop_stroke_width));
1142f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira
1143f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            final ViewConfiguration configuration = ViewConfiguration.get(context);
1144f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            final int touchSlop = configuration.getScaledTouchSlop();
1145f0fac25e570ef98dd9d5df34cf8437888cd118cdMindy Pereira            sTouchSlopSquare = touchSlop * touchSlop;
1146f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1147f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1148bdf13fd95aa92f794be827421945370c1456ea7cPaul Westbrook        mGestureDetector = new GestureDetectorCompat(context, this, null);
1149f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleGetureDetector = new ScaleGestureDetector(context, this);
115069d49ba36ecf8b6288c196f2fe5e3795dfe1486eMindy DelliCarpini        mQuickScaleEnabled = ScaleGestureDetectorCompat.isQuickScaleEnabled(mScaleGetureDetector);
1151f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mScaleRunnable = new ScaleRunnable(this);
1152f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mTranslateRunnable = new TranslateRunnable(this);
1153f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mSnapRunnable = new SnapRunnable(this);
1154f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        mRotateRunnable = new RotateRunnable(this);
1155f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1156f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1157f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1158f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Runnable that animates an image scale operation.
1159f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1160f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static class ScaleRunnable implements Runnable {
1161f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1162f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private final PhotoView mHeader;
1163f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1164f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mCenterX;
1165f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mCenterY;
1166f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1167f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mZoomingIn;
1168f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1169f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mTargetScale;
1170f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mStartScale;
1171f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mVelocity;
1172f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private long mStartTime;
1173f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1174f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mRunning;
1175f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mStop;
1176f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1177f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public ScaleRunnable(PhotoView header) {
1178f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader = header;
1179f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1180f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1181f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1182f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Starts the animation. There is no target scale bounds check.
1183f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1184f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public boolean start(float startScale, float targetScale, float centerX, float centerY) {
1185f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mRunning) {
1186f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
1187f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1188f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1189f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mCenterX = centerX;
1190f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mCenterY = centerY;
1191f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1192f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Ensure the target scale is within the min/max bounds
1193f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTargetScale = targetScale;
1194f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStartTime = System.currentTimeMillis();
1195f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStartScale = startScale;
1196f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mZoomingIn = mTargetScale > mStartScale;
1197f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mVelocity = (mTargetScale - mStartScale) / ZOOM_ANIMATION_DURATION;
1198f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = true;
1199f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = false;
1200f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1201f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
1202f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1203f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1204f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1205f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final zoom.
1206f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1207f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void stop() {
1208f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = false;
1209f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = true;
1210f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1211f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1212f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        @Override
1213f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void run() {
1214f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1215f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1216f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1217f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1218f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Scale
1219f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            long now = System.currentTimeMillis();
1220f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            long ellapsed = now - mStartTime;
1221f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float newScale = (mStartScale + mVelocity * ellapsed);
1222f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.scale(newScale, mCenterX, mCenterY);
1223f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1224f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Stop when done
1225f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (newScale == mTargetScale || (mZoomingIn == (newScale > mTargetScale))) {
1226f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mHeader.scale(mTargetScale, mCenterX, mCenterY);
1227f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                stop();
1228f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1229f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1230f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (!mStop) {
1231f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mHeader.post(this);
1232f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1233f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1234f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1235f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1236f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1237f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Runnable that animates an image translation operation.
1238f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1239f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static class TranslateRunnable implements Runnable {
1240f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1241ac61de54b49ff9d6345060f57f1063a617c0ebfdKhoa Ha        private static final float DECELERATION_RATE = 20000f;
1242f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private static final long NEVER = -1L;
1243f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1244f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private final PhotoView mHeader;
1245f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1246f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mVelocityX;
1247f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mVelocityY;
1248f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1249b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        private float mDecelerationX;
1250b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha        private float mDecelerationY;
1251b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha
1252f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private long mLastRunTime;
1253f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mRunning;
1254f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mStop;
1255f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1256f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public TranslateRunnable(PhotoView header) {
1257f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mLastRunTime = NEVER;
1258f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader = header;
1259f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1260f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1261f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1262f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Starts the animation.
1263f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1264f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public boolean start(float velocityX, float velocityY) {
1265f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mRunning) {
1266f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
1267f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1268f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mLastRunTime = NEVER;
1269f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mVelocityX = velocityX;
1270f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mVelocityY = velocityY;
1271b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha
1272b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            float angle = (float) Math.atan2(mVelocityY, mVelocityX);
1273b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            mDecelerationX = (float) (DECELERATION_RATE * Math.cos(angle));
1274b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            mDecelerationY = (float) (DECELERATION_RATE * Math.sin(angle));
1275b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha
1276f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = false;
1277f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = true;
1278f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1279f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
1280f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1281f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1282f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1283f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final translation.
1284f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1285f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void stop() {
1286f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = false;
1287f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = true;
1288f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1289f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1290f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        @Override
1291f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void run() {
1292f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // See if we were told to stop:
1293f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1294f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1295f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1296f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1297f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Translate according to current velocities and time delta:
1298f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            long now = System.currentTimeMillis();
1299f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float delta = (mLastRunTime != NEVER) ? (now - mLastRunTime) / 1000f : 0f;
1300b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            final int translateResult = mHeader.translate(mVelocityX * delta, mVelocityY * delta);
1301f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mLastRunTime = now;
1302f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Slow down:
1303b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            float slowDownX = mDecelerationX * delta;
1304b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            if (Math.abs(mVelocityX) > Math.abs(slowDownX)) {
1305b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityX -= slowDownX;
1306f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
1307b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityX = 0f;
1308f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1309b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            float slowDownY = mDecelerationY * delta;
1310b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            if (Math.abs(mVelocityY) > Math.abs(slowDownY)) {
1311b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityY -= slowDownY;
1312f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
1313b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityY = 0f;
1314f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1315f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1316f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Stop when done
1317b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            if ((mVelocityX == 0f && mVelocityY == 0f)
1318b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                    || translateResult == TRANSLATE_NONE) {
1319f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                stop();
1320f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mHeader.snap();
1321b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            } else if (translateResult == TRANSLATE_X_ONLY) {
1322b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mDecelerationX = (mVelocityX > 0) ? DECELERATION_RATE : -DECELERATION_RATE;
1323b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mDecelerationY = 0;
1324b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityY = 0f;
1325b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha            } else if (translateResult == TRANSLATE_Y_ONLY) {
1326b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mDecelerationX = 0;
1327b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mDecelerationY = (mVelocityY > 0) ? DECELERATION_RATE : -DECELERATION_RATE;
1328b2bd703691056776a4d13af0bd3410d9046c3b1fkhoaha                mVelocityX = 0f;
1329f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1330f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1331f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // See if we need to continue flinging:
1332f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1333f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1334f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1335f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1336f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1337f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1338f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1339f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1340f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Runnable that animates an image translation operation.
1341f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1342f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static class SnapRunnable implements Runnable {
1343f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1344f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private static final long NEVER = -1L;
1345f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1346f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private final PhotoView mHeader;
1347f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1348f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mTranslateX;
1349f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mTranslateY;
1350f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1351f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private long mStartRunTime;
1352f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mRunning;
1353f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mStop;
1354f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1355f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public SnapRunnable(PhotoView header) {
1356f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStartRunTime = NEVER;
1357f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader = header;
1358f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1359f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1360f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1361f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Starts the animation.
1362f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1363f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public boolean start(float translateX, float translateY) {
1364f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mRunning) {
1365f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return false;
1366f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1367f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStartRunTime = NEVER;
1368f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateX = translateX;
1369f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateY = translateY;
1370f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = false;
1371f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = true;
1372f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.postDelayed(this, SNAP_DELAY);
1373f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            return true;
1374f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1375f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1376f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1377f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final translation.
1378f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1379f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void stop() {
1380f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = false;
1381f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = true;
1382f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1383f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1384f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        @Override
1385f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void run() {
1386f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // See if we were told to stop:
1387f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1388f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1389f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1390f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1391f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // Translate according to current velocities and time delta:
1392f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            long now = System.currentTimeMillis();
1393f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float delta = (mStartRunTime != NEVER) ? (now - mStartRunTime) : 0f;
1394f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1395f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStartRunTime == NEVER) {
1396f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mStartRunTime = now;
1397f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1398f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1399f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float transX;
1400f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            float transY;
1401f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (delta >= SNAP_DURATION) {
1402f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                transX = mTranslateX;
1403f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                transY = mTranslateY;
1404f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            } else {
1405f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                transX = (mTranslateX / (SNAP_DURATION - delta)) * 10f;
1406f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                transY = (mTranslateY / (SNAP_DURATION - delta)) * 10f;
14078749b62b1a7c7c8ac1d5db1dbe506e45961604b6Andrew Sapperstein                if (Math.abs(transX) > Math.abs(mTranslateX) || Float.isNaN(transX)) {
1408f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    transX = mTranslateX;
1409f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
14108749b62b1a7c7c8ac1d5db1dbe506e45961604b6Andrew Sapperstein                if (Math.abs(transY) > Math.abs(mTranslateY) || Float.isNaN(transY)) {
1411f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    transY = mTranslateY;
1412f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
1413f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1414f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1415f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.translate(transX, transY);
1416f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateX -= transX;
1417f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTranslateY -= transY;
1418f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1419f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mTranslateX == 0 && mTranslateY == 0) {
1420f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                stop();
1421f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1422f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1423f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            // See if we need to continue flinging:
1424f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1425f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1426f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1427f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1428f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1429f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
1430f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1431f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    /**
1432f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     * Runnable that animates an image rotation operation.
1433f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein     */
1434f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    private static class RotateRunnable implements Runnable {
1435f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1436f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private static final long NEVER = -1L;
1437f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1438f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private final PhotoView mHeader;
1439f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1440f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mTargetRotation;
1441f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mAppliedRotation;
1442f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private float mVelocity;
1443f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private long mLastRuntime;
1444f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1445f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mRunning;
1446f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        private boolean mStop;
1447f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1448f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public RotateRunnable(PhotoView header) {
1449f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader = header;
1450f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1451f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1452f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1453f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Starts the animation.
1454f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1455f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void start(float rotation) {
1456f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mRunning) {
1457f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1458f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1459f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1460f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mTargetRotation = rotation;
1461f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mVelocity = mTargetRotation / ROTATE_ANIMATION_DURATION;
1462f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mAppliedRotation = 0f;
1463f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mLastRuntime = NEVER;
1464f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = false;
1465f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = true;
1466f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1467f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1468f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1469f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        /**
1470f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final rotation.
1471f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein         */
1472f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void stop() {
1473f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mRunning = false;
1474f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mStop = true;
1475f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1476f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1477f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        @Override
1478f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        public void run() {
1479f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1480f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1481f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1482f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1483f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mAppliedRotation != mTargetRotation) {
1484f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                long now = System.currentTimeMillis();
1485f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                long delta = mLastRuntime != NEVER ? now - mLastRuntime : 0L;
1486f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                float rotationAmount = mVelocity * delta;
1487f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (mAppliedRotation < mTargetRotation
1488f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        && mAppliedRotation + rotationAmount > mTargetRotation
1489f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        || mAppliedRotation > mTargetRotation
1490f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                        && mAppliedRotation + rotationAmount < mTargetRotation) {
1491f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    rotationAmount = mTargetRotation - mAppliedRotation;
1492f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
1493f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mHeader.rotate(rotationAmount, false);
1494f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mAppliedRotation += rotationAmount;
1495f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                if (mAppliedRotation == mTargetRotation) {
1496f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                    stop();
1497f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                }
1498f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                mLastRuntime = now;
1499f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1500f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein
1501f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            if (mStop) {
1502f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein                return;
1503f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            }
1504f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein            mHeader.post(this);
1505f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein        }
1506f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein    }
150791dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp
150891dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp    public void setMaxInitialScale(float f) {
150991dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp        mMaxInitialScaleFactor = f;
151091dbfd25cc234de393ae22fc39a832a6335e1bc2Adam Copp    }
1511f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein}
1512