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