11cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein/*
21cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Copyright (C) 2011 Google Inc.
31cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Licensed to The Android Open Source Project.
41cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
51cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
61cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * you may not use this file except in compliance with the License.
71cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * You may obtain a copy of the License at
81cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
91cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Unless required by applicable law or agreed to in writing, software
121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * See the License for the specific language governing permissions and
151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * limitations under the License.
161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein */
171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinpackage com.android.ex.photo.views;
191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.content.Context;
211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.content.pm.PackageManager;
221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.content.res.Resources;
231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Bitmap;
241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Canvas;
251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Matrix;
261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Paint;
271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Paint.Style;
281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.Rect;
291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.RectF;
301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.graphics.drawable.BitmapDrawable;
314054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrookimport android.support.v4.view.GestureDetectorCompat;
321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.util.AttributeSet;
334054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrookimport android.view.GestureDetector.OnGestureListener;
344054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrookimport android.view.GestureDetector.OnDoubleTapListener;
351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.view.MotionEvent;
361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.view.ScaleGestureDetector;
371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.view.View;
381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport com.android.ex.photo.R;
401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport com.android.ex.photo.fragments.PhotoViewFragment.HorizontallyScrollable;
411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein/**
431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Layout for the photo list view header.
441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein */
454054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrookpublic class PhotoView extends View implements OnGestureListener,
464054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrook        OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener,
471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        HorizontallyScrollable {
481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Zoom animation duration; in milliseconds */
491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final static long ZOOM_ANIMATION_DURATION = 300L;
501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Rotate animation duration; in milliseconds */
511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final static long ROTATE_ANIMATION_DURATION = 500L;
521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Snap animation duration; in milliseconds */
531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static final long SNAP_DURATION = 100L;
541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Amount of time to wait before starting snap animation; in milliseconds */
551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static final long SNAP_DELAY = 250L;
561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** By how much to scale the image when double click occurs */
571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final static float DOUBLE_TAP_SCALE_FACTOR = 1.5f;
581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Amount of translation needed before starting a snap animation */
591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final static float SNAP_THRESHOLD = 20.0f;
601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The width & height of the bitmap returned by {@link #getCroppedPhoto()} */
611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final static float CROPPED_SIZE = 256.0f;
621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** If {@code true}, the static values have been initialized */
641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static boolean sInitialized;
651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // Various dimensions
671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Width & height of the crop region */
681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static int sCropSize;
691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // Bitmaps
711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Video icon */
721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static Bitmap sVideoImage;
731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Video icon */
741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static Bitmap sVideoNotReadyImage;
751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // Features
771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static boolean sHasMultitouchDistinct;
781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // Paints
801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Paint to partially dim the photo during crop */
811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static Paint sCropDimPaint;
821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Paint to highlight the cropped portion of the photo */
831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static Paint sCropPaint;
841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The photo to display */
861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private BitmapDrawable mDrawable;
871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The matrix used for drawing; this may be {@code null} */
881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private Matrix mDrawMatrix;
891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** A matrix to apply the scaling of the photo */
901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private Matrix mMatrix = new Matrix();
911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The original matrix for this image; used to reset any transformations applied by the user */
921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private Matrix mOriginalMatrix = new Matrix();
931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The fixed height of this view. If {@code -1}, calculate the height */
951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private int mFixedHeight = -1;
961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** When {@code true}, the view has been laid out */
971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mHaveLayout;
981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Whether or not the photo is full-screen */
991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mFullScreen;
1001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Whether or not this is a still image of a video */
1011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private byte[] mVideoBlob;
1021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Whether or not this is a still image of a video */
1031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mVideoReady;
1041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Whether or not crop is allowed */
1061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mAllowCrop;
1071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The crop region */
1081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private Rect mCropRect = new Rect();
1091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Actual crop size; may differ from {@link #sCropSize} if the screen is smaller */
1101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private int mCropSize;
1111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Gesture detector */
1134054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrook    private GestureDetectorCompat mGestureDetector;
1141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Gesture detector that detects pinch gestures */
1151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private ScaleGestureDetector mScaleGetureDetector;
1161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** An external click listener */
1171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private OnClickListener mExternalClickListener;
1181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** When {@code true}, allows gestures to scale / pan the image */
1191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mTransformsEnabled;
1201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // To support zooming
1221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** When {@code true}, a double tap scales the image by {@link #DOUBLE_TAP_SCALE_FACTOR} */
1231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mDoubleTapToZoomEnabled = true;
1241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** When {@code true}, prevents scale end gesture from falsely triggering a double click. */
1251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mDoubleTapDebounce;
1261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** When {@code false}, event is a scale gesture. Otherwise, event is a double touch. */
1271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean mIsDoubleTouch;
1281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Runnable that scales the image */
1291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private ScaleRunnable mScaleRunnable;
1301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Minimum scale the image can have. */
1311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private float mMinScale;
1321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Maximum scale to limit scaling to, 0 means no limit. */
1331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private float mMaxScale;
1341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // To support translation [i.e. panning]
1361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Runnable that can move the image */
1371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private TranslateRunnable mTranslateRunnable;
1381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private SnapRunnable mSnapRunnable;
1391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // To support rotation
1411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The rotate runnable used to animate rotations of the image */
1421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private RotateRunnable mRotateRunnable;
1431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The current rotation amount, in degrees */
1441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private float mRotation;
1451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // Convenience fields
1471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // These are declared here not because they are important properties of the view. Rather, we
1481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // declare them here to avoid object allocation during critical graphics operations; such as
1491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    // layout or drawing.
1501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Source (i.e. the photo size) bounds */
1511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private RectF mTempSrc = new RectF();
1521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Destination (i.e. the display) bounds. The image is scaled to this size. */
1531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private RectF mTempDst = new RectF();
1541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Rectangle to handle translations */
1551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private RectF mTranslateRect = new RectF();
1561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Array to store a copy of the matrix values */
1571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private float[] mValues = new float[9];
1581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public PhotoView(Context context) {
1601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        super(context);
1611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        initialize();
1621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public PhotoView(Context context, AttributeSet attrs) {
1651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        super(context, attrs);
1661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        initialize();
1671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public PhotoView(Context context, AttributeSet attrs, int defStyle) {
1701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        super(context, attrs, defStyle);
1711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        initialize();
1721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onTouchEvent(MotionEvent event) {
1761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mScaleGetureDetector == null || mGestureDetector == null) {
1771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // We're being destroyed; ignore any touch events
1781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
1791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleGetureDetector.onTouchEvent(event);
1821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mGestureDetector.onTouchEvent(event);
1831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int action = event.getAction();
1841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        switch (action) {
1861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            case MotionEvent.ACTION_UP:
1871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            case MotionEvent.ACTION_CANCEL:
1881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (!mTranslateRunnable.mRunning) {
1891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    snap();
1901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
1911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                break;
1921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
1951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onDoubleTap(MotionEvent e) {
1991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDoubleTapToZoomEnabled && mTransformsEnabled) {
2001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (!mDoubleTapDebounce) {
2011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                float currentScale = getScale();
2021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                float targetScale = currentScale * DOUBLE_TAP_SCALE_FACTOR;
2031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // Ensure the target scale is within our bounds
2051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                targetScale = Math.max(mMinScale, targetScale);
2061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                targetScale = Math.min(mMaxScale, targetScale);
2071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mScaleRunnable.start(currentScale, targetScale, e.getX(), e.getY());
2091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
2101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDoubleTapDebounce = false;
2111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onDoubleTapEvent(MotionEvent e) {
2171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onSingleTapConfirmed(MotionEvent e) {
2221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mExternalClickListener != null && !mIsDoubleTouch) {
2231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mExternalClickListener.onClick(this);
2241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mIsDoubleTouch = false;
2261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onSingleTapUp(MotionEvent e) {
2311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return false;
2321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void onLongPress(MotionEvent e) {
2361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void onShowPress(MotionEvent e) {
2401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled) {
245bd64181d797dabd8b346b97ebb3e4083e53b864dPaul Westbrook            translate(-distanceX, -distanceY);
2461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onDown(MotionEvent e) {
2521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled) {
2531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateRunnable.stop();
2541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mSnapRunnable.stop();
2551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled) {
2624054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrook            mTranslateRunnable.start(velocityX, velocityY);
2631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onScale(ScaleGestureDetector detector) {
2691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled) {
2701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mIsDoubleTouch = false;
2711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float currentScale = getScale();
2721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float newScale = currentScale * detector.getScaleFactor();
2731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            scale(newScale, detector.getFocusX(), detector.getFocusY());
2741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean onScaleBegin(ScaleGestureDetector detector) {
2801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled) {
2811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mScaleRunnable.stop();
2821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mIsDoubleTouch = true;
2831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return true;
2851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void onScaleEnd(ScaleGestureDetector detector) {
2891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mTransformsEnabled && mIsDoubleTouch) {
2901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDoubleTapDebounce = true;
2911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            resetTransformations();
2921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
2931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
2951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
2961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void setOnClickListener(OnClickListener listener) {
2971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mExternalClickListener = listener;
2981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
2991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
3011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean interceptMoveLeft(float origX, float origY) {
3021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!mTransformsEnabled) {
3031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Allow intercept if we're not in transform mode
3041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return false;
3051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (mTranslateRunnable.mRunning) {
3061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Don't allow touch intercept until we've stopped flinging
3071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
3081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
3091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.getValues(mValues);
3101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateRect.set(mTempSrc);
3111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.mapRect(mTranslateRect);
3121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float viewWidth = getWidth();
3141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float transX = mValues[Matrix.MTRANS_X];
3151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float drawWidth = mTranslateRect.right - mTranslateRect.left;
3161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (!mTransformsEnabled || drawWidth <= viewWidth) {
3181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // Allow intercept if not in transform mode or the image is smaller than the view
3191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
3201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else if (transX == 0) {
3211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're at the left-side of the image; allow intercepting movements to the right
3221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
3231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else if (viewWidth >= drawWidth + transX) {
3241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're at the right-side of the image; allow intercepting movements to the left
3251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return true;
3261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
3271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're in the middle of the image; don't allow touch intercept
3281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return true;
3291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
3301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
3311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
3321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
3341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean interceptMoveRight(float origX, float origY) {
3351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!mTransformsEnabled) {
3361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Allow intercept if we're not in transform mode
3371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return false;
3381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (mTranslateRunnable.mRunning) {
3391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Don't allow touch intercept until we've stopped flinging
3401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
3411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
3421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.getValues(mValues);
3431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateRect.set(mTempSrc);
3441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.mapRect(mTranslateRect);
3451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float viewWidth = getWidth();
3471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float transX = mValues[Matrix.MTRANS_X];
3481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final float drawWidth = mTranslateRect.right - mTranslateRect.left;
3491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (!mTransformsEnabled || drawWidth <= viewWidth) {
3511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // Allow intercept if not in transform mode or the image is smaller than the view
3521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
3531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else if (transX == 0) {
3541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're at the left-side of the image; allow intercepting movements to the right
3551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return true;
3561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else if (viewWidth >= drawWidth + transX) {
3571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're at the right-side of the image; allow intercepting movements to the left
3581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
3591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
3601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // We're in the middle of the image; don't allow touch intercept
3611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return true;
3621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
3631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
3641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
3651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
3671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Free all resources held by this view.
3681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * The view is on its way to be collected and will not be reused.
3691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
3701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void clear() {
3711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mGestureDetector = null;
3721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleGetureDetector = null;
3731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mDrawable = null;
3741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleRunnable.stop();
3751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleRunnable = null;
3761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTranslateRunnable.stop();
3771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTranslateRunnable = null;
3781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mSnapRunnable.stop();
3791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mSnapRunnable = null;
3801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mRotateRunnable.stop();
3811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mRotateRunnable = null;
3821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        setOnClickListener(null);
3831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mExternalClickListener = null;
3841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
3851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
3861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
3871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Binds a bitmap to the view.
3881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
3891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param photoBitmap the bitmap to bind.
3901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
3911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void bindPhoto(Bitmap photoBitmap) {
3921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        boolean changed = false;
3931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable != null) {
3941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final Bitmap drawableBitmap = mDrawable.getBitmap();
3951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (photoBitmap == drawableBitmap) {
3961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                // setting the same bitmap; do nothing
3971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
3981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
3991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            changed = photoBitmap != null &&
4011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    (mDrawable.getIntrinsicWidth() != photoBitmap.getWidth() ||
4021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    mDrawable.getIntrinsicHeight() != photoBitmap.getHeight());
4031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Reset mMinScale to ensure the bounds / matrix are recalculated
4051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMinScale = 0f;
4061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawable = null;
4071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable == null && photoBitmap != null) {
4101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawable = new BitmapDrawable(getResources(), photoBitmap);
4111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        configureBounds(changed);
4141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        invalidate();
4151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Returns the bound photo data if set. Otherwise, {@code null}.
4191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public Bitmap getPhoto() {
4211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable != null) {
4221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return mDrawable.getBitmap();
4231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return null;
4251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Gets video data associated with this item. Returns {@code null} if this is not a video.
4291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public byte[] getVideoData() {
4311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mVideoBlob;
4321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Returns {@code true} if the photo represents a video. Otherwise, {@code false}.
4361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean isVideo() {
4381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mVideoBlob != null;
4391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Returns {@code true} if the video is ready to play. Otherwise, {@code false}.
4431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean isVideoReady() {
4451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mVideoBlob != null && mVideoReady;
4461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Returns {@code true} if a photo has been bound. Otherwise, {@code false}.
4501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean isPhotoBound() {
4521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mDrawable != null;
4531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Hides the photo info portion of the header. As a side effect, this automatically enables
4571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * or disables image transformations [eg zoom, pan, etc...] depending upon the value of
4581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * fullScreen. If this is not desirable, enable / disable image transformations manually.
4591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void setFullScreen(boolean fullScreen, boolean animate) {
4611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fullScreen != mFullScreen) {
4621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mFullScreen = fullScreen;
4631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            requestLayout();
4641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            invalidate();
4651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Enable or disable cropping of the displayed image. Cropping can only be enabled
4701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * <em>before</em> the view has been laid out. Additionally, once cropping has been
4711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * enabled, it cannot be disabled.
4721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void enableAllowCrop(boolean allowCrop) {
4741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (allowCrop && mHaveLayout) {
4751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            throw new IllegalArgumentException("Cannot set crop after view has been laid out");
4761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!allowCrop && mAllowCrop) {
4781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            throw new IllegalArgumentException("Cannot unset crop mode");
4791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mAllowCrop = allowCrop;
4811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
4821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
4841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Gets a bitmap of the cropped region. If cropping is not enabled, returns {@code null}.
4851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
4861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public Bitmap getCroppedPhoto() {
4871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!mAllowCrop) {
4881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return null;
4891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
4901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final Bitmap croppedBitmap = Bitmap.createBitmap(
4921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                (int) CROPPED_SIZE, (int) CROPPED_SIZE, Bitmap.Config.ARGB_8888);
4931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final Canvas croppedCanvas = new Canvas(croppedBitmap);
4941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
4951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // scale for the final dimensions
4961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int cropWidth = mCropRect.right - mCropRect.left;
4971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float scaleWidth = CROPPED_SIZE / cropWidth;
4981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float scaleHeight = CROPPED_SIZE / cropWidth;
4991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // translate to the origin & scale
5011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final Matrix matrix = new Matrix(mDrawMatrix);
5021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        matrix.postTranslate(-mCropRect.left, -mCropRect.top);
5031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        matrix.postScale(scaleWidth, scaleHeight);
5041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // draw the photo
5061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable != null) {
5071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            croppedCanvas.concat(matrix);
5081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawable.draw(croppedCanvas);
5091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
5101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return croppedBitmap;
5111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
5121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
5141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Resets the image transformation to its original value.
5151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
5161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void resetTransformations() {
5171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // snap transformations; we don't animate
5181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.set(mOriginalMatrix);
5191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
520d19540fb7139fc0baba3b55260a6aabdb029c831Andrew Sapperstein        // Invalidate the view because if you move off this PhotoView
5211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // to another one and come back, you want it to draw from scratch
5221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // in case you were zoomed in or translated (since those settings
5231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // are not preserved and probably shouldn't be).
5241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        invalidate();
5251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
5261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
5281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Rotates the image 90 degrees, clockwise.
5291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
5301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void rotateClockwise() {
5311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        rotate(90, true);
5321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
5331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
5351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Rotates the image 90 degrees, counter clockwise.
5361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
5371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void rotateCounterClockwise() {
5381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        rotate(-90, true);
5391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
5401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
5421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    protected void onDraw(Canvas canvas) {
5431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        super.onDraw(canvas);
5441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // draw the photo
5461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable != null) {
5471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            int saveCount = canvas.getSaveCount();
5481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            canvas.save();
5491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mDrawMatrix != null) {
5511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.concat(mDrawMatrix);
5521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
5531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawable.draw(canvas);
5541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            canvas.restoreToCount(saveCount);
5561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mVideoBlob != null) {
5581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                final Bitmap videoImage = (mVideoReady ? sVideoImage : sVideoNotReadyImage);
5591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                final int drawLeft = (getWidth() - videoImage.getWidth()) / 2;
5601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                final int drawTop = (getHeight() - videoImage.getHeight()) / 2;
5611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.drawBitmap(videoImage, drawLeft, drawTop, null);
5621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
5631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Extract the drawable's bounds (in our own copy, to not alter the image)
5651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateRect.set(mDrawable.getBounds());
5661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mDrawMatrix != null) {
5671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mDrawMatrix.mapRect(mTranslateRect);
5681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
5691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mAllowCrop) {
5711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                int previousSaveCount = canvas.getSaveCount();
5721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.drawRect(0, 0, getWidth(), getHeight(), sCropDimPaint);
5731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.save();
5741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.clipRect(mCropRect);
5751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mDrawMatrix != null) {
5771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    canvas.concat(mDrawMatrix);
5781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
5791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mDrawable.draw(canvas);
5811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.restoreToCount(previousSaveCount);
5821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                canvas.drawRect(mCropRect, sCropPaint);
5831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
5841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
5851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
5861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
5881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
5891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        super.onLayout(changed, left, top, right, bottom);
5901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mHaveLayout = true;
5911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int layoutWidth = getWidth();
5921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int layoutHeight = getHeight();
5931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
5941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mAllowCrop) {
5951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCropSize = Math.min(sCropSize, Math.min(layoutWidth, layoutHeight));
5961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final int cropLeft = (layoutWidth - mCropSize) / 2;
5971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final int cropTop = (layoutHeight - mCropSize) / 2;
5981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final int cropRight = cropLeft + mCropSize;
5991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final int cropBottom =  cropTop + mCropSize;
6001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Create a crop region overlay. We need a separate canvas to be able to "punch
6021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // a hole" through to the underlying image.
6031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCropRect.set(cropLeft, cropTop, cropRight, cropBottom);
6041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        configureBounds(changed);
6061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
6071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
6091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mFixedHeight != -1) {
6111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mFixedHeight,
6121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    MeasureSpec.AT_MOST));
6131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            setMeasuredDimension(getMeasuredWidth(), mFixedHeight);
6141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
6151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
6161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
6181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
6201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Forces a fixed height for this view.
6211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
6221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param fixedHeight The height. If {@code -1}, use the measured height.
6231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
6241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void setFixedHeight(int fixedHeight) {
6251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final boolean adjustBounds = (fixedHeight != mFixedHeight);
6261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mFixedHeight = fixedHeight;
6271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        setMeasuredDimension(getMeasuredWidth(), mFixedHeight);
6281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (adjustBounds) {
6291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            configureBounds(true);
6301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            requestLayout();
6311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
6331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
6351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Enable or disable image transformations. When transformations are enabled, this view
6361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * consumes all touch events.
6371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
6381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void enableImageTransforms(boolean enable) {
6391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTransformsEnabled = enable;
6401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!mTransformsEnabled) {
6411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            resetTransformations();
6421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
6441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
6461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Configures the bounds of the photo. The photo will always be scaled to fit center.
6471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
6481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void configureBounds(boolean changed) {
6491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mDrawable == null || !mHaveLayout) {
6501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return;
6511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
6531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
6541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vwidth = getWidth();
6561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vheight = getHeight();
6571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final boolean fits = (dwidth < 0 || vwidth == dwidth) &&
6591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                (dheight < 0 || vheight == dheight);
6601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // We need to do the scaling ourself, so have the drawable use its native size.
6621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mDrawable.setBounds(0, 0, dwidth, dheight);
6631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Create a matrix with the proper transforms
6651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (changed || (mMinScale == 0 && mDrawable != null && mHaveLayout)) {
6661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            generateMatrix();
6671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            generateScale();
6681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fits || mMatrix.isIdentity()) {
6711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // The bitmap fits exactly, no transform needed.
6721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawMatrix = null;
6731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
6741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mDrawMatrix = mMatrix;
6751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
6761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
6771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
6791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Generates the initial transformation matrix for drawing. Additionally, it sets the
6801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * minimum and maximum scale values.
6811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
6821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void generateMatrix() {
6831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
6841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
6851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vwidth = mAllowCrop ? sCropSize : getWidth();
6871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vheight = mAllowCrop ? sCropSize : getHeight();
6881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final boolean fits = (dwidth < 0 || vwidth == dwidth) &&
6901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                (dheight < 0 || vheight == dheight);
6911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
6921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fits && !mAllowCrop) {
6931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.reset();
6941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
6951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Generate the required transforms for the photo
6961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTempSrc.set(0, 0, dwidth, dheight);
6971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mAllowCrop) {
6981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mTempDst.set(mCropRect);
6991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
7001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mTempDst.set(0, 0, vwidth, vheight);
7011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
7024f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei
7034f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei            if (dwidth < vwidth && dheight < vheight && !mAllowCrop) {
7044f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei                mMatrix.setTranslate(vwidth / 2 - dwidth / 2, vheight / 2 - dheight / 2);
7054f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei            } else {
7064f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei                mMatrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);
7074f38cc1673163848c7b843cb9d23b21945b0bf82Mark Wei            }
7081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
7091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mOriginalMatrix.set(mMatrix);
7101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
7111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void generateScale() {
7131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dwidth = mDrawable.getIntrinsicWidth();
7141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int dheight = mDrawable.getIntrinsicHeight();
7151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vwidth = mAllowCrop ? getCropSize() : getWidth();
7171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final int vheight = mAllowCrop ? getCropSize() : getHeight();
7181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (dwidth < vwidth && dheight < vheight && !mAllowCrop) {
7201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMinScale = 1.0f;
7211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
7221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMinScale = getScale();
7231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
7241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMaxScale = Math.max(mMinScale * 8, 8);
7251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
7261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
7281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @return the size of the crop regions
7291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
7301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private int getCropSize() {
7311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mCropSize > 0 ? mCropSize : sCropSize;
7321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
7331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
7351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Returns the currently applied scale factor for the image.
7361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * <p>
7371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * NOTE: This method overwrites any values stored in {@link #mValues}.
7381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
7391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private float getScale() {
7401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.getValues(mValues);
7411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return mValues[Matrix.MSCALE_X];
7421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
7431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
7451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Scales the image while keeping the aspect ratio.
7461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
7471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * The given scale is capped so that the resulting scale of the image always remains
7481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * between {@link #mMinScale} and {@link #mMaxScale}.
7491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
7501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * The scaled image is never allowed to be outside of the viewable area. If the image
7511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * is smaller than the viewable area, it will be centered.
7521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
7531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param newScale the new scale
7541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param centerX the center horizontal point around which to scale
7551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param centerY the center vertical point around which to scale
7561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
7571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void scale(float newScale, float centerX, float centerY) {
7581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // rotate back to the original orientation
7591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.postRotate(-mRotation, getWidth() / 2, getHeight() / 2);
7601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // ensure that mMixScale <= newScale <= mMaxScale
7621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        newScale = Math.max(newScale, mMinScale);
7631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        newScale = Math.min(newScale, mMaxScale);
7641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float currentScale = getScale();
7661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float factor = newScale / currentScale;
7671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // apply the scale factor
7691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.postScale(factor, factor, centerX, centerY);
7701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // ensure the image is within the view bounds
7721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        snap();
7731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // re-apply any rotation
7751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.postRotate(mRotation, getWidth() / 2, getHeight() / 2);
7761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        invalidate();
7781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
7791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
7811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Translates the image.
7821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
7831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * This method will not allow the image to be translated outside of the visible area.
7841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
7851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param tx how many pixels to translate horizontally
7861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param ty how many pixels to translate vertically
7871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @return {@code true} if the translation was applied as specified. Otherwise, {@code false}
7881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *      if the translation was modified.
7891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
7901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private boolean translate(float tx, float ty) {
7911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTranslateRect.set(mTempSrc);
7921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.mapRect(mTranslateRect);
7931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float maxLeft = mAllowCrop ? mCropRect.left : 0.0f;
7951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float maxRight = mAllowCrop ? mCropRect.right : getWidth();
7961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float l = mTranslateRect.left;
7971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float r = mTranslateRect.right;
7981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
7991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float translateX;
8001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mAllowCrop) {
8011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // If we're cropping, allow the image to scroll off the edge of the screen
8021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateX = Math.max(maxLeft - mTranslateRect.right,
8031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    Math.min(maxRight - mTranslateRect.left, tx));
8041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
8051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Otherwise, ensure the image never leaves the screen
8061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (r - l < maxRight - maxLeft) {
8071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2;
8081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
8091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                translateX = Math.max(maxRight - r, Math.min(maxLeft - l, tx));
8101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
8111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
8121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxTop = mAllowCrop ? mCropRect.top: 0.0f;
8141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight();
8151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float t = mTranslateRect.top;
8161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float b = mTranslateRect.bottom;
8171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float translateY;
8191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mAllowCrop) {
8211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // If we're cropping, allow the image to scroll off the edge of the screen
8221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateY = Math.max(maxTop - mTranslateRect.bottom,
8231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    Math.min(maxBottom - mTranslateRect.top, ty));
8241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
8251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Otherwise, ensure the image never leaves the screen
8261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (b - t < maxBottom - maxTop) {
8271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2;
8281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
8291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                translateY = Math.max(maxBottom - b, Math.min(maxTop - t, ty));
8301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
8311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
8321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Do the translation
8341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.postTranslate(translateX, translateY);
8351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        invalidate();
8361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return (translateX == tx) && (translateY == ty);
8381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
8391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
8411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Snaps the image so it touches all edges of the view.
8421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
8431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void snap() {
8441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTranslateRect.set(mTempSrc);
8451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mMatrix.mapRect(mTranslateRect);
8461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Determine how much to snap in the horizontal direction [if any]
8481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxLeft = mAllowCrop ? mCropRect.left : 0.0f;
8491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxRight = mAllowCrop ? mCropRect.right : getWidth();
8501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float l = mTranslateRect.left;
8511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float r = mTranslateRect.right;
8521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float translateX;
8541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (r - l < maxRight - maxLeft) {
8551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is narrower than view; translate to the center of the view
8561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2;
8571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (l > maxLeft) {
8581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is off right-edge of screen; bring it into view
8591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateX = maxLeft - l;
8601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (r < maxRight) {
8611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is off left-edge of screen; bring it into view
8621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateX = maxRight - r;
8631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
8641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateX = 0.0f;
8651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
8661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Determine how much to snap in the vertical direction [if any]
8681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxTop = mAllowCrop ? mCropRect.top : 0.0f;
8691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight();
8701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float t = mTranslateRect.top;
8711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        float b = mTranslateRect.bottom;
8721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        final float translateY;
8741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (b - t < maxBottom - maxTop) {
8751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is shorter than view; translate to the bottom edge of the view
8761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2;
8771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (t > maxTop) {
8781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is off bottom-edge of screen; bring it into view
8791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateY = maxTop - t;
8801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else if (b < maxBottom) {
8811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Image is off top-edge of screen; bring it into view
8821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateY = maxBottom - b;
8831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
8841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            translateY = 0.0f;
8851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
8861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (Math.abs(translateX) > SNAP_THRESHOLD || Math.abs(translateY) > SNAP_THRESHOLD) {
8881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mSnapRunnable.start(translateX, translateY);
8891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
8901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.postTranslate(translateX, translateY);
8911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            invalidate();
8921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
8931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
8941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
8951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
8961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Rotates the image, either instantly or gradually
8971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     *
8981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param degrees how many degrees to rotate the image, positive rotates clockwise
8991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * @param animate if {@code true}, animate during the rotation. Otherwise, snap rotate.
9001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
9011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void rotate(float degrees, boolean animate) {
9021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (animate) {
9031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRotateRunnable.start(degrees);
9041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
9051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRotation += degrees;
9061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mMatrix.postRotate(degrees, getWidth() / 2, getHeight() / 2);
9071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            invalidate();
9081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
9091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
9101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
9121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Initializes the header and any static values
9131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
9141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private void initialize() {
9151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        Context context = getContext();
9161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (!sInitialized) {
9181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sInitialized = true;
9191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            Resources resources = context.getApplicationContext().getResources();
9211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropSize = resources.getDimensionPixelSize(R.dimen.photo_crop_width);
9231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropDimPaint = new Paint();
9251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropDimPaint.setAntiAlias(true);
9261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropDimPaint.setColor(resources.getColor(R.color.photo_crop_dim_color));
9271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropDimPaint.setStyle(Style.FILL);
9281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropPaint = new Paint();
9301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropPaint.setAntiAlias(true);
9311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropPaint.setColor(resources.getColor(R.color.photo_crop_highlight_color));
9321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropPaint.setStyle(Style.STROKE);
9331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            sCropPaint.setStrokeWidth(resources.getDimension(R.dimen.photo_crop_stroke_width));
9341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
9351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9364054bd6d6c5d51d9492184b1881253c223ddbf7dPaul Westbrook        mGestureDetector = new GestureDetectorCompat(context, this, null);
9371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleGetureDetector = new ScaleGestureDetector(context, this);
9381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mScaleRunnable = new ScaleRunnable(this);
9391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mTranslateRunnable = new TranslateRunnable(this);
9401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mSnapRunnable = new SnapRunnable(this);
9411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mRotateRunnable = new RotateRunnable(this);
9421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
9431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
9451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Runnable that animates an image scale operation.
9461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
9471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static class ScaleRunnable implements Runnable {
9481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private final PhotoView mHeader;
9501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mCenterX;
9521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mCenterY;
9531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mZoomingIn;
9551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mTargetScale;
9571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mStartScale;
9581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mVelocity;
9591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private long mStartTime;
9601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mRunning;
9621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mStop;
9631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public ScaleRunnable(PhotoView header) {
9651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader = header;
9661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
9671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
9691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Starts the animation. There is no target scale bounds check.
9701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
9711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public boolean start(float startScale, float targetScale, float centerX, float centerY) {
9721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mRunning) {
9731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
9741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
9751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCenterX = centerX;
9771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCenterY = centerY;
9781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Ensure the target scale is within the min/max bounds
9801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTargetScale = targetScale;
9811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStartTime = System.currentTimeMillis();
9821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStartScale = startScale;
9831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mZoomingIn = mTargetScale > mStartScale;
9841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mVelocity = (mTargetScale - mStartScale) / ZOOM_ANIMATION_DURATION;
9851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = true;
9861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = false;
9871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
9881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
9891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
9901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
9921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final zoom.
9931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
9941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void stop() {
9951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = false;
9961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = true;
9971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
9981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
9991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        @Override
10001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void run() {
10011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
10021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
10031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Scale
10061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            long now = System.currentTimeMillis();
10071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            long ellapsed = now - mStartTime;
10081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float newScale = (mStartScale + mVelocity * ellapsed);
10091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.scale(newScale, mCenterX, mCenterY);
10101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Stop when done
10121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (newScale == mTargetScale || (mZoomingIn == (newScale > mTargetScale))) {
10131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mHeader.scale(mTargetScale, mCenterX, mCenterY);
10141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                stop();
10151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (!mStop) {
10181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mHeader.post(this);
10191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
10211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
10221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
10241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Runnable that animates an image translation operation.
10251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
10261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static class TranslateRunnable implements Runnable {
10271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private static final float DECELERATION_RATE = 1000f;
10291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private static final long NEVER = -1L;
10301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private final PhotoView mHeader;
10321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mVelocityX;
10341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mVelocityY;
10351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private long mLastRunTime;
10371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mRunning;
10381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mStop;
10391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public TranslateRunnable(PhotoView header) {
10411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mLastRunTime = NEVER;
10421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader = header;
10431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
10441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
10461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Starts the animation.
10471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
10481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public boolean start(float velocityX, float velocityY) {
10491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mRunning) {
10501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
10511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mLastRunTime = NEVER;
10531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mVelocityX = velocityX;
10541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mVelocityY = velocityY;
10551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = false;
10561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = true;
10571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
10581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
10591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
10601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
10621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final translation.
10631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
10641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void stop() {
10651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = false;
10661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = true;
10671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
10681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        @Override
10701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void run() {
10711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // See if we were told to stop:
10721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
10731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
10741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
10761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Translate according to current velocities and time delta:
10771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            long now = System.currentTimeMillis();
10781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float delta = (mLastRunTime != NEVER) ? (now - mLastRunTime) / 1000f : 0f;
10791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            final boolean didTranslate = mHeader.translate(mVelocityX * delta, mVelocityY * delta);
10801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mLastRunTime = now;
10811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Slow down:
10821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float slowDown = DECELERATION_RATE * delta;
10831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mVelocityX > 0f) {
10841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mVelocityX -= slowDown;
10851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mVelocityX < 0f) {
10861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    mVelocityX = 0f;
10871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
10881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
10891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mVelocityX += slowDown;
10901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mVelocityX > 0f) {
10911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    mVelocityX = 0f;
10921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
10931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
10941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mVelocityY > 0f) {
10951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mVelocityY -= slowDown;
10961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mVelocityY < 0f) {
10971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    mVelocityY = 0f;
10981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
10991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
11001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mVelocityY += slowDown;
11011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mVelocityY > 0f) {
11021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    mVelocityY = 0f;
11031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
11041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Stop when done
11071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if ((mVelocityX == 0f && mVelocityY == 0f) || !didTranslate) {
11081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                stop();
11091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mHeader.snap();
11101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // See if we need to continue flinging:
11131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
11141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
11151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
11171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
11181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
11191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
11211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Runnable that animates an image translation operation.
11221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
11231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static class SnapRunnable implements Runnable {
11241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private static final long NEVER = -1L;
11261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private final PhotoView mHeader;
11281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mTranslateX;
11301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mTranslateY;
11311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private long mStartRunTime;
11331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mRunning;
11341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mStop;
11351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public SnapRunnable(PhotoView header) {
11371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStartRunTime = NEVER;
11381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader = header;
11391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
11401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
11421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Starts the animation.
11431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
11441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public boolean start(float translateX, float translateY) {
11451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mRunning) {
11461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return false;
11471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStartRunTime = NEVER;
11491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateX = translateX;
11501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateY = translateY;
11511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = false;
11521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = true;
11531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.postDelayed(this, SNAP_DELAY);
11541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return true;
11551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
11561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
11581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final translation.
11591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
11601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void stop() {
11611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = false;
11621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = true;
11631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
11641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        @Override
11661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void run() {
11671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // See if we were told to stop:
11681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
11691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
11701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // Translate according to current velocities and time delta:
11731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            long now = System.currentTimeMillis();
11741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float delta = (mStartRunTime != NEVER) ? (now - mStartRunTime) : 0f;
11751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStartRunTime == NEVER) {
11771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mStartRunTime = now;
11781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float transX;
11811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            float transY;
11821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (delta >= SNAP_DURATION) {
11831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                transX = mTranslateX;
11841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                transY = mTranslateY;
11851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            } else {
11861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                transX = (mTranslateX / (SNAP_DURATION - delta)) * 10f;
11871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                transY = (mTranslateY / (SNAP_DURATION - delta)) * 10f;
11881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (Math.abs(transX) > Math.abs(mTranslateX) || transX == Float.NaN) {
11891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    transX = mTranslateX;
11901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
11911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (Math.abs(transY) > Math.abs(mTranslateY) || transY == Float.NaN) {
11921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    transY = mTranslateY;
11931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
11941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
11951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
11961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.translate(transX, transY);
11971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateX -= transX;
11981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTranslateY -= transY;
11991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mTranslateX == 0 && mTranslateY == 0) {
12011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                stop();
12021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // See if we need to continue flinging:
12051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
12061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
12071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
12091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
12101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
12111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
12131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Runnable that animates an image rotation operation.
12141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
12151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static class RotateRunnable implements Runnable {
12161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private static final long NEVER = -1L;
12181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private final PhotoView mHeader;
12201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mTargetRotation;
12221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mAppliedRotation;
12231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private float mVelocity;
12241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private long mLastRuntime;
12251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mRunning;
12271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        private boolean mStop;
12281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public RotateRunnable(PhotoView header) {
12301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader = header;
12311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
12321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
12341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Starts the animation.
12351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
12361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void start(float rotation) {
12371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mRunning) {
12381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
12391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mTargetRotation = rotation;
12421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mVelocity = mTargetRotation / ROTATE_ANIMATION_DURATION;
12431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mAppliedRotation = 0f;
12441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mLastRuntime = NEVER;
12451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = false;
12461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = true;
12471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
12481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
12491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        /**
12511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         * Stops the animation in place. It does not snap the image to its final rotation.
12521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein         */
12531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void stop() {
12541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mRunning = false;
12551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mStop = true;
12561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
12571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        @Override
12591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public void run() {
12601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
12611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
12621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mAppliedRotation != mTargetRotation) {
12651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                long now = System.currentTimeMillis();
12661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                long delta = mLastRuntime != NEVER ? now - mLastRuntime : 0L;
12671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                float rotationAmount = mVelocity * delta;
12681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mAppliedRotation < mTargetRotation
12691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                        && mAppliedRotation + rotationAmount > mTargetRotation
12701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                        || mAppliedRotation > mTargetRotation
12711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                        && mAppliedRotation + rotationAmount < mTargetRotation) {
12721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    rotationAmount = mTargetRotation - mAppliedRotation;
12731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
12741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mHeader.rotate(rotationAmount, false);
12751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mAppliedRotation += rotationAmount;
12761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                if (mAppliedRotation == mTargetRotation) {
12771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    stop();
12781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                }
12791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mLastRuntime = now;
12801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
12821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mStop) {
12831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return;
12841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
12851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mHeader.post(this);
12861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
12871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
12881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein}
1289