@@ -13,6 +13,8 @@ import android.widget.ImageView;  | 
            ||
| 13 | 13 | 
                import com.android.common.utils.LogHelper;  | 
            
| 14 | 14 | 
                import com.android.views.PhotoView.TouchImageView;  | 
            
| 15 | 15 | 
                import com.android.views.progressbar.ProgressWheel;  | 
            
| 16 | 
                +import com.android.views.rotatephotoview.PhotoView;  | 
            |
| 17 | 
                +import com.android.views.rotatephotoview.PhotoViewAttacher;  | 
            |
| 16 | 18 | 
                import com.nostra13.universalimageloader.core.DisplayImageOptions;  | 
            
| 17 | 19 | 
                import com.nostra13.universalimageloader.core.assist.FailReason;  | 
            
| 18 | 20 | 
                import com.nostra13.universalimageloader.core.assist.ImageScaleType;  | 
            
                @@ -65,8 +67,17 @@ public class FullScreenPhotoPageAdapter extends PagerAdapter implements TouchIma  | 
            ||
| 65 | 67 | 
                @Override  | 
            
| 66 | 68 | 
                     public Object instantiateItem(ViewGroup view, final int position) {
               | 
            
| 67 | 69 | 
                View imageLayout = inflater.inflate(R.layout.item_pager_touchable_photo, view, false);  | 
            
| 68 | 
                - final TouchImageView imageView = (TouchImageView) imageLayout.findViewById(R.id.iv_photo_item);  | 
            |
| 69 | 
                - imageView.setZoomModeChangeListener(this);  | 
            |
| 70 | 
                + final PhotoView imageView = (PhotoView) imageLayout.findViewById(R.id.iv_photo_item);  | 
            |
| 71 | 
                + PhotoViewAttacher attacher = new PhotoViewAttacher(imageView);  | 
            |
| 72 | 
                + attacher.setRotatable(true);  | 
            |
| 73 | 
                + attacher.setToRightAngle(true);  | 
            |
| 74 | 
                +        attacher.setOnRotateListener(new PhotoViewAttacher.OnRotateListener() {
               | 
            |
| 75 | 
                + @Override  | 
            |
| 76 | 
                +            public void onRotate(int degree) {
               | 
            |
| 77 | 
                + //do something  | 
            |
| 78 | 
                + }  | 
            |
| 79 | 
                + });  | 
            |
| 80 | 
                + attacher.update();  | 
            |
| 70 | 81 | 
                final ProgressWheel spinner = (ProgressWheel) imageLayout.findViewById(R.id.pb_loading);  | 
            
| 71 | 82 | 
                final ImageView reloadBtn = (ImageView) imageLayout.findViewById(R.id.btn_reload_photo);  | 
            
| 72 | 83 | 
                         reloadBtn.setOnClickListener(new View.OnClickListener(){
               | 
            
                @@ -22,7 +22,7 @@  | 
            ||
| 22 | 22 | 
                android:visibility="gone"/>  | 
            
| 23 | 23 | 
                 | 
            
| 24 | 24 | 
                 | 
            
| 25 | 
                - <com.android.views.PhotoView.TouchImageView  | 
            |
| 25 | 
                + <com.android.views.rotatephotoview.PhotoView  | 
            |
| 26 | 26 | 
                android:id="@+id/iv_photo_item"  | 
            
| 27 | 27 | 
                android:layout_width="match_parent"  | 
            
| 28 | 28 | 
                android:layout_height="match_parent"  | 
            
                @@ -49,6 +49,8 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 49 | 49 | 
                float minScale = 1f;  | 
            
| 50 | 50 | 
                float maxScale = 3f;  | 
            
| 51 | 51 | 
                float oldDist = 1f;  | 
            
| 52 | 
                + float oldRotation = 0;  | 
            |
| 53 | 
                + float rotation;  | 
            |
| 52 | 54 | 
                 | 
            
| 53 | 55 | 
                PointF lastDelta = new PointF(0, 0);  | 
            
| 54 | 56 | 
                float velocity = 0;  | 
            
                @@ -118,6 +120,7 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 118 | 120 | 
                break;  | 
            
| 119 | 121 | 
                case MotionEvent.ACTION_POINTER_DOWN:  | 
            
| 120 | 122 | 
                oldDist = spacing(event);  | 
            
| 123 | 
                + oldRotation = rotation(rawEvent);  | 
            |
| 121 | 124 | 
                //Log.d(TAG, "oldDist=" + oldDist);  | 
            
| 122 | 125 | 
                                         if (oldDist > 10f) {
               | 
            
| 123 | 126 | 
                savedMatrix.set(matrix);  | 
            
                @@ -206,7 +209,7 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 206 | 209 | 
                if (10 > Math.abs(oldDist - newDist) || Math.abs(oldDist - newDist) > 50) break;  | 
            
| 207 | 210 | 
                float mScaleFactor = newDist / oldDist;  | 
            
| 208 | 211 | 
                oldDist = newDist;  | 
            
| 209 | 
                -  | 
            |
| 212 | 
                + rotation = rotation(rawEvent) - oldRotation;  | 
            |
| 210 | 213 | 
                float origScale = saveScale;  | 
            
| 211 | 214 | 
                saveScale *= mScaleFactor;  | 
            
| 212 | 215 | 
                                             if (saveScale > maxScale) {
               | 
            
                @@ -226,6 +229,7 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 226 | 229 | 
                calcPadding();  | 
            
| 227 | 230 | 
                                             if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
               | 
            
| 228 | 231 | 
                matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);  | 
            
| 232 | 
                + matrix.postRotate(rotation, mid.x, mid.y);  | 
            |
| 229 | 233 | 
                                                 if (mScaleFactor < 1) {
               | 
            
| 230 | 234 | 
                fillMatrixXY();  | 
            
| 231 | 235 | 
                                                     if (mScaleFactor < 1) {
               | 
            
                @@ -235,6 +239,7 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 235 | 239 | 
                                             } else {
               | 
            
| 236 | 240 | 
                PointF mid = midPointF(event);  | 
            
| 237 | 241 | 
                matrix.postScale(mScaleFactor, mScaleFactor, mid.x, mid.y);  | 
            
| 242 | 
                + matrix.postRotate(rotation, mid.x, mid.y);  | 
            |
| 238 | 243 | 
                fillMatrixXY();  | 
            
| 239 | 244 | 
                                                 if (mScaleFactor < 1) {
               | 
            
| 240 | 245 | 
                if (matrixX < -right)  | 
            
                @@ -258,6 +263,22 @@ public class TouchImageView extends ImageView {
               | 
            ||
| 258 | 263 | 
                }  | 
            
| 259 | 264 | 
                });  | 
            
| 260 | 265 | 
                }  | 
            
| 266 | 
                +  | 
            |
| 267 | 
                + // 取旋转角度  | 
            |
| 268 | 
                +    private float rotation(MotionEvent event) {
               | 
            |
| 269 | 
                + double delta_x;  | 
            |
| 270 | 
                + double delta_y;  | 
            |
| 271 | 
                + double radians = 0;  | 
            |
| 272 | 
                +        try {
               | 
            |
| 273 | 
                + delta_x = (event.getX(0) - event.getX(1));  | 
            |
| 274 | 
                + delta_y = (event.getY(0) - event.getY(1));  | 
            |
| 275 | 
                + radians = Math.atan2(delta_y, delta_x);  | 
            |
| 276 | 
                +        } catch (IllegalArgumentException e) {
               | 
            |
| 277 | 
                + e.printStackTrace();  | 
            |
| 278 | 
                + }  | 
            |
| 279 | 
                + return (float) Math.toDegrees(radians);  | 
            |
| 280 | 
                + }  | 
            |
| 281 | 
                +  | 
            |
| 261 | 282 | 
                public void resetScale()  | 
            
| 262 | 283 | 
                     {
               | 
            
| 263 | 284 | 
                fillMatrixXY();  | 
            
                @@ -0,0 +1,59 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.annotation.TargetApi;  | 
            |
| 19 | 
                +import android.os.Build.VERSION;  | 
            |
| 20 | 
                +import android.os.Build.VERSION_CODES;  | 
            |
| 21 | 
                +import android.view.MotionEvent;  | 
            |
| 22 | 
                +import android.view.View;  | 
            |
| 23 | 
                +  | 
            |
| 24 | 
                +public class Compat {
               | 
            |
| 25 | 
                +  | 
            |
| 26 | 
                + private static final int SIXTY_FPS_INTERVAL = 1000 / 60;  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                +    public static void postOnAnimation(View view, Runnable runnable) {
               | 
            |
| 29 | 
                +        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
               | 
            |
| 30 | 
                + postOnAnimationJellyBean(view, runnable);  | 
            |
| 31 | 
                +        } else {
               | 
            |
| 32 | 
                + view.postDelayed(runnable, SIXTY_FPS_INTERVAL);  | 
            |
| 33 | 
                + }  | 
            |
| 34 | 
                + }  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + @TargetApi(16)  | 
            |
| 37 | 
                +    private static void postOnAnimationJellyBean(View view, Runnable runnable) {
               | 
            |
| 38 | 
                + view.postOnAnimation(runnable);  | 
            |
| 39 | 
                + }  | 
            |
| 40 | 
                +  | 
            |
| 41 | 
                +    public static int getPointerIndex(int action) {
               | 
            |
| 42 | 
                + if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)  | 
            |
| 43 | 
                + return getPointerIndexHoneyComb(action);  | 
            |
| 44 | 
                + else  | 
            |
| 45 | 
                + return getPointerIndexEclair(action);  | 
            |
| 46 | 
                + }  | 
            |
| 47 | 
                +  | 
            |
| 48 | 
                +    @SuppressWarnings("deprecation")
               | 
            |
| 49 | 
                + @TargetApi(VERSION_CODES.ECLAIR)  | 
            |
| 50 | 
                +    private static int getPointerIndexEclair(int action) {
               | 
            |
| 51 | 
                + return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;  | 
            |
| 52 | 
                + }  | 
            |
| 53 | 
                +  | 
            |
| 54 | 
                + @TargetApi(VERSION_CODES.HONEYCOMB)  | 
            |
| 55 | 
                +    private static int getPointerIndexHoneyComb(int action) {
               | 
            |
| 56 | 
                + return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;  | 
            |
| 57 | 
                + }  | 
            |
| 58 | 
                +  | 
            |
| 59 | 
                +}  | 
            
                @@ -0,0 +1,100 @@  | 
            ||
| 1 | 
                +package com.android.views.rotatephotoview;  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +import android.graphics.RectF;  | 
            |
| 4 | 
                +import android.view.GestureDetector;  | 
            |
| 5 | 
                +import android.view.MotionEvent;  | 
            |
| 6 | 
                +import android.widget.ImageView;  | 
            |
| 7 | 
                +  | 
            |
| 8 | 
                +/**  | 
            |
| 9 | 
                + * Provided default implementation of GestureDetector.OnDoubleTapListener, to be overridden with custom behavior, if needed  | 
            |
| 10 | 
                + * <p> </p>  | 
            |
| 11 | 
                + * To be used via {@link uk.co.senab.photoview.PhotoViewAttacher#setOnDoubleTapListener(GestureDetector.OnDoubleTapListener)}
               | 
            |
| 12 | 
                + */  | 
            |
| 13 | 
                +public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener {
               | 
            |
| 14 | 
                +  | 
            |
| 15 | 
                + private PhotoViewAttacher photoViewAttacher;  | 
            |
| 16 | 
                +  | 
            |
| 17 | 
                + /**  | 
            |
| 18 | 
                + * Default constructor  | 
            |
| 19 | 
                + *  | 
            |
| 20 | 
                + * @param photoViewAttacher PhotoViewAttacher to bind to  | 
            |
| 21 | 
                + */  | 
            |
| 22 | 
                +    public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) {
               | 
            |
| 23 | 
                + setPhotoViewAttacher(photoViewAttacher);  | 
            |
| 24 | 
                + }  | 
            |
| 25 | 
                +  | 
            |
| 26 | 
                + /**  | 
            |
| 27 | 
                + * Allows to change PhotoViewAttacher within range of single instance  | 
            |
| 28 | 
                + *  | 
            |
| 29 | 
                + * @param newPhotoViewAttacher PhotoViewAttacher to bind to  | 
            |
| 30 | 
                + */  | 
            |
| 31 | 
                +    public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) {
               | 
            |
| 32 | 
                + this.photoViewAttacher = newPhotoViewAttacher;  | 
            |
| 33 | 
                + }  | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                + @Override  | 
            |
| 36 | 
                +    public boolean onSingleTapConfirmed(MotionEvent e) {
               | 
            |
| 37 | 
                + if (this.photoViewAttacher == null)  | 
            |
| 38 | 
                + return false;  | 
            |
| 39 | 
                +  | 
            |
| 40 | 
                + ImageView imageView = photoViewAttacher.getImageView();  | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                +        if (null != photoViewAttacher.getOnPhotoTapListener()) {
               | 
            |
| 43 | 
                + final RectF displayRect = photoViewAttacher.getDisplayRect();  | 
            |
| 44 | 
                +  | 
            |
| 45 | 
                +            if (null != displayRect) {
               | 
            |
| 46 | 
                + final float x = e.getX(), y = e.getY();  | 
            |
| 47 | 
                +  | 
            |
| 48 | 
                + // Check to see if the user tapped on the photo  | 
            |
| 49 | 
                +                if (displayRect.contains(x, y)) {
               | 
            |
| 50 | 
                +  | 
            |
| 51 | 
                + float xResult = (x - displayRect.left)  | 
            |
| 52 | 
                + / displayRect.width();  | 
            |
| 53 | 
                + float yResult = (y - displayRect.top)  | 
            |
| 54 | 
                + / displayRect.height();  | 
            |
| 55 | 
                +  | 
            |
| 56 | 
                + photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult);  | 
            |
| 57 | 
                + return true;  | 
            |
| 58 | 
                +                }else{
               | 
            |
| 59 | 
                + photoViewAttacher.getOnPhotoTapListener().onOutsidePhotoTap();  | 
            |
| 60 | 
                + }  | 
            |
| 61 | 
                + }  | 
            |
| 62 | 
                + }  | 
            |
| 63 | 
                +        if (null != photoViewAttacher.getOnViewTapListener()) {
               | 
            |
| 64 | 
                + photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY());  | 
            |
| 65 | 
                + }  | 
            |
| 66 | 
                +  | 
            |
| 67 | 
                + return false;  | 
            |
| 68 | 
                + }  | 
            |
| 69 | 
                +  | 
            |
| 70 | 
                + @Override  | 
            |
| 71 | 
                +    public boolean onDoubleTap(MotionEvent ev) {
               | 
            |
| 72 | 
                + if (photoViewAttacher == null)  | 
            |
| 73 | 
                + return false;  | 
            |
| 74 | 
                +  | 
            |
| 75 | 
                +        try {
               | 
            |
| 76 | 
                + float scale = photoViewAttacher.getScale();  | 
            |
| 77 | 
                + float x = ev.getX();  | 
            |
| 78 | 
                + float y = ev.getY();  | 
            |
| 79 | 
                +  | 
            |
| 80 | 
                +            if (scale < photoViewAttacher.getMediumScale()) {
               | 
            |
| 81 | 
                + photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true);  | 
            |
| 82 | 
                +            } else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) {
               | 
            |
| 83 | 
                + photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true);  | 
            |
| 84 | 
                +            } else {
               | 
            |
| 85 | 
                + photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true);  | 
            |
| 86 | 
                + }  | 
            |
| 87 | 
                +        } catch (ArrayIndexOutOfBoundsException e) {
               | 
            |
| 88 | 
                + // Can sometimes happen when getX() and getY() is called  | 
            |
| 89 | 
                + }  | 
            |
| 90 | 
                +  | 
            |
| 91 | 
                + return true;  | 
            |
| 92 | 
                + }  | 
            |
| 93 | 
                +  | 
            |
| 94 | 
                + @Override  | 
            |
| 95 | 
                +    public boolean onDoubleTapEvent(MotionEvent e) {
               | 
            |
| 96 | 
                + // Wait for the confirmed onDoubleTap() instead  | 
            |
| 97 | 
                + return false;  | 
            |
| 98 | 
                + }  | 
            |
| 99 | 
                +  | 
            |
| 100 | 
                +}  | 
            
                @@ -0,0 +1,285 @@  | 
            ||
| 1 | 
                +/**  | 
            |
| 2 | 
                + * ****************************************************************************  | 
            |
| 3 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 4 | 
                + * <p>  | 
            |
| 5 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 6 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 7 | 
                + * You may obtain a copy of the License at  | 
            |
| 8 | 
                + * <p>  | 
            |
| 9 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 10 | 
                + * <p>  | 
            |
| 11 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 12 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 13 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 14 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 15 | 
                + * limitations under the License.  | 
            |
| 16 | 
                + * *****************************************************************************  | 
            |
| 17 | 
                + */  | 
            |
| 18 | 
                +package com.android.views.rotatephotoview;  | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                +import android.graphics.Bitmap;  | 
            |
| 21 | 
                +import android.graphics.Matrix;  | 
            |
| 22 | 
                +import android.graphics.RectF;  | 
            |
| 23 | 
                +import android.view.GestureDetector;  | 
            |
| 24 | 
                +import android.view.View;  | 
            |
| 25 | 
                +import android.widget.ImageView;  | 
            |
| 26 | 
                +  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                +public interface IPhotoView {
               | 
            |
| 29 | 
                +  | 
            |
| 30 | 
                + float DEFAULT_MAX_SCALE = 3.0f;  | 
            |
| 31 | 
                + float DEFAULT_MID_SCALE = 1.75f;  | 
            |
| 32 | 
                + float DEFAULT_MIN_SCALE = 1.0f;  | 
            |
| 33 | 
                + int DEFAULT_ZOOM_DURATION = 200;  | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                + /**  | 
            |
| 36 | 
                + * Returns true if the PhotoView is set to allow zooming of Photos.  | 
            |
| 37 | 
                + *  | 
            |
| 38 | 
                + * @return true if the PhotoView allows zooming.  | 
            |
| 39 | 
                + */  | 
            |
| 40 | 
                + boolean canZoom();  | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                + /**  | 
            |
| 43 | 
                + * Gets the Display Rectangle of the currently displayed Drawable. The Rectangle is relative to  | 
            |
| 44 | 
                + * this View and includes all scaling and translations.  | 
            |
| 45 | 
                + *  | 
            |
| 46 | 
                + * @return - RectF of Displayed Drawable  | 
            |
| 47 | 
                + */  | 
            |
| 48 | 
                + RectF getDisplayRect();  | 
            |
| 49 | 
                +  | 
            |
| 50 | 
                + /**  | 
            |
| 51 | 
                + * Sets the Display Matrix of the currently displayed Drawable. The Rectangle is considered  | 
            |
| 52 | 
                + * relative to this View and includes all scaling and translations.  | 
            |
| 53 | 
                + *  | 
            |
| 54 | 
                + * @param finalMatrix target matrix to set PhotoView to  | 
            |
| 55 | 
                + * @return - true if rectangle was applied successfully  | 
            |
| 56 | 
                + */  | 
            |
| 57 | 
                + boolean setDisplayMatrix(Matrix finalMatrix);  | 
            |
| 58 | 
                +  | 
            |
| 59 | 
                + /**  | 
            |
| 60 | 
                + * Copies the Display Matrix of the currently displayed Drawable. The Rectangle is considered  | 
            |
| 61 | 
                + * relative to this View and includes all scaling and translations.  | 
            |
| 62 | 
                + *  | 
            |
| 63 | 
                + * @param matrix target matrix to copy to  | 
            |
| 64 | 
                + */  | 
            |
| 65 | 
                + void getDisplayMatrix(Matrix matrix);  | 
            |
| 66 | 
                +  | 
            |
| 67 | 
                + /**  | 
            |
| 68 | 
                + * @return The current minimum scale level. What this value represents depends on the current  | 
            |
| 69 | 
                +     * {@link ImageView.ScaleType}.
               | 
            |
| 70 | 
                + */  | 
            |
| 71 | 
                + float getMinimumScale();  | 
            |
| 72 | 
                +  | 
            |
| 73 | 
                + /**  | 
            |
| 74 | 
                + * @return The current medium scale level. What this value represents depends on the current  | 
            |
| 75 | 
                +     * {@link ImageView.ScaleType}.
               | 
            |
| 76 | 
                + */  | 
            |
| 77 | 
                + float getMediumScale();  | 
            |
| 78 | 
                +  | 
            |
| 79 | 
                + /**  | 
            |
| 80 | 
                + * @return The current maximum scale level. What this value represents depends on the current  | 
            |
| 81 | 
                +     * {@link ImageView.ScaleType}.
               | 
            |
| 82 | 
                + */  | 
            |
| 83 | 
                + float getMaximumScale();  | 
            |
| 84 | 
                +  | 
            |
| 85 | 
                + /**  | 
            |
| 86 | 
                + * Returns the current scale value  | 
            |
| 87 | 
                + *  | 
            |
| 88 | 
                + * @return float - current scale value  | 
            |
| 89 | 
                + */  | 
            |
| 90 | 
                + float getScale();  | 
            |
| 91 | 
                +  | 
            |
| 92 | 
                + /**  | 
            |
| 93 | 
                + * Return the current scale type in use by the ImageView.  | 
            |
| 94 | 
                + *  | 
            |
| 95 | 
                + * @return current ImageView.ScaleType  | 
            |
| 96 | 
                + */  | 
            |
| 97 | 
                + ImageView.ScaleType getScaleType();  | 
            |
| 98 | 
                +  | 
            |
| 99 | 
                + /**  | 
            |
| 100 | 
                + * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll  | 
            |
| 101 | 
                + * to it's horizontal edge.  | 
            |
| 102 | 
                + *  | 
            |
| 103 | 
                + * @param allow whether to allow intercepting by parent element or not  | 
            |
| 104 | 
                + */  | 
            |
| 105 | 
                + void setAllowParentInterceptOnEdge(boolean allow);  | 
            |
| 106 | 
                +  | 
            |
| 107 | 
                + /**  | 
            |
| 108 | 
                +     * Sets the minimum scale level. What this value represents depends on the current {@link
               | 
            |
| 109 | 
                + * ImageView.ScaleType}.  | 
            |
| 110 | 
                + *  | 
            |
| 111 | 
                + * @param minimumScale minimum allowed scale  | 
            |
| 112 | 
                + */  | 
            |
| 113 | 
                + void setMinimumScale(float minimumScale);  | 
            |
| 114 | 
                +  | 
            |
| 115 | 
                + /**  | 
            |
| 116 | 
                +     * Sets the medium scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
               | 
            |
| 117 | 
                + *  | 
            |
| 118 | 
                + * @param mediumScale medium scale preset  | 
            |
| 119 | 
                + */  | 
            |
| 120 | 
                + void setMediumScale(float mediumScale);  | 
            |
| 121 | 
                +  | 
            |
| 122 | 
                + /**  | 
            |
| 123 | 
                +     * Sets the maximum scale level. What this value represents depends on the current {@link
               | 
            |
| 124 | 
                + * ImageView.ScaleType}.  | 
            |
| 125 | 
                + *  | 
            |
| 126 | 
                + * @param maximumScale maximum allowed scale preset  | 
            |
| 127 | 
                + */  | 
            |
| 128 | 
                + void setMaximumScale(float maximumScale);  | 
            |
| 129 | 
                +  | 
            |
| 130 | 
                + /**  | 
            |
| 131 | 
                + * Allows to set all three scale levels at once, so you don't run into problem with setting  | 
            |
| 132 | 
                + * medium/minimum scale before the maximum one  | 
            |
| 133 | 
                + *  | 
            |
| 134 | 
                + * @param minimumScale minimum allowed scale  | 
            |
| 135 | 
                + * @param mediumScale medium allowed scale  | 
            |
| 136 | 
                + * @param maximumScale maximum allowed scale preset  | 
            |
| 137 | 
                + */  | 
            |
| 138 | 
                + void setScaleLevels(float minimumScale, float mediumScale, float maximumScale);  | 
            |
| 139 | 
                +  | 
            |
| 140 | 
                + /**  | 
            |
| 141 | 
                + * Register a callback to be invoked when the Photo displayed by this view is long-pressed.  | 
            |
| 142 | 
                + *  | 
            |
| 143 | 
                + * @param listener - Listener to be registered.  | 
            |
| 144 | 
                + */  | 
            |
| 145 | 
                + void setOnLongClickListener(View.OnLongClickListener listener);  | 
            |
| 146 | 
                +  | 
            |
| 147 | 
                + /**  | 
            |
| 148 | 
                + * Register a callback to be invoked when the Matrix has changed for this View. An example would  | 
            |
| 149 | 
                + * be the user panning or scaling the Photo.  | 
            |
| 150 | 
                + *  | 
            |
| 151 | 
                + * @param listener - Listener to be registered.  | 
            |
| 152 | 
                + */  | 
            |
| 153 | 
                + void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);  | 
            |
| 154 | 
                +  | 
            |
| 155 | 
                + /**  | 
            |
| 156 | 
                + * Register a callback to be invoked when the Photo displayed by this View is tapped with a  | 
            |
| 157 | 
                + * single tap.  | 
            |
| 158 | 
                + *  | 
            |
| 159 | 
                + * @param listener - Listener to be registered.  | 
            |
| 160 | 
                + */  | 
            |
| 161 | 
                + void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);  | 
            |
| 162 | 
                +  | 
            |
| 163 | 
                + /**  | 
            |
| 164 | 
                + * Register a callback to be invoked when the View is tapped with a single tap.  | 
            |
| 165 | 
                + *  | 
            |
| 166 | 
                + * @param onRotateListener  | 
            |
| 167 | 
                + */  | 
            |
| 168 | 
                + void setOnRotateListener(PhotoViewAttacher.OnRotateListener onRotateListener);  | 
            |
| 169 | 
                +  | 
            |
| 170 | 
                + /**  | 
            |
| 171 | 
                + * Enables rotation via PhotoView internal functions.  | 
            |
| 172 | 
                + *  | 
            |
| 173 | 
                + * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360  | 
            |
| 174 | 
                + */  | 
            |
| 175 | 
                + void setRotationTo(float rotationDegree);  | 
            |
| 176 | 
                +  | 
            |
| 177 | 
                + /**  | 
            |
| 178 | 
                + * Enables rotation via PhotoView internal functions.  | 
            |
| 179 | 
                + *  | 
            |
| 180 | 
                + * @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360  | 
            |
| 181 | 
                + */  | 
            |
| 182 | 
                + void setRotationBy(float rotationDegree);  | 
            |
| 183 | 
                +  | 
            |
| 184 | 
                + /**  | 
            |
| 185 | 
                + * Changes the current scale to the specified value.  | 
            |
| 186 | 
                + *  | 
            |
| 187 | 
                + * @param scale - Value to scale to  | 
            |
| 188 | 
                + */  | 
            |
| 189 | 
                + void setScale(float scale);  | 
            |
| 190 | 
                +  | 
            |
| 191 | 
                + /**  | 
            |
| 192 | 
                + * Returns a callback listener to be invoked when the View is tapped with a single tap.  | 
            |
| 193 | 
                + *  | 
            |
| 194 | 
                + * @return PhotoViewAttacher.OnViewTapListener currently set, may be null  | 
            |
| 195 | 
                + */  | 
            |
| 196 | 
                + PhotoViewAttacher.OnViewTapListener getOnViewTapListener();  | 
            |
| 197 | 
                +  | 
            |
| 198 | 
                + /**  | 
            |
| 199 | 
                + * Register a callback to be invoked when the View is tapped with a single tap.  | 
            |
| 200 | 
                + *  | 
            |
| 201 | 
                + * @param listener - Listener to be registered.  | 
            |
| 202 | 
                + */  | 
            |
| 203 | 
                + void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);  | 
            |
| 204 | 
                +  | 
            |
| 205 | 
                + /**  | 
            |
| 206 | 
                + * Changes the current scale to the specified value.  | 
            |
| 207 | 
                + *  | 
            |
| 208 | 
                + * @param scale - Value to scale to  | 
            |
| 209 | 
                + * @param animate - Whether to animate the scale  | 
            |
| 210 | 
                + */  | 
            |
| 211 | 
                + void setScale(float scale, boolean animate);  | 
            |
| 212 | 
                +  | 
            |
| 213 | 
                + /**  | 
            |
| 214 | 
                + * Changes the current scale to the specified value, around the given focal point.  | 
            |
| 215 | 
                + *  | 
            |
| 216 | 
                + * @param scale - Value to scale to  | 
            |
| 217 | 
                + * @param focalX - X Focus Point  | 
            |
| 218 | 
                + * @param focalY - Y Focus Point  | 
            |
| 219 | 
                + * @param animate - Whether to animate the scale  | 
            |
| 220 | 
                + */  | 
            |
| 221 | 
                + void setScale(float scale, float focalX, float focalY, boolean animate);  | 
            |
| 222 | 
                +  | 
            |
| 223 | 
                + /**  | 
            |
| 224 | 
                + * Controls how the image should be resized or moved to match the size of the ImageView. Any  | 
            |
| 225 | 
                +     * scaling or panning will happen within the confines of this {@link
               | 
            |
| 226 | 
                + * ImageView.ScaleType}.  | 
            |
| 227 | 
                + *  | 
            |
| 228 | 
                + * @param scaleType - The desired scaling mode.  | 
            |
| 229 | 
                + */  | 
            |
| 230 | 
                + void setScaleType(ImageView.ScaleType scaleType);  | 
            |
| 231 | 
                +  | 
            |
| 232 | 
                + /**  | 
            |
| 233 | 
                + * Allows you to enable/disable the zoom functionality on the ImageView. When disable the  | 
            |
| 234 | 
                + * ImageView reverts to using the FIT_CENTER matrix.  | 
            |
| 235 | 
                + *  | 
            |
| 236 | 
                + * @param zoomable - Whether the zoom functionality is enabled.  | 
            |
| 237 | 
                + */  | 
            |
| 238 | 
                + void setZoomable(boolean zoomable);  | 
            |
| 239 | 
                +  | 
            |
| 240 | 
                + /**  | 
            |
| 241 | 
                + * Extracts currently visible area to Bitmap object, if there is no image loaded yet or the  | 
            |
| 242 | 
                +     * ImageView is already destroyed, returns {@code null}
               | 
            |
| 243 | 
                + *  | 
            |
| 244 | 
                + * @return currently visible area as bitmap or null  | 
            |
| 245 | 
                + */  | 
            |
| 246 | 
                + Bitmap getVisibleRectangleBitmap();  | 
            |
| 247 | 
                +  | 
            |
| 248 | 
                + /**  | 
            |
| 249 | 
                + * Allows to change zoom transition speed, default value is 200 (PhotoViewAttacher.DEFAULT_ZOOM_DURATION).  | 
            |
| 250 | 
                + * Will default to 200 if provided negative value  | 
            |
| 251 | 
                + *  | 
            |
| 252 | 
                + * @param milliseconds duration of zoom interpolation  | 
            |
| 253 | 
                + */  | 
            |
| 254 | 
                + void setZoomTransitionDuration(int milliseconds);  | 
            |
| 255 | 
                +  | 
            |
| 256 | 
                + /**  | 
            |
| 257 | 
                + * Will return instance of IPhotoView (eg. PhotoViewAttacher), can be used to provide better  | 
            |
| 258 | 
                + * integration  | 
            |
| 259 | 
                + *  | 
            |
| 260 | 
                + * @return IPhotoView implementation instance if available, null if not  | 
            |
| 261 | 
                + */  | 
            |
| 262 | 
                + IPhotoView getIPhotoViewImplementation();  | 
            |
| 263 | 
                +  | 
            |
| 264 | 
                + /**  | 
            |
| 265 | 
                + * Sets custom double tap listener, to intercept default given functions. To reset behavior to  | 
            |
| 266 | 
                + * default, you can just pass in "null" or public field of PhotoViewAttacher.defaultOnDoubleTapListener  | 
            |
| 267 | 
                + *  | 
            |
| 268 | 
                + * @param newOnDoubleTapListener custom OnDoubleTapListener to be set on ImageView  | 
            |
| 269 | 
                + */  | 
            |
| 270 | 
                + void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener);  | 
            |
| 271 | 
                +  | 
            |
| 272 | 
                + /**  | 
            |
| 273 | 
                + * Will report back about scale changes  | 
            |
| 274 | 
                + *  | 
            |
| 275 | 
                + * @param onScaleChangeListener OnScaleChangeListener instance  | 
            |
| 276 | 
                + */  | 
            |
| 277 | 
                + void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener);  | 
            |
| 278 | 
                +  | 
            |
| 279 | 
                + /**  | 
            |
| 280 | 
                + * Will report back about fling(single touch)  | 
            |
| 281 | 
                + *  | 
            |
| 282 | 
                + * @param onSingleFlingListener OnSingleFlingListener instance  | 
            |
| 283 | 
                + */  | 
            |
| 284 | 
                + void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener);  | 
            |
| 285 | 
                +}  | 
            
                @@ -0,0 +1,284 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.content.Context;  | 
            |
| 19 | 
                +import android.graphics.Bitmap;  | 
            |
| 20 | 
                +import android.graphics.Matrix;  | 
            |
| 21 | 
                +import android.graphics.RectF;  | 
            |
| 22 | 
                +import android.graphics.drawable.Drawable;  | 
            |
| 23 | 
                +import android.net.Uri;  | 
            |
| 24 | 
                +import android.util.AttributeSet;  | 
            |
| 25 | 
                +import android.view.GestureDetector;  | 
            |
| 26 | 
                +import android.widget.ImageView;  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                +import com.android.views.rotatephotoview.PhotoViewAttacher.OnMatrixChangedListener;  | 
            |
| 29 | 
                +import com.android.views.rotatephotoview.PhotoViewAttacher.OnPhotoTapListener;  | 
            |
| 30 | 
                +import com.android.views.rotatephotoview.PhotoViewAttacher.OnViewTapListener;  | 
            |
| 31 | 
                +  | 
            |
| 32 | 
                +public class PhotoView extends ImageView implements IPhotoView {
               | 
            |
| 33 | 
                +  | 
            |
| 34 | 
                + private PhotoViewAttacher mAttacher;  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + private ScaleType mPendingScaleType;  | 
            |
| 37 | 
                +  | 
            |
| 38 | 
                +    public PhotoView(Context context) {
               | 
            |
| 39 | 
                + this(context, null);  | 
            |
| 40 | 
                + }  | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                +    public PhotoView(Context context, AttributeSet attr) {
               | 
            |
| 43 | 
                + this(context, attr, 0);  | 
            |
| 44 | 
                + }  | 
            |
| 45 | 
                +  | 
            |
| 46 | 
                +    public PhotoView(Context context, AttributeSet attr, int defStyle) {
               | 
            |
| 47 | 
                + super(context, attr, defStyle);  | 
            |
| 48 | 
                + super.setScaleType(ScaleType.MATRIX);  | 
            |
| 49 | 
                + init();  | 
            |
| 50 | 
                + }  | 
            |
| 51 | 
                +  | 
            |
| 52 | 
                +    protected void init() {
               | 
            |
| 53 | 
                +        if (null == mAttacher || null == mAttacher.getImageView()) {
               | 
            |
| 54 | 
                + mAttacher = new PhotoViewAttacher(this);  | 
            |
| 55 | 
                + }  | 
            |
| 56 | 
                +  | 
            |
| 57 | 
                +        if (null != mPendingScaleType) {
               | 
            |
| 58 | 
                + setScaleType(mPendingScaleType);  | 
            |
| 59 | 
                + mPendingScaleType = null;  | 
            |
| 60 | 
                + }  | 
            |
| 61 | 
                + }  | 
            |
| 62 | 
                +  | 
            |
| 63 | 
                + @Override  | 
            |
| 64 | 
                +    public void setRotationTo(float rotationDegree) {
               | 
            |
| 65 | 
                + mAttacher.setRotationTo(rotationDegree);  | 
            |
| 66 | 
                + }  | 
            |
| 67 | 
                +  | 
            |
| 68 | 
                + @Override  | 
            |
| 69 | 
                +    public void setRotationBy(float rotationDegree) {
               | 
            |
| 70 | 
                + mAttacher.setRotationBy(rotationDegree);  | 
            |
| 71 | 
                + }  | 
            |
| 72 | 
                +  | 
            |
| 73 | 
                + @Override  | 
            |
| 74 | 
                +    public boolean canZoom() {
               | 
            |
| 75 | 
                + return mAttacher.canZoom();  | 
            |
| 76 | 
                + }  | 
            |
| 77 | 
                +  | 
            |
| 78 | 
                + @Override  | 
            |
| 79 | 
                +    public RectF getDisplayRect() {
               | 
            |
| 80 | 
                + return mAttacher.getDisplayRect();  | 
            |
| 81 | 
                + }  | 
            |
| 82 | 
                +  | 
            |
| 83 | 
                + @Override  | 
            |
| 84 | 
                +    public void getDisplayMatrix(Matrix matrix) {
               | 
            |
| 85 | 
                + mAttacher.getDisplayMatrix(matrix);  | 
            |
| 86 | 
                + }  | 
            |
| 87 | 
                +  | 
            |
| 88 | 
                + @Override  | 
            |
| 89 | 
                +    public boolean setDisplayMatrix(Matrix finalRectangle) {
               | 
            |
| 90 | 
                + return mAttacher.setDisplayMatrix(finalRectangle);  | 
            |
| 91 | 
                + }  | 
            |
| 92 | 
                +  | 
            |
| 93 | 
                + @Override  | 
            |
| 94 | 
                +    public float getMinimumScale() {
               | 
            |
| 95 | 
                + return mAttacher.getMinimumScale();  | 
            |
| 96 | 
                + }  | 
            |
| 97 | 
                +  | 
            |
| 98 | 
                + @Override  | 
            |
| 99 | 
                +    public float getMediumScale() {
               | 
            |
| 100 | 
                + return mAttacher.getMediumScale();  | 
            |
| 101 | 
                + }  | 
            |
| 102 | 
                +  | 
            |
| 103 | 
                + @Override  | 
            |
| 104 | 
                +    public float getMaximumScale() {
               | 
            |
| 105 | 
                + return mAttacher.getMaximumScale();  | 
            |
| 106 | 
                + }  | 
            |
| 107 | 
                +  | 
            |
| 108 | 
                + @Override  | 
            |
| 109 | 
                +    public float getScale() {
               | 
            |
| 110 | 
                + return mAttacher.getScale();  | 
            |
| 111 | 
                + }  | 
            |
| 112 | 
                +  | 
            |
| 113 | 
                + @Override  | 
            |
| 114 | 
                +    public ScaleType getScaleType() {
               | 
            |
| 115 | 
                + return mAttacher.getScaleType();  | 
            |
| 116 | 
                + }  | 
            |
| 117 | 
                +  | 
            |
| 118 | 
                + @Override  | 
            |
| 119 | 
                +    public Matrix getImageMatrix() {
               | 
            |
| 120 | 
                + return mAttacher.getImageMatrix();  | 
            |
| 121 | 
                + }  | 
            |
| 122 | 
                +  | 
            |
| 123 | 
                + @Override  | 
            |
| 124 | 
                +    public void setAllowParentInterceptOnEdge(boolean allow) {
               | 
            |
| 125 | 
                + mAttacher.setAllowParentInterceptOnEdge(allow);  | 
            |
| 126 | 
                + }  | 
            |
| 127 | 
                +  | 
            |
| 128 | 
                + @Override  | 
            |
| 129 | 
                +    public void setMinimumScale(float minimumScale) {
               | 
            |
| 130 | 
                + mAttacher.setMinimumScale(minimumScale);  | 
            |
| 131 | 
                + }  | 
            |
| 132 | 
                +  | 
            |
| 133 | 
                + @Override  | 
            |
| 134 | 
                +    public void setMediumScale(float mediumScale) {
               | 
            |
| 135 | 
                + mAttacher.setMediumScale(mediumScale);  | 
            |
| 136 | 
                + }  | 
            |
| 137 | 
                +  | 
            |
| 138 | 
                + @Override  | 
            |
| 139 | 
                +    public void setMaximumScale(float maximumScale) {
               | 
            |
| 140 | 
                + mAttacher.setMaximumScale(maximumScale);  | 
            |
| 141 | 
                + }  | 
            |
| 142 | 
                +  | 
            |
| 143 | 
                + @Override  | 
            |
| 144 | 
                +    public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
               | 
            |
| 145 | 
                + mAttacher.setScaleLevels(minimumScale, mediumScale, maximumScale);  | 
            |
| 146 | 
                + }  | 
            |
| 147 | 
                +  | 
            |
| 148 | 
                + @Override  | 
            |
| 149 | 
                + // setImageBitmap calls through to this method  | 
            |
| 150 | 
                +    public void setImageDrawable(Drawable drawable) {
               | 
            |
| 151 | 
                + super.setImageDrawable(drawable);  | 
            |
| 152 | 
                +        if (null != mAttacher) {
               | 
            |
| 153 | 
                + mAttacher.update();  | 
            |
| 154 | 
                + }  | 
            |
| 155 | 
                + }  | 
            |
| 156 | 
                +  | 
            |
| 157 | 
                + @Override  | 
            |
| 158 | 
                +    public void setImageResource(int resId) {
               | 
            |
| 159 | 
                + super.setImageResource(resId);  | 
            |
| 160 | 
                +        if (null != mAttacher) {
               | 
            |
| 161 | 
                + mAttacher.update();  | 
            |
| 162 | 
                + }  | 
            |
| 163 | 
                + }  | 
            |
| 164 | 
                +  | 
            |
| 165 | 
                + @Override  | 
            |
| 166 | 
                +    public void setImageURI(Uri uri) {
               | 
            |
| 167 | 
                + super.setImageURI(uri);  | 
            |
| 168 | 
                +        if (null != mAttacher) {
               | 
            |
| 169 | 
                + mAttacher.update();  | 
            |
| 170 | 
                + }  | 
            |
| 171 | 
                + }  | 
            |
| 172 | 
                +  | 
            |
| 173 | 
                + @Override  | 
            |
| 174 | 
                +    protected boolean setFrame(int l, int t, int r, int b) {
               | 
            |
| 175 | 
                + boolean changed = super.setFrame(l, t, r, b);  | 
            |
| 176 | 
                +        if (null != mAttacher) {
               | 
            |
| 177 | 
                + mAttacher.update();  | 
            |
| 178 | 
                + }  | 
            |
| 179 | 
                + return changed;  | 
            |
| 180 | 
                + }  | 
            |
| 181 | 
                +  | 
            |
| 182 | 
                + @Override  | 
            |
| 183 | 
                +    public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
               | 
            |
| 184 | 
                + mAttacher.setOnMatrixChangeListener(listener);  | 
            |
| 185 | 
                + }  | 
            |
| 186 | 
                +  | 
            |
| 187 | 
                + @Override  | 
            |
| 188 | 
                +    public void setOnLongClickListener(OnLongClickListener l) {
               | 
            |
| 189 | 
                + mAttacher.setOnLongClickListener(l);  | 
            |
| 190 | 
                + }  | 
            |
| 191 | 
                +  | 
            |
| 192 | 
                + @Override  | 
            |
| 193 | 
                +    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
               | 
            |
| 194 | 
                + mAttacher.setOnPhotoTapListener(listener);  | 
            |
| 195 | 
                + }  | 
            |
| 196 | 
                +  | 
            |
| 197 | 
                + @Override  | 
            |
| 198 | 
                +    public void setOnRotateListener(PhotoViewAttacher.OnRotateListener onRotateListener) {
               | 
            |
| 199 | 
                + mAttacher.setOnRotateListener(onRotateListener);  | 
            |
| 200 | 
                + }  | 
            |
| 201 | 
                +  | 
            |
| 202 | 
                + @Override  | 
            |
| 203 | 
                +    public OnViewTapListener getOnViewTapListener() {
               | 
            |
| 204 | 
                + return mAttacher.getOnViewTapListener();  | 
            |
| 205 | 
                + }  | 
            |
| 206 | 
                +  | 
            |
| 207 | 
                + @Override  | 
            |
| 208 | 
                +    public void setOnViewTapListener(OnViewTapListener listener) {
               | 
            |
| 209 | 
                + mAttacher.setOnViewTapListener(listener);  | 
            |
| 210 | 
                + }  | 
            |
| 211 | 
                +  | 
            |
| 212 | 
                + @Override  | 
            |
| 213 | 
                +    public void setScale(float scale) {
               | 
            |
| 214 | 
                + mAttacher.setScale(scale);  | 
            |
| 215 | 
                + }  | 
            |
| 216 | 
                +  | 
            |
| 217 | 
                + @Override  | 
            |
| 218 | 
                +    public void setScale(float scale, boolean animate) {
               | 
            |
| 219 | 
                + mAttacher.setScale(scale, animate);  | 
            |
| 220 | 
                + }  | 
            |
| 221 | 
                +  | 
            |
| 222 | 
                + @Override  | 
            |
| 223 | 
                +    public void setScale(float scale, float focalX, float focalY, boolean animate) {
               | 
            |
| 224 | 
                + mAttacher.setScale(scale, focalX, focalY, animate);  | 
            |
| 225 | 
                + }  | 
            |
| 226 | 
                +  | 
            |
| 227 | 
                + @Override  | 
            |
| 228 | 
                +    public void setScaleType(ScaleType scaleType) {
               | 
            |
| 229 | 
                +        if (null != mAttacher) {
               | 
            |
| 230 | 
                + mAttacher.setScaleType(scaleType);  | 
            |
| 231 | 
                +        } else {
               | 
            |
| 232 | 
                + mPendingScaleType = scaleType;  | 
            |
| 233 | 
                + }  | 
            |
| 234 | 
                + }  | 
            |
| 235 | 
                +  | 
            |
| 236 | 
                + @Override  | 
            |
| 237 | 
                +    public void setZoomable(boolean zoomable) {
               | 
            |
| 238 | 
                + mAttacher.setZoomable(zoomable);  | 
            |
| 239 | 
                + }  | 
            |
| 240 | 
                +  | 
            |
| 241 | 
                + @Override  | 
            |
| 242 | 
                +    public Bitmap getVisibleRectangleBitmap() {
               | 
            |
| 243 | 
                + return mAttacher.getVisibleRectangleBitmap();  | 
            |
| 244 | 
                + }  | 
            |
| 245 | 
                +  | 
            |
| 246 | 
                + @Override  | 
            |
| 247 | 
                +    public void setZoomTransitionDuration(int milliseconds) {
               | 
            |
| 248 | 
                + mAttacher.setZoomTransitionDuration(milliseconds);  | 
            |
| 249 | 
                + }  | 
            |
| 250 | 
                +  | 
            |
| 251 | 
                + @Override  | 
            |
| 252 | 
                +    public IPhotoView getIPhotoViewImplementation() {
               | 
            |
| 253 | 
                + return mAttacher;  | 
            |
| 254 | 
                + }  | 
            |
| 255 | 
                +  | 
            |
| 256 | 
                + @Override  | 
            |
| 257 | 
                +    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
               | 
            |
| 258 | 
                + mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);  | 
            |
| 259 | 
                + }  | 
            |
| 260 | 
                +  | 
            |
| 261 | 
                + @Override  | 
            |
| 262 | 
                +    public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) {
               | 
            |
| 263 | 
                + mAttacher.setOnScaleChangeListener(onScaleChangeListener);  | 
            |
| 264 | 
                + }  | 
            |
| 265 | 
                +  | 
            |
| 266 | 
                + @Override  | 
            |
| 267 | 
                +    public void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener) {
               | 
            |
| 268 | 
                + mAttacher.setOnSingleFlingListener(onSingleFlingListener);  | 
            |
| 269 | 
                + }  | 
            |
| 270 | 
                +  | 
            |
| 271 | 
                + @Override  | 
            |
| 272 | 
                +    protected void onDetachedFromWindow() {
               | 
            |
| 273 | 
                + mAttacher.cleanup();  | 
            |
| 274 | 
                + mAttacher = null;  | 
            |
| 275 | 
                + super.onDetachedFromWindow();  | 
            |
| 276 | 
                + }  | 
            |
| 277 | 
                +  | 
            |
| 278 | 
                + @Override  | 
            |
| 279 | 
                +    protected void onAttachedToWindow() {
               | 
            |
| 280 | 
                + init();  | 
            |
| 281 | 
                + super.onAttachedToWindow();  | 
            |
| 282 | 
                + }  | 
            |
| 283 | 
                +  | 
            |
| 284 | 
                +}  | 
            
                @@ -0,0 +1,1373 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.annotation.SuppressLint;  | 
            |
| 19 | 
                +import android.content.Context;  | 
            |
| 20 | 
                +import android.graphics.Bitmap;  | 
            |
| 21 | 
                +import android.graphics.Matrix;  | 
            |
| 22 | 
                +import android.graphics.Matrix.ScaleToFit;  | 
            |
| 23 | 
                +import android.graphics.RectF;  | 
            |
| 24 | 
                +import android.graphics.drawable.Drawable;  | 
            |
| 25 | 
                +import android.support.annotation.Nullable;  | 
            |
| 26 | 
                +import android.support.v4.view.MotionEventCompat;  | 
            |
| 27 | 
                +import android.util.Log;  | 
            |
| 28 | 
                +import android.view.GestureDetector;  | 
            |
| 29 | 
                +import android.view.MotionEvent;  | 
            |
| 30 | 
                +import android.view.View;  | 
            |
| 31 | 
                +import android.view.View.OnLongClickListener;  | 
            |
| 32 | 
                +import android.view.ViewParent;  | 
            |
| 33 | 
                +import android.view.ViewTreeObserver;  | 
            |
| 34 | 
                +import android.view.animation.AccelerateDecelerateInterpolator;  | 
            |
| 35 | 
                +import android.view.animation.Interpolator;  | 
            |
| 36 | 
                +import android.widget.ImageView;  | 
            |
| 37 | 
                +import android.widget.ImageView.ScaleType;  | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                +import java.lang.ref.WeakReference;  | 
            |
| 40 | 
                +  | 
            |
| 41 | 
                +import com.android.views.rotatephotoview.gestures.IRotateListener;  | 
            |
| 42 | 
                +import com.android.views.rotatephotoview.gestures.OnGestureListener;  | 
            |
| 43 | 
                +import com.android.views.rotatephotoview.gestures.RotateGestureDetector;  | 
            |
| 44 | 
                +import com.android.views.rotatephotoview.gestures.VersionedGestureDetector;  | 
            |
| 45 | 
                +import com.android.views.rotatephotoview.log.LogManager;  | 
            |
| 46 | 
                +import com.android.views.rotatephotoview.scrollerproxy.ScrollerProxy;  | 
            |
| 47 | 
                +  | 
            |
| 48 | 
                +import static android.view.MotionEvent.ACTION_CANCEL;  | 
            |
| 49 | 
                +import static android.view.MotionEvent.ACTION_DOWN;  | 
            |
| 50 | 
                +import static android.view.MotionEvent.ACTION_UP;  | 
            |
| 51 | 
                +  | 
            |
| 52 | 
                +public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,  | 
            |
| 53 | 
                + OnGestureListener,  | 
            |
| 54 | 
                +        ViewTreeObserver.OnGlobalLayoutListener {
               | 
            |
| 55 | 
                +  | 
            |
| 56 | 
                + private static final String LOG_TAG = "PhotoViewAttacher";  | 
            |
| 57 | 
                +  | 
            |
| 58 | 
                + // let debug flag be dynamic, but still Proguard can be used to remove from  | 
            |
| 59 | 
                + // release builds  | 
            |
| 60 | 
                + private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);  | 
            |
| 61 | 
                +  | 
            |
| 62 | 
                + private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();  | 
            |
| 63 | 
                + private int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;  | 
            |
| 64 | 
                +  | 
            |
| 65 | 
                + private static final int EDGE_NONE = -1;  | 
            |
| 66 | 
                + private static final int EDGE_LEFT = 0;  | 
            |
| 67 | 
                + private static final int EDGE_RIGHT = 1;  | 
            |
| 68 | 
                + private static final int EDGE_BOTH = 2;  | 
            |
| 69 | 
                +  | 
            |
| 70 | 
                + private static int SINGLE_TOUCH = 1;  | 
            |
| 71 | 
                +  | 
            |
| 72 | 
                + private float mMinScale = DEFAULT_MIN_SCALE;  | 
            |
| 73 | 
                + private float mMidScale = DEFAULT_MID_SCALE;  | 
            |
| 74 | 
                + private float mMaxScale = DEFAULT_MAX_SCALE;  | 
            |
| 75 | 
                +  | 
            |
| 76 | 
                + private boolean mAllowParentInterceptOnEdge = true;  | 
            |
| 77 | 
                + private boolean mBlockParentIntercept = false;  | 
            |
| 78 | 
                +  | 
            |
| 79 | 
                + private static void checkZoomLevels(float minZoom, float midZoom,  | 
            |
| 80 | 
                +                                        float maxZoom) {
               | 
            |
| 81 | 
                +        if (minZoom >= midZoom) {
               | 
            |
| 82 | 
                + throw new IllegalArgumentException(  | 
            |
| 83 | 
                + "Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");  | 
            |
| 84 | 
                +        } else if (midZoom >= maxZoom) {
               | 
            |
| 85 | 
                + throw new IllegalArgumentException(  | 
            |
| 86 | 
                + "Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");  | 
            |
| 87 | 
                + }  | 
            |
| 88 | 
                + }  | 
            |
| 89 | 
                +  | 
            |
| 90 | 
                + /**  | 
            |
| 91 | 
                + * @return true if the ImageView exists, and it's Drawable exists  | 
            |
| 92 | 
                + */  | 
            |
| 93 | 
                +    private static boolean hasDrawable(ImageView imageView) {
               | 
            |
| 94 | 
                + return null != imageView && null != imageView.getDrawable();  | 
            |
| 95 | 
                + }  | 
            |
| 96 | 
                +  | 
            |
| 97 | 
                + /**  | 
            |
| 98 | 
                + * @return true if the ScaleType is supported.  | 
            |
| 99 | 
                + */  | 
            |
| 100 | 
                +    private static boolean isSupportedScaleType(final ScaleType scaleType) {
               | 
            |
| 101 | 
                +        if (null == scaleType) {
               | 
            |
| 102 | 
                + return false;  | 
            |
| 103 | 
                + }  | 
            |
| 104 | 
                +  | 
            |
| 105 | 
                +        switch (scaleType) {
               | 
            |
| 106 | 
                + case MATRIX:  | 
            |
| 107 | 
                + throw new IllegalArgumentException(scaleType.name()  | 
            |
| 108 | 
                + + " is not supported in PhotoView");  | 
            |
| 109 | 
                +  | 
            |
| 110 | 
                + default:  | 
            |
| 111 | 
                + return true;  | 
            |
| 112 | 
                + }  | 
            |
| 113 | 
                + }  | 
            |
| 114 | 
                +  | 
            |
| 115 | 
                + /**  | 
            |
| 116 | 
                + * Set's the ImageView's ScaleType to Matrix.  | 
            |
| 117 | 
                + */  | 
            |
| 118 | 
                +    private static void setImageViewScaleTypeMatrix(ImageView imageView) {
               | 
            |
| 119 | 
                + /**  | 
            |
| 120 | 
                + * PhotoView sets it's own ScaleType to Matrix, then diverts all calls  | 
            |
| 121 | 
                + * setScaleType to this.setScaleType automatically.  | 
            |
| 122 | 
                + */  | 
            |
| 123 | 
                +        if (null != imageView && !(imageView instanceof IPhotoView)) {
               | 
            |
| 124 | 
                +            if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
               | 
            |
| 125 | 
                + imageView.setScaleType(ScaleType.MATRIX);  | 
            |
| 126 | 
                + }  | 
            |
| 127 | 
                + }  | 
            |
| 128 | 
                + }  | 
            |
| 129 | 
                +  | 
            |
| 130 | 
                + private WeakReference<ImageView> mImageView;  | 
            |
| 131 | 
                +  | 
            |
| 132 | 
                + // Gesture Detectors  | 
            |
| 133 | 
                + private GestureDetector mGestureDetector;  | 
            |
| 134 | 
                + private com.android.views.rotatephotoview.gestures.GestureDetector mScaleDragDetector;  | 
            |
| 135 | 
                + private RotateGestureDetector mRotateGestureDetector;  | 
            |
| 136 | 
                +  | 
            |
| 137 | 
                +  | 
            |
| 138 | 
                + // These are set so we don't keep allocating them on the heap  | 
            |
| 139 | 
                + private final Matrix mBaseMatrix = new Matrix();  | 
            |
| 140 | 
                + private final Matrix mDrawMatrix = new Matrix();  | 
            |
| 141 | 
                + private final Matrix mSuppMatrix = new Matrix();  | 
            |
| 142 | 
                + private final RectF mDisplayRect = new RectF();  | 
            |
| 143 | 
                + private final float[] mMatrixValues = new float[9];  | 
            |
| 144 | 
                +  | 
            |
| 145 | 
                + // Listeners  | 
            |
| 146 | 
                + private OnMatrixChangedListener mMatrixChangeListener;  | 
            |
| 147 | 
                + private OnPhotoTapListener mPhotoTapListener;  | 
            |
| 148 | 
                + private OnViewTapListener mViewTapListener;  | 
            |
| 149 | 
                + private OnLongClickListener mLongClickListener;  | 
            |
| 150 | 
                + private OnScaleChangeListener mScaleChangeListener;  | 
            |
| 151 | 
                + private OnRotateListener mOnRotateListener;  | 
            |
| 152 | 
                + private OnSingleFlingListener mSingleFlingListener;  | 
            |
| 153 | 
                +  | 
            |
| 154 | 
                + private int mIvTop, mIvRight, mIvBottom, mIvLeft;  | 
            |
| 155 | 
                + private FlingRunnable mCurrentFlingRunnable;  | 
            |
| 156 | 
                + private int mScrollEdge = EDGE_BOTH;  | 
            |
| 157 | 
                + private float mBaseRotation;  | 
            |
| 158 | 
                +  | 
            |
| 159 | 
                + private boolean mZoomEnabled;  | 
            |
| 160 | 
                + private ScaleType mScaleType = ScaleType.FIT_CENTER;  | 
            |
| 161 | 
                + //create by ChenSiLiang  | 
            |
| 162 | 
                + private boolean mIsEnableRotate;  | 
            |
| 163 | 
                + private boolean mIsToRightAngle;  | 
            |
| 164 | 
                + private boolean mIsToRighting;  | 
            |
| 165 | 
                + private RightAngleRunnable mRightAngleRunnable;  | 
            |
| 166 | 
                +  | 
            |
| 167 | 
                +    public PhotoViewAttacher(ImageView imageView) {
               | 
            |
| 168 | 
                + this(imageView, true);  | 
            |
| 169 | 
                + }  | 
            |
| 170 | 
                +  | 
            |
| 171 | 
                +    public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
               | 
            |
| 172 | 
                + mImageView = new WeakReference<>(imageView);  | 
            |
| 173 | 
                +  | 
            |
| 174 | 
                + imageView.setDrawingCacheEnabled(true);  | 
            |
| 175 | 
                + imageView.setOnTouchListener(this);  | 
            |
| 176 | 
                +  | 
            |
| 177 | 
                + ViewTreeObserver observer = imageView.getViewTreeObserver();  | 
            |
| 178 | 
                + if (null != observer)  | 
            |
| 179 | 
                + observer.addOnGlobalLayoutListener(this);  | 
            |
| 180 | 
                +  | 
            |
| 181 | 
                + // Make sure we using MATRIX Scale Type  | 
            |
| 182 | 
                + setImageViewScaleTypeMatrix(imageView);  | 
            |
| 183 | 
                +  | 
            |
| 184 | 
                +        if (imageView.isInEditMode()) {
               | 
            |
| 185 | 
                + return;  | 
            |
| 186 | 
                + }  | 
            |
| 187 | 
                + // Create Gesture Detectors...  | 
            |
| 188 | 
                + mScaleDragDetector = VersionedGestureDetector.newInstance(  | 
            |
| 189 | 
                + imageView.getContext(), this);  | 
            |
| 190 | 
                +  | 
            |
| 191 | 
                + mGestureDetector = new GestureDetector(imageView.getContext(),  | 
            |
| 192 | 
                +                new GestureDetector.SimpleOnGestureListener() {
               | 
            |
| 193 | 
                +  | 
            |
| 194 | 
                + // forward long click listener  | 
            |
| 195 | 
                + @Override  | 
            |
| 196 | 
                +                    public void onLongPress(MotionEvent e) {
               | 
            |
| 197 | 
                +                        if (null != mLongClickListener) {
               | 
            |
| 198 | 
                + mLongClickListener.onLongClick(getImageView());  | 
            |
| 199 | 
                + }  | 
            |
| 200 | 
                + }  | 
            |
| 201 | 
                +  | 
            |
| 202 | 
                + @Override  | 
            |
| 203 | 
                + public boolean onFling(MotionEvent e1, MotionEvent e2,  | 
            |
| 204 | 
                +                                           float velocityX, float velocityY) {
               | 
            |
| 205 | 
                +                        if (mSingleFlingListener != null) {
               | 
            |
| 206 | 
                +                            if (getScale() > DEFAULT_MIN_SCALE) {
               | 
            |
| 207 | 
                + return false;  | 
            |
| 208 | 
                + }  | 
            |
| 209 | 
                +  | 
            |
| 210 | 
                + if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH  | 
            |
| 211 | 
                +                                    || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) {
               | 
            |
| 212 | 
                + return false;  | 
            |
| 213 | 
                + }  | 
            |
| 214 | 
                +  | 
            |
| 215 | 
                + return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);  | 
            |
| 216 | 
                + }  | 
            |
| 217 | 
                + return false;  | 
            |
| 218 | 
                + }  | 
            |
| 219 | 
                + });  | 
            |
| 220 | 
                + //modify by ChenSiLiang  | 
            |
| 221 | 
                + setRotateGestureDetector();  | 
            |
| 222 | 
                +  | 
            |
| 223 | 
                + mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));  | 
            |
| 224 | 
                + mBaseRotation = 0.0f;  | 
            |
| 225 | 
                +  | 
            |
| 226 | 
                + // Finally, update the UI so that we're zoomable  | 
            |
| 227 | 
                + setZoomable(zoomable);  | 
            |
| 228 | 
                + }  | 
            |
| 229 | 
                +  | 
            |
| 230 | 
                + /**  | 
            |
| 231 | 
                + * set rotate  | 
            |
| 232 | 
                + * Modify by ChenSL on 2015 / 9 / 16.  | 
            |
| 233 | 
                + */  | 
            |
| 234 | 
                +    private void setRotateGestureDetector() {
               | 
            |
| 235 | 
                +        if (mRotateGestureDetector == null) {
               | 
            |
| 236 | 
                + mRotateGestureDetector = new RotateGestureDetector();  | 
            |
| 237 | 
                +            mRotateGestureDetector.setRotateListener(new IRotateListener() {
               | 
            |
| 238 | 
                + @Override  | 
            |
| 239 | 
                +                public void rotate(int degree, int pivotX, int pivotY) {
               | 
            |
| 240 | 
                +                    if (mRightAngleRunnable != null && mIsToRighting) {
               | 
            |
| 241 | 
                + getImageView().removeCallbacks(mRightAngleRunnable);  | 
            |
| 242 | 
                + }  | 
            |
| 243 | 
                + mSuppMatrix.postRotate(degree, pivotX, pivotY);  | 
            |
| 244 | 
                +                    if (mOnRotateListener != null) {
               | 
            |
| 245 | 
                + mOnRotateListener.onRotate(degree);  | 
            |
| 246 | 
                + }  | 
            |
| 247 | 
                + //Post the rotation to the image  | 
            |
| 248 | 
                + checkAndDisplayMatrix();  | 
            |
| 249 | 
                + }  | 
            |
| 250 | 
                +  | 
            |
| 251 | 
                + @Override  | 
            |
| 252 | 
                +                public void upRotate(int pivotX, int pivotY) {
               | 
            |
| 253 | 
                +                    if (mIsToRightAngle) {
               | 
            |
| 254 | 
                + float[] v = new float[9];  | 
            |
| 255 | 
                + mSuppMatrix.getValues(v);  | 
            |
| 256 | 
                + // calculate the degree of rotation  | 
            |
| 257 | 
                + int angle = (int) (Math.round(Math.atan2(v[Matrix.MSKEW_X], v[Matrix.MSCALE_X]) * (180 / Math.PI)));  | 
            |
| 258 | 
                +                        if (angle <= 0) {
               | 
            |
| 259 | 
                + angle = -angle;  | 
            |
| 260 | 
                +                        } else {
               | 
            |
| 261 | 
                + angle = 360 - angle;  | 
            |
| 262 | 
                + }  | 
            |
| 263 | 
                + mRightAngleRunnable = new RightAngleRunnable(angle, pivotX, pivotY);  | 
            |
| 264 | 
                + getImageView().post(mRightAngleRunnable);  | 
            |
| 265 | 
                + }  | 
            |
| 266 | 
                + }  | 
            |
| 267 | 
                + });  | 
            |
| 268 | 
                + }  | 
            |
| 269 | 
                + }  | 
            |
| 270 | 
                +  | 
            |
| 271 | 
                + @Override  | 
            |
| 272 | 
                +    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
               | 
            |
| 273 | 
                +        if (newOnDoubleTapListener != null) {
               | 
            |
| 274 | 
                + this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);  | 
            |
| 275 | 
                +        } else {
               | 
            |
| 276 | 
                + this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));  | 
            |
| 277 | 
                + }  | 
            |
| 278 | 
                + }  | 
            |
| 279 | 
                +  | 
            |
| 280 | 
                + @Override  | 
            |
| 281 | 
                +    public void setOnScaleChangeListener(OnScaleChangeListener onScaleChangeListener) {
               | 
            |
| 282 | 
                + this.mScaleChangeListener = onScaleChangeListener;  | 
            |
| 283 | 
                + }  | 
            |
| 284 | 
                +  | 
            |
| 285 | 
                + @Override  | 
            |
| 286 | 
                +    public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
               | 
            |
| 287 | 
                + this.mSingleFlingListener = onSingleFlingListener;  | 
            |
| 288 | 
                + }  | 
            |
| 289 | 
                +  | 
            |
| 290 | 
                + /**  | 
            |
| 291 | 
                + * set Rotatable  | 
            |
| 292 | 
                + * Created by ChenSL on 2015/9/16.  | 
            |
| 293 | 
                + *  | 
            |
| 294 | 
                + * @param isRotatable true,enbale  | 
            |
| 295 | 
                + */  | 
            |
| 296 | 
                +    public void setRotatable(boolean isRotatable) {
               | 
            |
| 297 | 
                + mIsEnableRotate = isRotatable;  | 
            |
| 298 | 
                + }  | 
            |
| 299 | 
                +  | 
            |
| 300 | 
                + /**  | 
            |
| 301 | 
                + * set the boolean to the rotation to right angle(0,90,180,270 degree) when one finger up from the screen  | 
            |
| 302 | 
                + * Created by ChenSL on 2015/9/16.  | 
            |
| 303 | 
                + *  | 
            |
| 304 | 
                + * @param toRightAngle true,recover to right angle when one finger lift;false,otherwise.  | 
            |
| 305 | 
                + */  | 
            |
| 306 | 
                +    public void setToRightAngle(boolean toRightAngle) {
               | 
            |
| 307 | 
                + mIsToRightAngle = toRightAngle;  | 
            |
| 308 | 
                + }  | 
            |
| 309 | 
                +  | 
            |
| 310 | 
                + @Override  | 
            |
| 311 | 
                +    public boolean canZoom() {
               | 
            |
| 312 | 
                + return mZoomEnabled;  | 
            |
| 313 | 
                + }  | 
            |
| 314 | 
                +  | 
            |
| 315 | 
                + /**  | 
            |
| 316 | 
                + * Clean-up the resources attached to this object. This needs to be called when the ImageView is  | 
            |
| 317 | 
                +     * no longer used. A good example is from {@link View#onDetachedFromWindow()} or
               | 
            |
| 318 | 
                +     * from {@link android.app.Activity#onDestroy()}. This is automatically called if you are using
               | 
            |
| 319 | 
                +     * {@link com.android.views.rotatephotoview.PhotoView}.
               | 
            |
| 320 | 
                + */  | 
            |
| 321 | 
                +    @SuppressWarnings("deprecation")
               | 
            |
| 322 | 
                +    public void cleanup() {
               | 
            |
| 323 | 
                +        if (null == mImageView) {
               | 
            |
| 324 | 
                + return; // cleanup already done  | 
            |
| 325 | 
                + }  | 
            |
| 326 | 
                +  | 
            |
| 327 | 
                + final ImageView imageView = mImageView.get();  | 
            |
| 328 | 
                +  | 
            |
| 329 | 
                +        if (null != imageView) {
               | 
            |
| 330 | 
                + // Remove this as a global layout listener  | 
            |
| 331 | 
                + ViewTreeObserver observer = imageView.getViewTreeObserver();  | 
            |
| 332 | 
                +            if (null != observer && observer.isAlive()) {
               | 
            |
| 333 | 
                + observer.removeGlobalOnLayoutListener(this);  | 
            |
| 334 | 
                + }  | 
            |
| 335 | 
                +  | 
            |
| 336 | 
                + // Remove the ImageView's reference to this  | 
            |
| 337 | 
                + imageView.setOnTouchListener(null);  | 
            |
| 338 | 
                +  | 
            |
| 339 | 
                + // make sure a pending fling runnable won't be run  | 
            |
| 340 | 
                + cancelFling();  | 
            |
| 341 | 
                + }  | 
            |
| 342 | 
                +  | 
            |
| 343 | 
                +        if (null != mGestureDetector) {
               | 
            |
| 344 | 
                + mGestureDetector.setOnDoubleTapListener(null);  | 
            |
| 345 | 
                + }  | 
            |
| 346 | 
                +        if (null != mRotateGestureDetector) {
               | 
            |
| 347 | 
                + mRotateGestureDetector.setRotateListener(null);  | 
            |
| 348 | 
                + }  | 
            |
| 349 | 
                +  | 
            |
| 350 | 
                + // Clear listeners too  | 
            |
| 351 | 
                + mMatrixChangeListener = null;  | 
            |
| 352 | 
                + mPhotoTapListener = null;  | 
            |
| 353 | 
                + mViewTapListener = null;  | 
            |
| 354 | 
                + mOnRotateListener = null;  | 
            |
| 355 | 
                +  | 
            |
| 356 | 
                + // Finally, clear ImageView  | 
            |
| 357 | 
                + mImageView = null;  | 
            |
| 358 | 
                + }  | 
            |
| 359 | 
                +  | 
            |
| 360 | 
                + @Override  | 
            |
| 361 | 
                +    public RectF getDisplayRect() {
               | 
            |
| 362 | 
                + checkMatrixBounds();  | 
            |
| 363 | 
                + return getDisplayRect(getDrawMatrix());  | 
            |
| 364 | 
                + }  | 
            |
| 365 | 
                +  | 
            |
| 366 | 
                + @Override  | 
            |
| 367 | 
                +    public boolean setDisplayMatrix(Matrix finalMatrix) {
               | 
            |
| 368 | 
                +        if (finalMatrix == null) {
               | 
            |
| 369 | 
                +            throw new IllegalArgumentException("Matrix cannot be null");
               | 
            |
| 370 | 
                + }  | 
            |
| 371 | 
                +  | 
            |
| 372 | 
                + ImageView imageView = getImageView();  | 
            |
| 373 | 
                +        if (null == imageView) {
               | 
            |
| 374 | 
                + return false;  | 
            |
| 375 | 
                + }  | 
            |
| 376 | 
                +  | 
            |
| 377 | 
                +        if (null == imageView.getDrawable()) {
               | 
            |
| 378 | 
                + return false;  | 
            |
| 379 | 
                + }  | 
            |
| 380 | 
                +  | 
            |
| 381 | 
                + mSuppMatrix.set(finalMatrix);  | 
            |
| 382 | 
                + setImageViewMatrix(getDrawMatrix());  | 
            |
| 383 | 
                + checkMatrixBounds();  | 
            |
| 384 | 
                +  | 
            |
| 385 | 
                + return true;  | 
            |
| 386 | 
                + }  | 
            |
| 387 | 
                +  | 
            |
| 388 | 
                +    public void setBaseRotation(final float degrees) {
               | 
            |
| 389 | 
                + mBaseRotation = degrees % 360;  | 
            |
| 390 | 
                + update();  | 
            |
| 391 | 
                + setRotationBy(mBaseRotation);  | 
            |
| 392 | 
                + checkAndDisplayMatrix();  | 
            |
| 393 | 
                + }  | 
            |
| 394 | 
                +  | 
            |
| 395 | 
                + @Override  | 
            |
| 396 | 
                +    public void setRotationTo(float degrees) {
               | 
            |
| 397 | 
                + mSuppMatrix.setRotate(degrees % 360);  | 
            |
| 398 | 
                + checkAndDisplayMatrix();  | 
            |
| 399 | 
                + }  | 
            |
| 400 | 
                +  | 
            |
| 401 | 
                + @Override  | 
            |
| 402 | 
                +    public void setRotationBy(float degrees) {
               | 
            |
| 403 | 
                + mSuppMatrix.postRotate(degrees % 360);  | 
            |
| 404 | 
                + checkAndDisplayMatrix();  | 
            |
| 405 | 
                + }  | 
            |
| 406 | 
                +  | 
            |
| 407 | 
                +    public ImageView getImageView() {
               | 
            |
| 408 | 
                + ImageView imageView = null;  | 
            |
| 409 | 
                +  | 
            |
| 410 | 
                +        if (null != mImageView) {
               | 
            |
| 411 | 
                + imageView = mImageView.get();  | 
            |
| 412 | 
                + }  | 
            |
| 413 | 
                +  | 
            |
| 414 | 
                + // If we don't have an ImageView, call cleanup()  | 
            |
| 415 | 
                +        if (null == imageView) {
               | 
            |
| 416 | 
                + cleanup();  | 
            |
| 417 | 
                + LogManager.getLogger().i(LOG_TAG,  | 
            |
| 418 | 
                + "ImageView no longer exists. You should not use this PhotoViewAttacher any more.");  | 
            |
| 419 | 
                + }  | 
            |
| 420 | 
                +  | 
            |
| 421 | 
                + return imageView;  | 
            |
| 422 | 
                + }  | 
            |
| 423 | 
                +  | 
            |
| 424 | 
                + @Override  | 
            |
| 425 | 
                +    public float getMinimumScale() {
               | 
            |
| 426 | 
                + return mMinScale;  | 
            |
| 427 | 
                + }  | 
            |
| 428 | 
                +  | 
            |
| 429 | 
                + @Override  | 
            |
| 430 | 
                +    public float getMediumScale() {
               | 
            |
| 431 | 
                + return mMidScale;  | 
            |
| 432 | 
                + }  | 
            |
| 433 | 
                +  | 
            |
| 434 | 
                + @Override  | 
            |
| 435 | 
                +    public float getMaximumScale() {
               | 
            |
| 436 | 
                + return mMaxScale;  | 
            |
| 437 | 
                + }  | 
            |
| 438 | 
                +  | 
            |
| 439 | 
                + @Override  | 
            |
| 440 | 
                +    public float getScale() {
               | 
            |
| 441 | 
                + return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));  | 
            |
| 442 | 
                + }  | 
            |
| 443 | 
                +  | 
            |
| 444 | 
                + @Override  | 
            |
| 445 | 
                +    public ScaleType getScaleType() {
               | 
            |
| 446 | 
                + return mScaleType;  | 
            |
| 447 | 
                + }  | 
            |
| 448 | 
                +  | 
            |
| 449 | 
                + @Override  | 
            |
| 450 | 
                +    public void onDrag(float dx, float dy) {
               | 
            |
| 451 | 
                +        if (mScaleDragDetector.isScaling()) {
               | 
            |
| 452 | 
                + return; // Do not drag if we are already scaling  | 
            |
| 453 | 
                + }  | 
            |
| 454 | 
                +  | 
            |
| 455 | 
                +        if (DEBUG) {
               | 
            |
| 456 | 
                + LogManager.getLogger().d(LOG_TAG,  | 
            |
| 457 | 
                +                    String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
               | 
            |
| 458 | 
                + }  | 
            |
| 459 | 
                +  | 
            |
| 460 | 
                + ImageView imageView = getImageView();  | 
            |
| 461 | 
                + mSuppMatrix.postTranslate(dx, dy);  | 
            |
| 462 | 
                + checkAndDisplayMatrix();  | 
            |
| 463 | 
                +  | 
            |
| 464 | 
                + /**  | 
            |
| 465 | 
                + * Here we decide whether to let the ImageView's parent to start taking  | 
            |
| 466 | 
                + * over the touch event.  | 
            |
| 467 | 
                + *  | 
            |
| 468 | 
                + * First we check whether this function is enabled. We never want the  | 
            |
| 469 | 
                + * parent to take over if we're scaling. We then check the edge we're  | 
            |
| 470 | 
                + * on, and the direction of the scroll (i.e. if we're pulling against  | 
            |
| 471 | 
                + * the edge, aka 'overscrolling', let the parent take over).  | 
            |
| 472 | 
                + */  | 
            |
| 473 | 
                + ViewParent parent = imageView.getParent();  | 
            |
| 474 | 
                +        if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
               | 
            |
| 475 | 
                + if (mScrollEdge == EDGE_BOTH  | 
            |
| 476 | 
                + || (mScrollEdge == EDGE_LEFT && dx >= 1f)  | 
            |
| 477 | 
                +                    || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
               | 
            |
| 478 | 
                +                if (null != parent) {
               | 
            |
| 479 | 
                + parent.requestDisallowInterceptTouchEvent(false);  | 
            |
| 480 | 
                + }  | 
            |
| 481 | 
                + }  | 
            |
| 482 | 
                +        } else {
               | 
            |
| 483 | 
                +            if (null != parent) {
               | 
            |
| 484 | 
                + parent.requestDisallowInterceptTouchEvent(true);  | 
            |
| 485 | 
                + }  | 
            |
| 486 | 
                + }  | 
            |
| 487 | 
                + }  | 
            |
| 488 | 
                +  | 
            |
| 489 | 
                + @Override  | 
            |
| 490 | 
                + public void onFling(float startX, float startY, float velocityX,  | 
            |
| 491 | 
                +                        float velocityY) {
               | 
            |
| 492 | 
                +        if (DEBUG) {
               | 
            |
| 493 | 
                + LogManager.getLogger().d(  | 
            |
| 494 | 
                + LOG_TAG,  | 
            |
| 495 | 
                + "onFling. sX: " + startX + " sY: " + startY + " Vx: "  | 
            |
| 496 | 
                + + velocityX + " Vy: " + velocityY);  | 
            |
| 497 | 
                + }  | 
            |
| 498 | 
                + ImageView imageView = getImageView();  | 
            |
| 499 | 
                + mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());  | 
            |
| 500 | 
                + mCurrentFlingRunnable.fling(getImageViewWidth(imageView),  | 
            |
| 501 | 
                + getImageViewHeight(imageView), (int) velocityX, (int) velocityY);  | 
            |
| 502 | 
                + imageView.post(mCurrentFlingRunnable);  | 
            |
| 503 | 
                + }  | 
            |
| 504 | 
                +  | 
            |
| 505 | 
                + @Override  | 
            |
| 506 | 
                +    public void onGlobalLayout() {
               | 
            |
| 507 | 
                + ImageView imageView = getImageView();  | 
            |
| 508 | 
                +  | 
            |
| 509 | 
                +        if (null != imageView) {
               | 
            |
| 510 | 
                +            if (mZoomEnabled) {
               | 
            |
| 511 | 
                + final int top = imageView.getTop();  | 
            |
| 512 | 
                + final int right = imageView.getRight();  | 
            |
| 513 | 
                + final int bottom = imageView.getBottom();  | 
            |
| 514 | 
                + final int left = imageView.getLeft();  | 
            |
| 515 | 
                +  | 
            |
| 516 | 
                + /**  | 
            |
| 517 | 
                + * We need to check whether the ImageView's bounds have changed.  | 
            |
| 518 | 
                + * This would be easier if we targeted API 11+ as we could just use  | 
            |
| 519 | 
                + * View.OnLayoutChangeListener. Instead we have to replicate the  | 
            |
| 520 | 
                + * work, keeping track of the ImageView's bounds and then checking  | 
            |
| 521 | 
                + * if the values change.  | 
            |
| 522 | 
                + */  | 
            |
| 523 | 
                + if (top != mIvTop || bottom != mIvBottom || left != mIvLeft  | 
            |
| 524 | 
                +                        || right != mIvRight) {
               | 
            |
| 525 | 
                + // Update our base matrix, as the bounds have changed  | 
            |
| 526 | 
                + updateBaseMatrix(imageView.getDrawable());  | 
            |
| 527 | 
                +  | 
            |
| 528 | 
                + // Update values as something has changed  | 
            |
| 529 | 
                + mIvTop = top;  | 
            |
| 530 | 
                + mIvRight = right;  | 
            |
| 531 | 
                + mIvBottom = bottom;  | 
            |
| 532 | 
                + mIvLeft = left;  | 
            |
| 533 | 
                + }  | 
            |
| 534 | 
                +            } else {
               | 
            |
| 535 | 
                + updateBaseMatrix(imageView.getDrawable());  | 
            |
| 536 | 
                + }  | 
            |
| 537 | 
                + }  | 
            |
| 538 | 
                + }  | 
            |
| 539 | 
                +  | 
            |
| 540 | 
                + @Override  | 
            |
| 541 | 
                +    public void onScale(float scaleFactor, float focusX, float focusY) {
               | 
            |
| 542 | 
                +        if (DEBUG) {
               | 
            |
| 543 | 
                + LogManager.getLogger().d(  | 
            |
| 544 | 
                + LOG_TAG,  | 
            |
| 545 | 
                +                    String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f",
               | 
            |
| 546 | 
                + scaleFactor, focusX, focusY));  | 
            |
| 547 | 
                + }  | 
            |
| 548 | 
                +  | 
            |
| 549 | 
                +        if ((getScale() < mMaxScale || scaleFactor < 1f) && (getScale() > mMinScale || scaleFactor > 1f)) {
               | 
            |
| 550 | 
                +            if (null != mScaleChangeListener) {
               | 
            |
| 551 | 
                + mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);  | 
            |
| 552 | 
                + }  | 
            |
| 553 | 
                + mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);  | 
            |
| 554 | 
                + checkAndDisplayMatrix();  | 
            |
| 555 | 
                + }  | 
            |
| 556 | 
                + }  | 
            |
| 557 | 
                +  | 
            |
| 558 | 
                +    @SuppressLint("ClickableViewAccessibility")
               | 
            |
| 559 | 
                + @Override  | 
            |
| 560 | 
                +    public boolean onTouch(View v, MotionEvent ev) {
               | 
            |
| 561 | 
                + boolean handled = false;  | 
            |
| 562 | 
                +  | 
            |
| 563 | 
                +        if (mZoomEnabled && hasDrawable((ImageView) v)) {
               | 
            |
| 564 | 
                + ViewParent parent = v.getParent();  | 
            |
| 565 | 
                +            switch (ev.getAction()) {
               | 
            |
| 566 | 
                + case ACTION_DOWN:  | 
            |
| 567 | 
                + // First, disable the Parent from intercepting the touch  | 
            |
| 568 | 
                + // event  | 
            |
| 569 | 
                +                    if (null != parent) {
               | 
            |
| 570 | 
                + parent.requestDisallowInterceptTouchEvent(true);  | 
            |
| 571 | 
                +                    } else {
               | 
            |
| 572 | 
                + LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");  | 
            |
| 573 | 
                + }  | 
            |
| 574 | 
                +  | 
            |
| 575 | 
                + // If we're flinging, and the user presses down, cancel  | 
            |
| 576 | 
                + // fling  | 
            |
| 577 | 
                + cancelFling();  | 
            |
| 578 | 
                + break;  | 
            |
| 579 | 
                +  | 
            |
| 580 | 
                + case ACTION_CANCEL:  | 
            |
| 581 | 
                + case ACTION_UP:  | 
            |
| 582 | 
                + // If the user has zoomed less than min scale, zoom back  | 
            |
| 583 | 
                + // to min scale  | 
            |
| 584 | 
                +                    if (getScale() < mMinScale) {
               | 
            |
| 585 | 
                + RectF rect = getDisplayRect();  | 
            |
| 586 | 
                +                        if (null != rect) {
               | 
            |
| 587 | 
                + v.post(new AnimatedZoomRunnable(getScale(), mMinScale,  | 
            |
| 588 | 
                + rect.centerX(), rect.centerY()));  | 
            |
| 589 | 
                + handled = true;  | 
            |
| 590 | 
                + }  | 
            |
| 591 | 
                + }  | 
            |
| 592 | 
                + break;  | 
            |
| 593 | 
                + }  | 
            |
| 594 | 
                +  | 
            |
| 595 | 
                + //detect the rotation  | 
            |
| 596 | 
                +            if (mIsEnableRotate && ev.getPointerCount() == 2) {
               | 
            |
| 597 | 
                + mRotateGestureDetector.onTouchEvent(ev);  | 
            |
| 598 | 
                + }  | 
            |
| 599 | 
                + boolean wasRotate = mRotateGestureDetector.isRotating();  | 
            |
| 600 | 
                +  | 
            |
| 601 | 
                + // Try the Scale/Drag detector  | 
            |
| 602 | 
                +            if (null != mScaleDragDetector) {
               | 
            |
| 603 | 
                + boolean wasScaling = mScaleDragDetector.isScaling();  | 
            |
| 604 | 
                + boolean wasDragging = mScaleDragDetector.isDragging();  | 
            |
| 605 | 
                +  | 
            |
| 606 | 
                + handled = mScaleDragDetector.onTouchEvent(ev);  | 
            |
| 607 | 
                +  | 
            |
| 608 | 
                + boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();  | 
            |
| 609 | 
                + boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();  | 
            |
| 610 | 
                + boolean didnttRotate = !wasRotate && !mRotateGestureDetector.isRotating();  | 
            |
| 611 | 
                +  | 
            |
| 612 | 
                + mBlockParentIntercept = didntScale && didntDrag && didnttRotate;  | 
            |
| 613 | 
                + }  | 
            |
| 614 | 
                +  | 
            |
| 615 | 
                + // Check to see if the user double tapped  | 
            |
| 616 | 
                +            if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
               | 
            |
| 617 | 
                + handled = true;  | 
            |
| 618 | 
                + }  | 
            |
| 619 | 
                +  | 
            |
| 620 | 
                + }  | 
            |
| 621 | 
                +  | 
            |
| 622 | 
                + return handled;  | 
            |
| 623 | 
                + }  | 
            |
| 624 | 
                +  | 
            |
| 625 | 
                + @Override  | 
            |
| 626 | 
                +    public void setAllowParentInterceptOnEdge(boolean allow) {
               | 
            |
| 627 | 
                + mAllowParentInterceptOnEdge = allow;  | 
            |
| 628 | 
                + }  | 
            |
| 629 | 
                +  | 
            |
| 630 | 
                + @Override  | 
            |
| 631 | 
                +    public void setMinimumScale(float minimumScale) {
               | 
            |
| 632 | 
                + checkZoomLevels(minimumScale, mMidScale, mMaxScale);  | 
            |
| 633 | 
                + mMinScale = minimumScale;  | 
            |
| 634 | 
                + }  | 
            |
| 635 | 
                +  | 
            |
| 636 | 
                + @Override  | 
            |
| 637 | 
                +    public void setMediumScale(float mediumScale) {
               | 
            |
| 638 | 
                + checkZoomLevels(mMinScale, mediumScale, mMaxScale);  | 
            |
| 639 | 
                + mMidScale = mediumScale;  | 
            |
| 640 | 
                + }  | 
            |
| 641 | 
                +  | 
            |
| 642 | 
                + @Override  | 
            |
| 643 | 
                +    public void setMaximumScale(float maximumScale) {
               | 
            |
| 644 | 
                + checkZoomLevels(mMinScale, mMidScale, maximumScale);  | 
            |
| 645 | 
                + mMaxScale = maximumScale;  | 
            |
| 646 | 
                + }  | 
            |
| 647 | 
                +  | 
            |
| 648 | 
                + @Override  | 
            |
| 649 | 
                +    public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
               | 
            |
| 650 | 
                + checkZoomLevels(minimumScale, mediumScale, maximumScale);  | 
            |
| 651 | 
                + mMinScale = minimumScale;  | 
            |
| 652 | 
                + mMidScale = mediumScale;  | 
            |
| 653 | 
                + mMaxScale = maximumScale;  | 
            |
| 654 | 
                + }  | 
            |
| 655 | 
                +  | 
            |
| 656 | 
                + @Override  | 
            |
| 657 | 
                +    public void setOnLongClickListener(OnLongClickListener listener) {
               | 
            |
| 658 | 
                + mLongClickListener = listener;  | 
            |
| 659 | 
                + }  | 
            |
| 660 | 
                +  | 
            |
| 661 | 
                + @Override  | 
            |
| 662 | 
                +    public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
               | 
            |
| 663 | 
                + mMatrixChangeListener = listener;  | 
            |
| 664 | 
                + }  | 
            |
| 665 | 
                +  | 
            |
| 666 | 
                + @Override  | 
            |
| 667 | 
                +    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
               | 
            |
| 668 | 
                + mPhotoTapListener = listener;  | 
            |
| 669 | 
                + }  | 
            |
| 670 | 
                +  | 
            |
| 671 | 
                + @Override  | 
            |
| 672 | 
                +    public void setOnRotateListener(OnRotateListener onRotateListener) {
               | 
            |
| 673 | 
                + mOnRotateListener = onRotateListener;  | 
            |
| 674 | 
                + }  | 
            |
| 675 | 
                +  | 
            |
| 676 | 
                + @Override  | 
            |
| 677 | 
                +    public OnViewTapListener getOnViewTapListener() {
               | 
            |
| 678 | 
                + return mViewTapListener;  | 
            |
| 679 | 
                + }  | 
            |
| 680 | 
                +  | 
            |
| 681 | 
                + @Nullable  | 
            |
| 682 | 
                +    OnPhotoTapListener getOnPhotoTapListener() {
               | 
            |
| 683 | 
                + return mPhotoTapListener;  | 
            |
| 684 | 
                + }  | 
            |
| 685 | 
                +  | 
            |
| 686 | 
                + @Override  | 
            |
| 687 | 
                +    public void setOnViewTapListener(OnViewTapListener listener) {
               | 
            |
| 688 | 
                + mViewTapListener = listener;  | 
            |
| 689 | 
                + }  | 
            |
| 690 | 
                +  | 
            |
| 691 | 
                + @Override  | 
            |
| 692 | 
                +    public void setScale(float scale) {
               | 
            |
| 693 | 
                + setScale(scale, false);  | 
            |
| 694 | 
                + }  | 
            |
| 695 | 
                +  | 
            |
| 696 | 
                + @Override  | 
            |
| 697 | 
                +    public void setScale(float scale, boolean animate) {
               | 
            |
| 698 | 
                + ImageView imageView = getImageView();  | 
            |
| 699 | 
                +  | 
            |
| 700 | 
                +        if (null != imageView) {
               | 
            |
| 701 | 
                + setScale(scale,  | 
            |
| 702 | 
                + (imageView.getRight()) / 2,  | 
            |
| 703 | 
                + (imageView.getBottom()) / 2,  | 
            |
| 704 | 
                + animate);  | 
            |
| 705 | 
                + }  | 
            |
| 706 | 
                + }  | 
            |
| 707 | 
                +  | 
            |
| 708 | 
                + @Override  | 
            |
| 709 | 
                + public void setScale(float scale, float focalX, float focalY,  | 
            |
| 710 | 
                +                         boolean animate) {
               | 
            |
| 711 | 
                + ImageView imageView = getImageView();  | 
            |
| 712 | 
                +  | 
            |
| 713 | 
                +        if (null != imageView) {
               | 
            |
| 714 | 
                + // Check to see if the scale is within bounds  | 
            |
| 715 | 
                +            if (scale < mMinScale || scale > mMaxScale) {
               | 
            |
| 716 | 
                + LogManager  | 
            |
| 717 | 
                + .getLogger()  | 
            |
| 718 | 
                + .i(LOG_TAG,  | 
            |
| 719 | 
                + "Scale must be within the range of minScale and maxScale");  | 
            |
| 720 | 
                + return;  | 
            |
| 721 | 
                + }  | 
            |
| 722 | 
                +  | 
            |
| 723 | 
                +            if (animate) {
               | 
            |
| 724 | 
                + imageView.post(new AnimatedZoomRunnable(getScale(), scale,  | 
            |
| 725 | 
                + focalX, focalY));  | 
            |
| 726 | 
                +            } else {
               | 
            |
| 727 | 
                + mSuppMatrix.setScale(scale, scale, focalX, focalY);  | 
            |
| 728 | 
                + checkAndDisplayMatrix();  | 
            |
| 729 | 
                + }  | 
            |
| 730 | 
                + }  | 
            |
| 731 | 
                + }  | 
            |
| 732 | 
                +  | 
            |
| 733 | 
                + /**  | 
            |
| 734 | 
                + * Set the zoom interpolator  | 
            |
| 735 | 
                + * @param interpolator the zoom interpolator  | 
            |
| 736 | 
                + */  | 
            |
| 737 | 
                +    public void setZoomInterpolator(Interpolator interpolator) {
               | 
            |
| 738 | 
                + mInterpolator = interpolator;  | 
            |
| 739 | 
                + }  | 
            |
| 740 | 
                +  | 
            |
| 741 | 
                + @Override  | 
            |
| 742 | 
                +    public void setScaleType(ScaleType scaleType) {
               | 
            |
| 743 | 
                +        if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
               | 
            |
| 744 | 
                + mScaleType = scaleType;  | 
            |
| 745 | 
                +  | 
            |
| 746 | 
                + // Finally update  | 
            |
| 747 | 
                + update();  | 
            |
| 748 | 
                + }  | 
            |
| 749 | 
                + }  | 
            |
| 750 | 
                +  | 
            |
| 751 | 
                + @Override  | 
            |
| 752 | 
                +    public void setZoomable(boolean zoomable) {
               | 
            |
| 753 | 
                + mZoomEnabled = zoomable;  | 
            |
| 754 | 
                + update();  | 
            |
| 755 | 
                + }  | 
            |
| 756 | 
                +  | 
            |
| 757 | 
                +    public void update() {
               | 
            |
| 758 | 
                + ImageView imageView = getImageView();  | 
            |
| 759 | 
                +  | 
            |
| 760 | 
                +        if (null != imageView) {
               | 
            |
| 761 | 
                +            if (mZoomEnabled) {
               | 
            |
| 762 | 
                + // Make sure we using MATRIX Scale Type  | 
            |
| 763 | 
                + setImageViewScaleTypeMatrix(imageView);  | 
            |
| 764 | 
                +  | 
            |
| 765 | 
                + // Update the base matrix using the current drawable  | 
            |
| 766 | 
                + updateBaseMatrix(imageView.getDrawable());  | 
            |
| 767 | 
                +            } else {
               | 
            |
| 768 | 
                + // Reset the Matrix...  | 
            |
| 769 | 
                + resetMatrix();  | 
            |
| 770 | 
                + }  | 
            |
| 771 | 
                + }  | 
            |
| 772 | 
                + }  | 
            |
| 773 | 
                +  | 
            |
| 774 | 
                + /**  | 
            |
| 775 | 
                + * Get the display matrix  | 
            |
| 776 | 
                + * @param matrix target matrix to copy to  | 
            |
| 777 | 
                + */  | 
            |
| 778 | 
                + @Override  | 
            |
| 779 | 
                +    public void getDisplayMatrix(Matrix matrix) {
               | 
            |
| 780 | 
                + matrix.set(getDrawMatrix());  | 
            |
| 781 | 
                + }  | 
            |
| 782 | 
                +  | 
            |
| 783 | 
                + /**  | 
            |
| 784 | 
                + * Get the current support matrix  | 
            |
| 785 | 
                + */  | 
            |
| 786 | 
                +    public void getSuppMatrix(Matrix matrix) {
               | 
            |
| 787 | 
                + matrix.set(mSuppMatrix);  | 
            |
| 788 | 
                + }  | 
            |
| 789 | 
                +  | 
            |
| 790 | 
                +    private Matrix getDrawMatrix() {
               | 
            |
| 791 | 
                + mDrawMatrix.set(mBaseMatrix);  | 
            |
| 792 | 
                + mDrawMatrix.postConcat(mSuppMatrix);  | 
            |
| 793 | 
                + return mDrawMatrix;  | 
            |
| 794 | 
                + }  | 
            |
| 795 | 
                +  | 
            |
| 796 | 
                +    private void cancelFling() {
               | 
            |
| 797 | 
                +        if (null != mCurrentFlingRunnable) {
               | 
            |
| 798 | 
                + mCurrentFlingRunnable.cancelFling();  | 
            |
| 799 | 
                + mCurrentFlingRunnable = null;  | 
            |
| 800 | 
                + }  | 
            |
| 801 | 
                + }  | 
            |
| 802 | 
                +  | 
            |
| 803 | 
                +    public Matrix getImageMatrix() {
               | 
            |
| 804 | 
                + return mDrawMatrix;  | 
            |
| 805 | 
                + }  | 
            |
| 806 | 
                +  | 
            |
| 807 | 
                + /**  | 
            |
| 808 | 
                + * Helper method that simply checks the Matrix, and then displays the result  | 
            |
| 809 | 
                + */  | 
            |
| 810 | 
                +    private void checkAndDisplayMatrix() {
               | 
            |
| 811 | 
                +        if (checkMatrixBounds()) {
               | 
            |
| 812 | 
                + setImageViewMatrix(getDrawMatrix());  | 
            |
| 813 | 
                + }  | 
            |
| 814 | 
                + }  | 
            |
| 815 | 
                +  | 
            |
| 816 | 
                +    private void checkImageViewScaleType() {
               | 
            |
| 817 | 
                + ImageView imageView = getImageView();  | 
            |
| 818 | 
                +  | 
            |
| 819 | 
                + /**  | 
            |
| 820 | 
                + * PhotoView's getScaleType() will just divert to this.getScaleType() so  | 
            |
| 821 | 
                + * only call if we're not attached to a PhotoView.  | 
            |
| 822 | 
                + */  | 
            |
| 823 | 
                +        if (null != imageView && !(imageView instanceof IPhotoView)) {
               | 
            |
| 824 | 
                +            if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
               | 
            |
| 825 | 
                + throw new IllegalStateException(  | 
            |
| 826 | 
                + "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher. You should call setScaleType on the PhotoViewAttacher instead of on the ImageView" );  | 
            |
| 827 | 
                + }  | 
            |
| 828 | 
                + }  | 
            |
| 829 | 
                + }  | 
            |
| 830 | 
                +  | 
            |
| 831 | 
                +    private boolean checkMatrixBounds() {
               | 
            |
| 832 | 
                + final ImageView imageView = getImageView();  | 
            |
| 833 | 
                +        if (null == imageView) {
               | 
            |
| 834 | 
                + return false;  | 
            |
| 835 | 
                + }  | 
            |
| 836 | 
                +  | 
            |
| 837 | 
                + final RectF rect = getDisplayRect(getDrawMatrix());  | 
            |
| 838 | 
                +        if (null == rect) {
               | 
            |
| 839 | 
                + return false;  | 
            |
| 840 | 
                + }  | 
            |
| 841 | 
                +  | 
            |
| 842 | 
                + final float height = rect.height(), width = rect.width();  | 
            |
| 843 | 
                + float deltaX = 0, deltaY = 0;  | 
            |
| 844 | 
                +  | 
            |
| 845 | 
                + final int viewHeight = getImageViewHeight(imageView);  | 
            |
| 846 | 
                +        if (height <= viewHeight) {
               | 
            |
| 847 | 
                +            switch (mScaleType) {
               | 
            |
| 848 | 
                + case FIT_START:  | 
            |
| 849 | 
                + deltaY = -rect.top;  | 
            |
| 850 | 
                + break;  | 
            |
| 851 | 
                + case FIT_END:  | 
            |
| 852 | 
                + deltaY = viewHeight - height - rect.top;  | 
            |
| 853 | 
                + break;  | 
            |
| 854 | 
                + default:  | 
            |
| 855 | 
                + deltaY = (viewHeight - height) / 2 - rect.top;  | 
            |
| 856 | 
                + break;  | 
            |
| 857 | 
                + }  | 
            |
| 858 | 
                +        } else if (rect.top > 0) {
               | 
            |
| 859 | 
                + deltaY = -rect.top;  | 
            |
| 860 | 
                +        } else if (rect.bottom < viewHeight) {
               | 
            |
| 861 | 
                + deltaY = viewHeight - rect.bottom;  | 
            |
| 862 | 
                + }  | 
            |
| 863 | 
                +  | 
            |
| 864 | 
                + final int viewWidth = getImageViewWidth(imageView);  | 
            |
| 865 | 
                +        if (width <= viewWidth) {
               | 
            |
| 866 | 
                +            switch (mScaleType) {
               | 
            |
| 867 | 
                + case FIT_START:  | 
            |
| 868 | 
                + deltaX = -rect.left;  | 
            |
| 869 | 
                + break;  | 
            |
| 870 | 
                + case FIT_END:  | 
            |
| 871 | 
                + deltaX = viewWidth - width - rect.left;  | 
            |
| 872 | 
                + break;  | 
            |
| 873 | 
                + default:  | 
            |
| 874 | 
                + deltaX = (viewWidth - width) / 2 - rect.left;  | 
            |
| 875 | 
                + break;  | 
            |
| 876 | 
                + }  | 
            |
| 877 | 
                + mScrollEdge = EDGE_BOTH;  | 
            |
| 878 | 
                +        } else if (rect.left > 0) {
               | 
            |
| 879 | 
                + mScrollEdge = EDGE_LEFT;  | 
            |
| 880 | 
                + deltaX = -rect.left;  | 
            |
| 881 | 
                +        } else if (rect.right < viewWidth) {
               | 
            |
| 882 | 
                + deltaX = viewWidth - rect.right;  | 
            |
| 883 | 
                + mScrollEdge = EDGE_RIGHT;  | 
            |
| 884 | 
                +        } else {
               | 
            |
| 885 | 
                + mScrollEdge = EDGE_NONE;  | 
            |
| 886 | 
                + }  | 
            |
| 887 | 
                +  | 
            |
| 888 | 
                + // Finally actually translate the matrix  | 
            |
| 889 | 
                + mSuppMatrix.postTranslate(deltaX, deltaY);  | 
            |
| 890 | 
                +  | 
            |
| 891 | 
                + return true;  | 
            |
| 892 | 
                + }  | 
            |
| 893 | 
                +  | 
            |
| 894 | 
                + /**  | 
            |
| 895 | 
                + * Helper method that maps the supplied Matrix to the current Drawable  | 
            |
| 896 | 
                + *  | 
            |
| 897 | 
                + * @param matrix - Matrix to map Drawable against  | 
            |
| 898 | 
                + * @return RectF - Displayed Rectangle  | 
            |
| 899 | 
                + */  | 
            |
| 900 | 
                +    private RectF getDisplayRect(Matrix matrix) {
               | 
            |
| 901 | 
                + ImageView imageView = getImageView();  | 
            |
| 902 | 
                +  | 
            |
| 903 | 
                +        if (null != imageView) {
               | 
            |
| 904 | 
                + Drawable d = imageView.getDrawable();  | 
            |
| 905 | 
                +            if (null != d) {
               | 
            |
| 906 | 
                + mDisplayRect.set(0, 0, d.getIntrinsicWidth(),  | 
            |
| 907 | 
                + d.getIntrinsicHeight());  | 
            |
| 908 | 
                + matrix.mapRect(mDisplayRect);  | 
            |
| 909 | 
                + return mDisplayRect;  | 
            |
| 910 | 
                + }  | 
            |
| 911 | 
                + }  | 
            |
| 912 | 
                + return null;  | 
            |
| 913 | 
                + }  | 
            |
| 914 | 
                +  | 
            |
| 915 | 
                +    public Bitmap getVisibleRectangleBitmap() {
               | 
            |
| 916 | 
                + ImageView imageView = getImageView();  | 
            |
| 917 | 
                + return imageView == null ? null : imageView.getDrawingCache();  | 
            |
| 918 | 
                + }  | 
            |
| 919 | 
                +  | 
            |
| 920 | 
                + @Override  | 
            |
| 921 | 
                +    public void setZoomTransitionDuration(int milliseconds) {
               | 
            |
| 922 | 
                + if (milliseconds < 0)  | 
            |
| 923 | 
                + milliseconds = DEFAULT_ZOOM_DURATION;  | 
            |
| 924 | 
                + this.ZOOM_DURATION = milliseconds;  | 
            |
| 925 | 
                + }  | 
            |
| 926 | 
                +  | 
            |
| 927 | 
                + @Override  | 
            |
| 928 | 
                +    public IPhotoView getIPhotoViewImplementation() {
               | 
            |
| 929 | 
                + return this;  | 
            |
| 930 | 
                + }  | 
            |
| 931 | 
                +  | 
            |
| 932 | 
                + /**  | 
            |
| 933 | 
                + * Helper method that 'unpacks' a Matrix and returns the required value  | 
            |
| 934 | 
                + *  | 
            |
| 935 | 
                + * @param matrix - Matrix to unpack  | 
            |
| 936 | 
                + * @param whichValue - Which value from Matrix.M* to return  | 
            |
| 937 | 
                + * @return float - returned value  | 
            |
| 938 | 
                + */  | 
            |
| 939 | 
                +    private float getValue(Matrix matrix, int whichValue) {
               | 
            |
| 940 | 
                + matrix.getValues(mMatrixValues);  | 
            |
| 941 | 
                + return mMatrixValues[whichValue];  | 
            |
| 942 | 
                + }  | 
            |
| 943 | 
                +  | 
            |
| 944 | 
                + /**  | 
            |
| 945 | 
                + * Resets the Matrix back to FIT_CENTER, and then displays it.s  | 
            |
| 946 | 
                + */  | 
            |
| 947 | 
                +    private void resetMatrix() {
               | 
            |
| 948 | 
                + mSuppMatrix.reset();  | 
            |
| 949 | 
                + setRotationBy(mBaseRotation);  | 
            |
| 950 | 
                + setImageViewMatrix(getDrawMatrix());  | 
            |
| 951 | 
                + checkMatrixBounds();  | 
            |
| 952 | 
                + }  | 
            |
| 953 | 
                +  | 
            |
| 954 | 
                +    private void setImageViewMatrix(Matrix matrix) {
               | 
            |
| 955 | 
                + ImageView imageView = getImageView();  | 
            |
| 956 | 
                +        if (null != imageView) {
               | 
            |
| 957 | 
                +  | 
            |
| 958 | 
                + checkImageViewScaleType();  | 
            |
| 959 | 
                + imageView.setImageMatrix(matrix);  | 
            |
| 960 | 
                +  | 
            |
| 961 | 
                + // Call MatrixChangedListener if needed  | 
            |
| 962 | 
                +            if (null != mMatrixChangeListener) {
               | 
            |
| 963 | 
                + RectF displayRect = getDisplayRect(matrix);  | 
            |
| 964 | 
                +                if (null != displayRect) {
               | 
            |
| 965 | 
                + mMatrixChangeListener.onMatrixChanged(displayRect);  | 
            |
| 966 | 
                + }  | 
            |
| 967 | 
                + }  | 
            |
| 968 | 
                + }  | 
            |
| 969 | 
                + }  | 
            |
| 970 | 
                +  | 
            |
| 971 | 
                + /**  | 
            |
| 972 | 
                + * Calculate Matrix for FIT_CENTER  | 
            |
| 973 | 
                + *  | 
            |
| 974 | 
                + * @param d - Drawable being displayed  | 
            |
| 975 | 
                + */  | 
            |
| 976 | 
                +    private void updateBaseMatrix(Drawable d) {
               | 
            |
| 977 | 
                + ImageView imageView = getImageView();  | 
            |
| 978 | 
                +        if (null == imageView || null == d) {
               | 
            |
| 979 | 
                + return;  | 
            |
| 980 | 
                + }  | 
            |
| 981 | 
                +  | 
            |
| 982 | 
                + final float viewWidth = getImageViewWidth(imageView);  | 
            |
| 983 | 
                + final float viewHeight = getImageViewHeight(imageView);  | 
            |
| 984 | 
                + final int drawableWidth = d.getIntrinsicWidth();  | 
            |
| 985 | 
                + final int drawableHeight = d.getIntrinsicHeight();  | 
            |
| 986 | 
                +  | 
            |
| 987 | 
                + mBaseMatrix.reset();  | 
            |
| 988 | 
                +  | 
            |
| 989 | 
                + final float widthScale = viewWidth / drawableWidth;  | 
            |
| 990 | 
                + final float heightScale = viewHeight / drawableHeight;  | 
            |
| 991 | 
                +  | 
            |
| 992 | 
                +        if (mScaleType == ScaleType.CENTER) {
               | 
            |
| 993 | 
                + mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,  | 
            |
| 994 | 
                + (viewHeight - drawableHeight) / 2F);  | 
            |
| 995 | 
                +  | 
            |
| 996 | 
                +        } else if (mScaleType == ScaleType.CENTER_CROP) {
               | 
            |
| 997 | 
                + float scale = Math.max(widthScale, heightScale);  | 
            |
| 998 | 
                + mBaseMatrix.postScale(scale, scale);  | 
            |
| 999 | 
                + mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,  | 
            |
| 1000 | 
                + (viewHeight - drawableHeight * scale) / 2F);  | 
            |
| 1001 | 
                +  | 
            |
| 1002 | 
                +        } else if (mScaleType == ScaleType.CENTER_INSIDE) {
               | 
            |
| 1003 | 
                + float scale = Math.min(1.0f, Math.min(widthScale, heightScale));  | 
            |
| 1004 | 
                + mBaseMatrix.postScale(scale, scale);  | 
            |
| 1005 | 
                + mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,  | 
            |
| 1006 | 
                + (viewHeight - drawableHeight * scale) / 2F);  | 
            |
| 1007 | 
                +  | 
            |
| 1008 | 
                +        } else {
               | 
            |
| 1009 | 
                + RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);  | 
            |
| 1010 | 
                + RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);  | 
            |
| 1011 | 
                +  | 
            |
| 1012 | 
                +            if ((int) mBaseRotation % 180 != 0) {
               | 
            |
| 1013 | 
                + mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);  | 
            |
| 1014 | 
                + }  | 
            |
| 1015 | 
                +  | 
            |
| 1016 | 
                +            switch (mScaleType) {
               | 
            |
| 1017 | 
                + case FIT_CENTER:  | 
            |
| 1018 | 
                + mBaseMatrix  | 
            |
| 1019 | 
                + .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);  | 
            |
| 1020 | 
                + break;  | 
            |
| 1021 | 
                +  | 
            |
| 1022 | 
                + case FIT_START:  | 
            |
| 1023 | 
                + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);  | 
            |
| 1024 | 
                + break;  | 
            |
| 1025 | 
                +  | 
            |
| 1026 | 
                + case FIT_END:  | 
            |
| 1027 | 
                + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);  | 
            |
| 1028 | 
                + break;  | 
            |
| 1029 | 
                +  | 
            |
| 1030 | 
                + case FIT_XY:  | 
            |
| 1031 | 
                + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);  | 
            |
| 1032 | 
                + break;  | 
            |
| 1033 | 
                +  | 
            |
| 1034 | 
                + default:  | 
            |
| 1035 | 
                + break;  | 
            |
| 1036 | 
                + }  | 
            |
| 1037 | 
                + }  | 
            |
| 1038 | 
                +  | 
            |
| 1039 | 
                + resetMatrix();  | 
            |
| 1040 | 
                + }  | 
            |
| 1041 | 
                +  | 
            |
| 1042 | 
                +    private int getImageViewWidth(ImageView imageView) {
               | 
            |
| 1043 | 
                + if (null == imageView)  | 
            |
| 1044 | 
                + return 0;  | 
            |
| 1045 | 
                + return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();  | 
            |
| 1046 | 
                + }  | 
            |
| 1047 | 
                +  | 
            |
| 1048 | 
                +    private int getImageViewHeight(ImageView imageView) {
               | 
            |
| 1049 | 
                + if (null == imageView)  | 
            |
| 1050 | 
                + return 0;  | 
            |
| 1051 | 
                + return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();  | 
            |
| 1052 | 
                + }  | 
            |
| 1053 | 
                +  | 
            |
| 1054 | 
                + /**  | 
            |
| 1055 | 
                + * Interface definition for a callback to be invoked when the internal Matrix has changed for  | 
            |
| 1056 | 
                + * this View.  | 
            |
| 1057 | 
                + *  | 
            |
| 1058 | 
                + * @author Chris Banes  | 
            |
| 1059 | 
                + */  | 
            |
| 1060 | 
                +    public interface OnMatrixChangedListener {
               | 
            |
| 1061 | 
                + /**  | 
            |
| 1062 | 
                + * Callback for when the Matrix displaying the Drawable has changed. This could be because  | 
            |
| 1063 | 
                + * the View's bounds have changed, or the user has zoomed.  | 
            |
| 1064 | 
                + *  | 
            |
| 1065 | 
                + * @param rect - Rectangle displaying the Drawable's new bounds.  | 
            |
| 1066 | 
                + */  | 
            |
| 1067 | 
                + void onMatrixChanged(RectF rect);  | 
            |
| 1068 | 
                + }  | 
            |
| 1069 | 
                +  | 
            |
| 1070 | 
                + /**  | 
            |
| 1071 | 
                + * Interface definition for callback to be invoked when attached ImageView scale changes  | 
            |
| 1072 | 
                + *  | 
            |
| 1073 | 
                + * @author Marek Sebera  | 
            |
| 1074 | 
                + */  | 
            |
| 1075 | 
                +    public interface OnScaleChangeListener {
               | 
            |
| 1076 | 
                + /**  | 
            |
| 1077 | 
                + * Callback for when the scale changes  | 
            |
| 1078 | 
                + *  | 
            |
| 1079 | 
                + * @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)  | 
            |
| 1080 | 
                + * @param focusX focal point X position  | 
            |
| 1081 | 
                + * @param focusY focal point Y position  | 
            |
| 1082 | 
                + */  | 
            |
| 1083 | 
                + void onScaleChange(float scaleFactor, float focusX, float focusY);  | 
            |
| 1084 | 
                + }  | 
            |
| 1085 | 
                +  | 
            |
| 1086 | 
                + /**  | 
            |
| 1087 | 
                + * Interface definition for a callback to be invoked when the Photo is tapped with a single  | 
            |
| 1088 | 
                + * tap.  | 
            |
| 1089 | 
                + *  | 
            |
| 1090 | 
                + * @author Chris Banes  | 
            |
| 1091 | 
                + */  | 
            |
| 1092 | 
                +    public interface OnPhotoTapListener {
               | 
            |
| 1093 | 
                +  | 
            |
| 1094 | 
                + /**  | 
            |
| 1095 | 
                + * A callback to receive where the user taps on a photo. You will only receive a callback if  | 
            |
| 1096 | 
                + * the user taps on the actual photo, tapping on 'whitespace' will be ignored.  | 
            |
| 1097 | 
                + *  | 
            |
| 1098 | 
                + * @param view - View the user tapped.  | 
            |
| 1099 | 
                + * @param x - where the user tapped from the of the Drawable, as percentage of the  | 
            |
| 1100 | 
                + * Drawable width.  | 
            |
| 1101 | 
                + * @param y - where the user tapped from the top of the Drawable, as percentage of the  | 
            |
| 1102 | 
                + * Drawable height.  | 
            |
| 1103 | 
                + */  | 
            |
| 1104 | 
                + void onPhotoTap(View view, float x, float y);  | 
            |
| 1105 | 
                +  | 
            |
| 1106 | 
                + /**  | 
            |
| 1107 | 
                + * A simple callback where out of photo happened;  | 
            |
| 1108 | 
                + * */  | 
            |
| 1109 | 
                + void onOutsidePhotoTap();  | 
            |
| 1110 | 
                + }  | 
            |
| 1111 | 
                +  | 
            |
| 1112 | 
                + /**  | 
            |
| 1113 | 
                + * Interface definition for a callback to be invoked when the ImageView is tapped with a single  | 
            |
| 1114 | 
                + * tap.  | 
            |
| 1115 | 
                + *  | 
            |
| 1116 | 
                + * @author Chris Banes  | 
            |
| 1117 | 
                + */  | 
            |
| 1118 | 
                +    public interface OnViewTapListener {
               | 
            |
| 1119 | 
                +  | 
            |
| 1120 | 
                + /**  | 
            |
| 1121 | 
                + * A callback to receive where the user taps on a ImageView. You will receive a callback if  | 
            |
| 1122 | 
                + * the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.  | 
            |
| 1123 | 
                + *  | 
            |
| 1124 | 
                + * @param view - View the user tapped.  | 
            |
| 1125 | 
                + * @param x - where the user tapped from the left of the View.  | 
            |
| 1126 | 
                + * @param y - where the user tapped from the top of the View.  | 
            |
| 1127 | 
                + */  | 
            |
| 1128 | 
                + void onViewTap(View view, float x, float y);  | 
            |
| 1129 | 
                + }  | 
            |
| 1130 | 
                +  | 
            |
| 1131 | 
                + /**  | 
            |
| 1132 | 
                + * Interface definition for a callback to be invoked when the ImageView is roateted with two finger.  | 
            |
| 1133 | 
                + *  | 
            |
| 1134 | 
                + * @author ChenSL  | 
            |
| 1135 | 
                + */  | 
            |
| 1136 | 
                +    public interface OnRotateListener {
               | 
            |
| 1137 | 
                + /**  | 
            |
| 1138 | 
                + * A callBack to receive when the user rotate a ImageView.You will receive a callback  | 
            |
| 1139 | 
                + * if the user rotate the ImageView  | 
            |
| 1140 | 
                + *  | 
            |
| 1141 | 
                + * @param degree rotate mOldDegree  | 
            |
| 1142 | 
                + */  | 
            |
| 1143 | 
                + void onRotate(int degree);  | 
            |
| 1144 | 
                + }  | 
            |
| 1145 | 
                +  | 
            |
| 1146 | 
                + /**  | 
            |
| 1147 | 
                + * Interface definition for a callback to be invoked when the ImageView is fling with a single  | 
            |
| 1148 | 
                + * touch  | 
            |
| 1149 | 
                + *  | 
            |
| 1150 | 
                + * @author tonyjs  | 
            |
| 1151 | 
                + */  | 
            |
| 1152 | 
                +    public interface OnSingleFlingListener {
               | 
            |
| 1153 | 
                +  | 
            |
| 1154 | 
                + /**  | 
            |
| 1155 | 
                + * A callback to receive where the user flings on a ImageView. You will receive a callback if  | 
            |
| 1156 | 
                + * the user flings anywhere on the view.  | 
            |
| 1157 | 
                + *  | 
            |
| 1158 | 
                + * @param e1 - MotionEvent the user first touch.  | 
            |
| 1159 | 
                + * @param e2 - MotionEvent the user last touch.  | 
            |
| 1160 | 
                + * @param velocityX - distance of user's horizontal fling.  | 
            |
| 1161 | 
                + * @param velocityY - distance of user's vertical fling.  | 
            |
| 1162 | 
                + */  | 
            |
| 1163 | 
                + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);  | 
            |
| 1164 | 
                + }  | 
            |
| 1165 | 
                +  | 
            |
| 1166 | 
                +    private class AnimatedZoomRunnable implements Runnable {
               | 
            |
| 1167 | 
                +  | 
            |
| 1168 | 
                + private final float mFocalX, mFocalY;  | 
            |
| 1169 | 
                + private final long mStartTime;  | 
            |
| 1170 | 
                + private final float mZoomStart, mZoomEnd;  | 
            |
| 1171 | 
                +  | 
            |
| 1172 | 
                + public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,  | 
            |
| 1173 | 
                +                                    final float focalX, final float focalY) {
               | 
            |
| 1174 | 
                + mFocalX = focalX;  | 
            |
| 1175 | 
                + mFocalY = focalY;  | 
            |
| 1176 | 
                + mStartTime = System.currentTimeMillis();  | 
            |
| 1177 | 
                + mZoomStart = currentZoom;  | 
            |
| 1178 | 
                + mZoomEnd = targetZoom;  | 
            |
| 1179 | 
                + }  | 
            |
| 1180 | 
                +  | 
            |
| 1181 | 
                + @Override  | 
            |
| 1182 | 
                +        public void run() {
               | 
            |
| 1183 | 
                + ImageView imageView = getImageView();  | 
            |
| 1184 | 
                +            if (imageView == null) {
               | 
            |
| 1185 | 
                + return;  | 
            |
| 1186 | 
                + }  | 
            |
| 1187 | 
                +  | 
            |
| 1188 | 
                + float t = interpolate();  | 
            |
| 1189 | 
                + float scale = mZoomStart + t * (mZoomEnd - mZoomStart);  | 
            |
| 1190 | 
                + float deltaScale = scale / getScale();  | 
            |
| 1191 | 
                +  | 
            |
| 1192 | 
                + onScale(deltaScale, mFocalX, mFocalY);  | 
            |
| 1193 | 
                +  | 
            |
| 1194 | 
                + // We haven't hit our target scale yet, so post ourselves again  | 
            |
| 1195 | 
                +            if (t < 1f) {
               | 
            |
| 1196 | 
                + Compat.postOnAnimation(imageView, this);  | 
            |
| 1197 | 
                + }  | 
            |
| 1198 | 
                + }  | 
            |
| 1199 | 
                +  | 
            |
| 1200 | 
                +        private float interpolate() {
               | 
            |
| 1201 | 
                + float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION;  | 
            |
| 1202 | 
                + t = Math.min(1f, t);  | 
            |
| 1203 | 
                + t = mInterpolator.getInterpolation(t);  | 
            |
| 1204 | 
                + return t;  | 
            |
| 1205 | 
                + }  | 
            |
| 1206 | 
                + }  | 
            |
| 1207 | 
                +  | 
            |
| 1208 | 
                +    private class FlingRunnable implements Runnable {
               | 
            |
| 1209 | 
                +  | 
            |
| 1210 | 
                + private final ScrollerProxy mScroller;  | 
            |
| 1211 | 
                + private int mCurrentX, mCurrentY;  | 
            |
| 1212 | 
                +  | 
            |
| 1213 | 
                +        public FlingRunnable(Context context) {
               | 
            |
| 1214 | 
                + mScroller = ScrollerProxy.getScroller(context);  | 
            |
| 1215 | 
                + }  | 
            |
| 1216 | 
                +  | 
            |
| 1217 | 
                +        public void cancelFling() {
               | 
            |
| 1218 | 
                +            if (DEBUG) {
               | 
            |
| 1219 | 
                + LogManager.getLogger().d(LOG_TAG, "Cancel Fling");  | 
            |
| 1220 | 
                + }  | 
            |
| 1221 | 
                + mScroller.forceFinished(true);  | 
            |
| 1222 | 
                + }  | 
            |
| 1223 | 
                +  | 
            |
| 1224 | 
                + public void fling(int viewWidth, int viewHeight, int velocityX,  | 
            |
| 1225 | 
                +                          int velocityY) {
               | 
            |
| 1226 | 
                + final RectF rect = getDisplayRect();  | 
            |
| 1227 | 
                +            if (null == rect) {
               | 
            |
| 1228 | 
                + return;  | 
            |
| 1229 | 
                + }  | 
            |
| 1230 | 
                +  | 
            |
| 1231 | 
                + final int startX = Math.round(-rect.left);  | 
            |
| 1232 | 
                + final int minX, maxX, minY, maxY;  | 
            |
| 1233 | 
                +  | 
            |
| 1234 | 
                +            if (viewWidth < rect.width()) {
               | 
            |
| 1235 | 
                + minX = 0;  | 
            |
| 1236 | 
                + maxX = Math.round(rect.width() - viewWidth);  | 
            |
| 1237 | 
                +            } else {
               | 
            |
| 1238 | 
                + minX = maxX = startX;  | 
            |
| 1239 | 
                + }  | 
            |
| 1240 | 
                +  | 
            |
| 1241 | 
                + final int startY = Math.round(-rect.top);  | 
            |
| 1242 | 
                +            if (viewHeight < rect.height()) {
               | 
            |
| 1243 | 
                + minY = 0;  | 
            |
| 1244 | 
                + maxY = Math.round(rect.height() - viewHeight);  | 
            |
| 1245 | 
                +            } else {
               | 
            |
| 1246 | 
                + minY = maxY = startY;  | 
            |
| 1247 | 
                + }  | 
            |
| 1248 | 
                +  | 
            |
| 1249 | 
                + mCurrentX = startX;  | 
            |
| 1250 | 
                + mCurrentY = startY;  | 
            |
| 1251 | 
                +  | 
            |
| 1252 | 
                +            if (DEBUG) {
               | 
            |
| 1253 | 
                + LogManager.getLogger().d(  | 
            |
| 1254 | 
                + LOG_TAG,  | 
            |
| 1255 | 
                + "fling. StartX:" + startX + " StartY:" + startY  | 
            |
| 1256 | 
                + + " MaxX:" + maxX + " MaxY:" + maxY);  | 
            |
| 1257 | 
                + }  | 
            |
| 1258 | 
                +  | 
            |
| 1259 | 
                + // If we actually can move, fling the scroller  | 
            |
| 1260 | 
                +            if (startX != maxX || startY != maxY) {
               | 
            |
| 1261 | 
                + mScroller.fling(startX, startY, velocityX, velocityY, minX,  | 
            |
| 1262 | 
                + maxX, minY, maxY, 0, 0);  | 
            |
| 1263 | 
                + }  | 
            |
| 1264 | 
                + }  | 
            |
| 1265 | 
                +  | 
            |
| 1266 | 
                + @Override  | 
            |
| 1267 | 
                +        public void run() {
               | 
            |
| 1268 | 
                +            if (mScroller.isFinished()) {
               | 
            |
| 1269 | 
                + return; // remaining post that should not be handled  | 
            |
| 1270 | 
                + }  | 
            |
| 1271 | 
                +  | 
            |
| 1272 | 
                + ImageView imageView = getImageView();  | 
            |
| 1273 | 
                +            if (null != imageView && mScroller.computeScrollOffset()) {
               | 
            |
| 1274 | 
                +  | 
            |
| 1275 | 
                + final int newX = mScroller.getCurrX();  | 
            |
| 1276 | 
                + final int newY = mScroller.getCurrY();  | 
            |
| 1277 | 
                +  | 
            |
| 1278 | 
                +                if (DEBUG) {
               | 
            |
| 1279 | 
                + LogManager.getLogger().d(  | 
            |
| 1280 | 
                + LOG_TAG,  | 
            |
| 1281 | 
                + "fling run(). CurrentX:" + mCurrentX + " CurrentY:"  | 
            |
| 1282 | 
                + + mCurrentY + " NewX:" + newX + " NewY:"  | 
            |
| 1283 | 
                + + newY);  | 
            |
| 1284 | 
                + }  | 
            |
| 1285 | 
                +  | 
            |
| 1286 | 
                + mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);  | 
            |
| 1287 | 
                + setImageViewMatrix(getDrawMatrix());  | 
            |
| 1288 | 
                +  | 
            |
| 1289 | 
                + mCurrentX = newX;  | 
            |
| 1290 | 
                + mCurrentY = newY;  | 
            |
| 1291 | 
                +  | 
            |
| 1292 | 
                + // Post On animation  | 
            |
| 1293 | 
                + Compat.postOnAnimation(imageView, this);  | 
            |
| 1294 | 
                + }  | 
            |
| 1295 | 
                + }  | 
            |
| 1296 | 
                + }  | 
            |
| 1297 | 
                +  | 
            |
| 1298 | 
                + /**  | 
            |
| 1299 | 
                + * a RightAngleRunnable that finger lift rotate to 0,90,180,270 degree  | 
            |
| 1300 | 
                + */  | 
            |
| 1301 | 
                +    private class RightAngleRunnable implements Runnable {
               | 
            |
| 1302 | 
                + private static final int RECOVER_SPEED = 4;  | 
            |
| 1303 | 
                + private int mOldDegree;  | 
            |
| 1304 | 
                + private int mNeedToRotate;  | 
            |
| 1305 | 
                + private int mRoPivotX;  | 
            |
| 1306 | 
                + private int mRoPivotY;  | 
            |
| 1307 | 
                +  | 
            |
| 1308 | 
                +        RightAngleRunnable(int degree, int pivotX, int pivotY) {
               | 
            |
| 1309 | 
                + this.mOldDegree = degree;  | 
            |
| 1310 | 
                + this.mNeedToRotate = calDegree(degree) - mOldDegree;  | 
            |
| 1311 | 
                + this.mRoPivotX = pivotX;  | 
            |
| 1312 | 
                + this.mRoPivotY = pivotY;  | 
            |
| 1313 | 
                + }  | 
            |
| 1314 | 
                +  | 
            |
| 1315 | 
                + /**  | 
            |
| 1316 | 
                + * get right degree,when one finger lifts  | 
            |
| 1317 | 
                + *  | 
            |
| 1318 | 
                + * @param oldDegree current degree  | 
            |
| 1319 | 
                + * @return 0, 90, 180, 270 according to oldDegree  | 
            |
| 1320 | 
                + */  | 
            |
| 1321 | 
                +        private int calDegree(int oldDegree) {
               | 
            |
| 1322 | 
                + int result;  | 
            |
| 1323 | 
                + float n = (float) oldDegree / 45;  | 
            |
| 1324 | 
                +            if (n >= 0 && n < 1) {
               | 
            |
| 1325 | 
                + result = 0;  | 
            |
| 1326 | 
                +            } else if (n >= 1 && n <= 2.5) {
               | 
            |
| 1327 | 
                + result = 90;  | 
            |
| 1328 | 
                +            } else if (n > 2.5 && n < 5.5) {
               | 
            |
| 1329 | 
                + result = 180;  | 
            |
| 1330 | 
                +            } else if (n >= 5.5 && n <= 7) {
               | 
            |
| 1331 | 
                + result = 270;  | 
            |
| 1332 | 
                +            } else {
               | 
            |
| 1333 | 
                + result = 360;  | 
            |
| 1334 | 
                + }  | 
            |
| 1335 | 
                + return result;  | 
            |
| 1336 | 
                + }  | 
            |
| 1337 | 
                +  | 
            |
| 1338 | 
                + @Override  | 
            |
| 1339 | 
                +        public void run() {
               | 
            |
| 1340 | 
                +            if (mNeedToRotate == 0) {
               | 
            |
| 1341 | 
                + mIsToRighting = false;  | 
            |
| 1342 | 
                + return;  | 
            |
| 1343 | 
                + }  | 
            |
| 1344 | 
                + ImageView imageView = getImageView();  | 
            |
| 1345 | 
                +            if (imageView == null) {
               | 
            |
| 1346 | 
                + mIsToRighting = false;  | 
            |
| 1347 | 
                + return;  | 
            |
| 1348 | 
                + }  | 
            |
| 1349 | 
                + mIsToRighting = true;  | 
            |
| 1350 | 
                +            if (mNeedToRotate > 0) {
               | 
            |
| 1351 | 
                + //Clockwise rotation  | 
            |
| 1352 | 
                +                if (mNeedToRotate >= RECOVER_SPEED) {
               | 
            |
| 1353 | 
                + mSuppMatrix.postRotate(RECOVER_SPEED, mRoPivotX, mRoPivotY);  | 
            |
| 1354 | 
                + mNeedToRotate -= RECOVER_SPEED;  | 
            |
| 1355 | 
                +                } else {
               | 
            |
| 1356 | 
                + mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY);  | 
            |
| 1357 | 
                + mNeedToRotate = 0;  | 
            |
| 1358 | 
                + }  | 
            |
| 1359 | 
                +            } else if (mNeedToRotate < 0) {
               | 
            |
| 1360 | 
                + //Counterclockwise rotation  | 
            |
| 1361 | 
                +                if (mNeedToRotate <= -RECOVER_SPEED) {
               | 
            |
| 1362 | 
                + mSuppMatrix.postRotate(-RECOVER_SPEED, mRoPivotX, mRoPivotY);  | 
            |
| 1363 | 
                + mNeedToRotate += RECOVER_SPEED;  | 
            |
| 1364 | 
                +                } else {
               | 
            |
| 1365 | 
                + mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY);  | 
            |
| 1366 | 
                + mNeedToRotate = 0;  | 
            |
| 1367 | 
                + }  | 
            |
| 1368 | 
                + }  | 
            |
| 1369 | 
                + checkAndDisplayMatrix();  | 
            |
| 1370 | 
                + Compat.postOnAnimation(imageView, this);  | 
            |
| 1371 | 
                + }  | 
            |
| 1372 | 
                + }  | 
            |
| 1373 | 
                +}  | 
            
                @@ -0,0 +1,149 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.content.Context;  | 
            |
| 19 | 
                +import android.view.MotionEvent;  | 
            |
| 20 | 
                +import android.view.VelocityTracker;  | 
            |
| 21 | 
                +import android.view.ViewConfiguration;  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                +import com.android.views.rotatephotoview.log.LogManager;  | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                +public class CupcakeGestureDetector implements GestureDetector {
               | 
            |
| 26 | 
                +  | 
            |
| 27 | 
                + protected OnGestureListener mListener;  | 
            |
| 28 | 
                + private static final String LOG_TAG = "CupcakeGestureDetector";  | 
            |
| 29 | 
                + float mLastTouchX;  | 
            |
| 30 | 
                + float mLastTouchY;  | 
            |
| 31 | 
                + final float mTouchSlop;  | 
            |
| 32 | 
                + final float mMinimumVelocity;  | 
            |
| 33 | 
                +  | 
            |
| 34 | 
                + @Override  | 
            |
| 35 | 
                +    public void setOnGestureListener(OnGestureListener listener) {
               | 
            |
| 36 | 
                + this.mListener = listener;  | 
            |
| 37 | 
                + }  | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                +    public CupcakeGestureDetector(Context context) {
               | 
            |
| 40 | 
                + final ViewConfiguration configuration = ViewConfiguration  | 
            |
| 41 | 
                + .get(context);  | 
            |
| 42 | 
                + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();  | 
            |
| 43 | 
                + mTouchSlop = configuration.getScaledTouchSlop();  | 
            |
| 44 | 
                + }  | 
            |
| 45 | 
                +  | 
            |
| 46 | 
                + private VelocityTracker mVelocityTracker;  | 
            |
| 47 | 
                + private boolean mIsDragging;  | 
            |
| 48 | 
                +  | 
            |
| 49 | 
                +    float getActiveX(MotionEvent ev) {
               | 
            |
| 50 | 
                + return ev.getX();  | 
            |
| 51 | 
                + }  | 
            |
| 52 | 
                +  | 
            |
| 53 | 
                +    float getActiveY(MotionEvent ev) {
               | 
            |
| 54 | 
                + return ev.getY();  | 
            |
| 55 | 
                + }  | 
            |
| 56 | 
                +  | 
            |
| 57 | 
                + @Override  | 
            |
| 58 | 
                +    public boolean isScaling() {
               | 
            |
| 59 | 
                + return false;  | 
            |
| 60 | 
                + }  | 
            |
| 61 | 
                +  | 
            |
| 62 | 
                + @Override  | 
            |
| 63 | 
                +    public boolean isDragging() {
               | 
            |
| 64 | 
                + return mIsDragging;  | 
            |
| 65 | 
                + }  | 
            |
| 66 | 
                +  | 
            |
| 67 | 
                + @Override  | 
            |
| 68 | 
                +    public boolean onTouchEvent(MotionEvent ev) {
               | 
            |
| 69 | 
                +        switch (ev.getAction()) {
               | 
            |
| 70 | 
                +            case MotionEvent.ACTION_DOWN: {
               | 
            |
| 71 | 
                + mVelocityTracker = VelocityTracker.obtain();  | 
            |
| 72 | 
                +                if (null != mVelocityTracker) {
               | 
            |
| 73 | 
                + mVelocityTracker.addMovement(ev);  | 
            |
| 74 | 
                +                } else {
               | 
            |
| 75 | 
                + LogManager.getLogger().i(LOG_TAG, "Velocity tracker is null");  | 
            |
| 76 | 
                + }  | 
            |
| 77 | 
                +  | 
            |
| 78 | 
                + mLastTouchX = getActiveX(ev);  | 
            |
| 79 | 
                + mLastTouchY = getActiveY(ev);  | 
            |
| 80 | 
                + mIsDragging = false;  | 
            |
| 81 | 
                + break;  | 
            |
| 82 | 
                + }  | 
            |
| 83 | 
                +  | 
            |
| 84 | 
                +            case MotionEvent.ACTION_MOVE: {
               | 
            |
| 85 | 
                + final float x = getActiveX(ev);  | 
            |
| 86 | 
                + final float y = getActiveY(ev);  | 
            |
| 87 | 
                + final float dx = x - mLastTouchX, dy = y - mLastTouchY;  | 
            |
| 88 | 
                +  | 
            |
| 89 | 
                +                if (!mIsDragging) {
               | 
            |
| 90 | 
                + // Use Pythagoras to see if drag length is larger than  | 
            |
| 91 | 
                + // touch slop  | 
            |
| 92 | 
                + mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;  | 
            |
| 93 | 
                + }  | 
            |
| 94 | 
                +  | 
            |
| 95 | 
                +                if (mIsDragging) {
               | 
            |
| 96 | 
                + mListener.onDrag(dx, dy);  | 
            |
| 97 | 
                + mLastTouchX = x;  | 
            |
| 98 | 
                + mLastTouchY = y;  | 
            |
| 99 | 
                +  | 
            |
| 100 | 
                +                    if (null != mVelocityTracker) {
               | 
            |
| 101 | 
                + mVelocityTracker.addMovement(ev);  | 
            |
| 102 | 
                + }  | 
            |
| 103 | 
                + }  | 
            |
| 104 | 
                + break;  | 
            |
| 105 | 
                + }  | 
            |
| 106 | 
                +  | 
            |
| 107 | 
                +            case MotionEvent.ACTION_CANCEL: {
               | 
            |
| 108 | 
                + // Recycle Velocity Tracker  | 
            |
| 109 | 
                +                if (null != mVelocityTracker) {
               | 
            |
| 110 | 
                + mVelocityTracker.recycle();  | 
            |
| 111 | 
                + mVelocityTracker = null;  | 
            |
| 112 | 
                + }  | 
            |
| 113 | 
                + break;  | 
            |
| 114 | 
                + }  | 
            |
| 115 | 
                +  | 
            |
| 116 | 
                +            case MotionEvent.ACTION_UP: {
               | 
            |
| 117 | 
                +                if (mIsDragging) {
               | 
            |
| 118 | 
                +                    if (null != mVelocityTracker) {
               | 
            |
| 119 | 
                + mLastTouchX = getActiveX(ev);  | 
            |
| 120 | 
                + mLastTouchY = getActiveY(ev);  | 
            |
| 121 | 
                +  | 
            |
| 122 | 
                + // Compute velocity within the last 1000ms  | 
            |
| 123 | 
                + mVelocityTracker.addMovement(ev);  | 
            |
| 124 | 
                + mVelocityTracker.computeCurrentVelocity(1000);  | 
            |
| 125 | 
                +  | 
            |
| 126 | 
                + final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker  | 
            |
| 127 | 
                + .getYVelocity();  | 
            |
| 128 | 
                +  | 
            |
| 129 | 
                + // If the velocity is greater than minVelocity, call  | 
            |
| 130 | 
                + // listener  | 
            |
| 131 | 
                +                        if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
               | 
            |
| 132 | 
                + mListener.onFling(mLastTouchX, mLastTouchY, -vX,  | 
            |
| 133 | 
                + -vY);  | 
            |
| 134 | 
                + }  | 
            |
| 135 | 
                + }  | 
            |
| 136 | 
                + }  | 
            |
| 137 | 
                +  | 
            |
| 138 | 
                + // Recycle Velocity Tracker  | 
            |
| 139 | 
                +                if (null != mVelocityTracker) {
               | 
            |
| 140 | 
                + mVelocityTracker.recycle();  | 
            |
| 141 | 
                + mVelocityTracker = null;  | 
            |
| 142 | 
                + }  | 
            |
| 143 | 
                + break;  | 
            |
| 144 | 
                + }  | 
            |
| 145 | 
                + }  | 
            |
| 146 | 
                +  | 
            |
| 147 | 
                + return true;  | 
            |
| 148 | 
                + }  | 
            |
| 149 | 
                +}  | 
            
                @@ -0,0 +1,92 @@  | 
            ||
| 1 | 
                +/**  | 
            |
| 2 | 
                + * ****************************************************************************  | 
            |
| 3 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 4 | 
                + * <p>  | 
            |
| 5 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 6 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 7 | 
                + * You may obtain a copy of the License at  | 
            |
| 8 | 
                + * <p>  | 
            |
| 9 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 10 | 
                + * <p>  | 
            |
| 11 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 12 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 13 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 14 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 15 | 
                + * limitations under the License.  | 
            |
| 16 | 
                + *******************************************************************************/  | 
            |
| 17 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 18 | 
                +  | 
            |
| 19 | 
                +import android.annotation.TargetApi;  | 
            |
| 20 | 
                +import android.content.Context;  | 
            |
| 21 | 
                +import android.view.MotionEvent;  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                +import com.android.views.rotatephotoview.Compat;  | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                +@TargetApi(5)  | 
            |
| 26 | 
                +public class EclairGestureDetector extends CupcakeGestureDetector {
               | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                + private static final int INVALID_POINTER_ID = -1;  | 
            |
| 29 | 
                + private int mActivePointerId = INVALID_POINTER_ID;  | 
            |
| 30 | 
                + private int mActivePointerIndex = 0;  | 
            |
| 31 | 
                +  | 
            |
| 32 | 
                +    public EclairGestureDetector(Context context) {
               | 
            |
| 33 | 
                + super(context);  | 
            |
| 34 | 
                + }  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + @Override  | 
            |
| 37 | 
                +    float getActiveX(MotionEvent ev) {
               | 
            |
| 38 | 
                +        try {
               | 
            |
| 39 | 
                + return ev.getX(mActivePointerIndex);  | 
            |
| 40 | 
                +        } catch (Exception e) {
               | 
            |
| 41 | 
                + return ev.getX();  | 
            |
| 42 | 
                + }  | 
            |
| 43 | 
                + }  | 
            |
| 44 | 
                +  | 
            |
| 45 | 
                + @Override  | 
            |
| 46 | 
                +    float getActiveY(MotionEvent ev) {
               | 
            |
| 47 | 
                +        try {
               | 
            |
| 48 | 
                + return ev.getY(mActivePointerIndex);  | 
            |
| 49 | 
                +        } catch (Exception e) {
               | 
            |
| 50 | 
                + return ev.getY();  | 
            |
| 51 | 
                + }  | 
            |
| 52 | 
                + }  | 
            |
| 53 | 
                +  | 
            |
| 54 | 
                + @Override  | 
            |
| 55 | 
                +    public boolean onTouchEvent(MotionEvent ev) {
               | 
            |
| 56 | 
                + final int action = ev.getAction();  | 
            |
| 57 | 
                +        switch (action & MotionEvent.ACTION_MASK) {
               | 
            |
| 58 | 
                + case MotionEvent.ACTION_DOWN:  | 
            |
| 59 | 
                + mActivePointerId = ev.getPointerId(0);  | 
            |
| 60 | 
                + break;  | 
            |
| 61 | 
                + case MotionEvent.ACTION_CANCEL:  | 
            |
| 62 | 
                + case MotionEvent.ACTION_UP:  | 
            |
| 63 | 
                + mActivePointerId = INVALID_POINTER_ID;  | 
            |
| 64 | 
                + break;  | 
            |
| 65 | 
                + case MotionEvent.ACTION_POINTER_UP:  | 
            |
| 66 | 
                + // Ignore deprecation, ACTION_POINTER_ID_MASK and  | 
            |
| 67 | 
                + // ACTION_POINTER_ID_SHIFT has same value and are deprecated  | 
            |
| 68 | 
                + // You can have either deprecation or lint target api warning  | 
            |
| 69 | 
                + final int pointerIndex = Compat.getPointerIndex(ev.getAction());  | 
            |
| 70 | 
                + final int pointerId = ev.getPointerId(pointerIndex);  | 
            |
| 71 | 
                +                if (pointerId == mActivePointerId) {
               | 
            |
| 72 | 
                + // This was our active pointer going up. Choose a new  | 
            |
| 73 | 
                + // active pointer and adjust accordingly.  | 
            |
| 74 | 
                + final int newPointerIndex = pointerIndex == 0 ? 1 : 0;  | 
            |
| 75 | 
                + mActivePointerId = ev.getPointerId(newPointerIndex);  | 
            |
| 76 | 
                + mLastTouchX = ev.getX(newPointerIndex);  | 
            |
| 77 | 
                + mLastTouchY = ev.getY(newPointerIndex);  | 
            |
| 78 | 
                + }  | 
            |
| 79 | 
                + break;  | 
            |
| 80 | 
                + }  | 
            |
| 81 | 
                +  | 
            |
| 82 | 
                + mActivePointerIndex = ev  | 
            |
| 83 | 
                + .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId  | 
            |
| 84 | 
                + : 0);  | 
            |
| 85 | 
                +        try {
               | 
            |
| 86 | 
                + return super.onTouchEvent(ev);  | 
            |
| 87 | 
                +        } catch (IllegalArgumentException e) {
               | 
            |
| 88 | 
                + // Fix for support lib bug, happening when onDestroy is  | 
            |
| 89 | 
                + return true;  | 
            |
| 90 | 
                + }  | 
            |
| 91 | 
                + }  | 
            |
| 92 | 
                +}  | 
            
                @@ -0,0 +1,73 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + * <p/>  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + * <p/>  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + * <p/>  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.annotation.TargetApi;  | 
            |
| 19 | 
                +import android.content.Context;  | 
            |
| 20 | 
                +import android.view.MotionEvent;  | 
            |
| 21 | 
                +import android.view.ScaleGestureDetector;  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                +@TargetApi(8)  | 
            |
| 24 | 
                +public class FroyoGestureDetector extends EclairGestureDetector {
               | 
            |
| 25 | 
                +  | 
            |
| 26 | 
                + protected final ScaleGestureDetector mDetector;  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                +    public FroyoGestureDetector(Context context) {
               | 
            |
| 29 | 
                + super(context);  | 
            |
| 30 | 
                +        ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
               | 
            |
| 31 | 
                +  | 
            |
| 32 | 
                + @Override  | 
            |
| 33 | 
                +            public boolean onScale(ScaleGestureDetector detector) {
               | 
            |
| 34 | 
                + float scaleFactor = detector.getScaleFactor();  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))  | 
            |
| 37 | 
                + return false;  | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                + mListener.onScale(scaleFactor,  | 
            |
| 40 | 
                + detector.getFocusX(), detector.getFocusY());  | 
            |
| 41 | 
                + return true;  | 
            |
| 42 | 
                + }  | 
            |
| 43 | 
                +  | 
            |
| 44 | 
                + @Override  | 
            |
| 45 | 
                +            public boolean onScaleBegin(ScaleGestureDetector detector) {
               | 
            |
| 46 | 
                + return true;  | 
            |
| 47 | 
                + }  | 
            |
| 48 | 
                +  | 
            |
| 49 | 
                + @Override  | 
            |
| 50 | 
                +            public void onScaleEnd(ScaleGestureDetector detector) {
               | 
            |
| 51 | 
                + // NO-OP  | 
            |
| 52 | 
                + }  | 
            |
| 53 | 
                + };  | 
            |
| 54 | 
                + mDetector = new ScaleGestureDetector(context, mScaleListener);  | 
            |
| 55 | 
                + }  | 
            |
| 56 | 
                +  | 
            |
| 57 | 
                + @Override  | 
            |
| 58 | 
                +    public boolean isScaling() {
               | 
            |
| 59 | 
                + return mDetector.isInProgress();  | 
            |
| 60 | 
                + }  | 
            |
| 61 | 
                +  | 
            |
| 62 | 
                + @Override  | 
            |
| 63 | 
                +    public boolean onTouchEvent(MotionEvent ev) {
               | 
            |
| 64 | 
                +        try {
               | 
            |
| 65 | 
                + mDetector.onTouchEvent(ev);  | 
            |
| 66 | 
                + return super.onTouchEvent(ev);  | 
            |
| 67 | 
                +        } catch (IllegalArgumentException e) {
               | 
            |
| 68 | 
                + // Fix for support lib bug, happening when onDestroy is  | 
            |
| 69 | 
                + return true;  | 
            |
| 70 | 
                + }  | 
            |
| 71 | 
                + }  | 
            |
| 72 | 
                +  | 
            |
| 73 | 
                +}  | 
            
                @@ -0,0 +1,30 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.view.MotionEvent;  | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                +public interface GestureDetector {
               | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                + boolean onTouchEvent(MotionEvent ev);  | 
            |
| 23 | 
                +  | 
            |
| 24 | 
                + boolean isScaling();  | 
            |
| 25 | 
                +  | 
            |
| 26 | 
                + boolean isDragging();  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                + void setOnGestureListener(OnGestureListener listener);  | 
            |
| 29 | 
                +  | 
            |
| 30 | 
                +}  | 
            
                @@ -0,0 +1,24 @@  | 
            ||
| 1 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +import android.view.MotionEvent;  | 
            |
| 4 | 
                +  | 
            |
| 5 | 
                +/**  | 
            |
| 6 | 
                + * Interface to detect rotation  | 
            |
| 7 | 
                + * Created by ChenSL on 2015/9/16.  | 
            |
| 8 | 
                + */  | 
            |
| 9 | 
                +public interface IRotateDetector {
               | 
            |
| 10 | 
                + /**  | 
            |
| 11 | 
                + * handle rotation in onTouchEvent  | 
            |
| 12 | 
                + *  | 
            |
| 13 | 
                + * @param event The motion event.  | 
            |
| 14 | 
                + * @return True if the event was handled, false otherwise.  | 
            |
| 15 | 
                + */  | 
            |
| 16 | 
                + boolean onTouchEvent(MotionEvent event);  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                + /**  | 
            |
| 19 | 
                + * is the Gesture Rotate  | 
            |
| 20 | 
                + *  | 
            |
| 21 | 
                + * @return true:rotating;false,otherwise  | 
            |
| 22 | 
                + */  | 
            |
| 23 | 
                + boolean isRotating();  | 
            |
| 24 | 
                +}  | 
            
                @@ -0,0 +1,20 @@  | 
            ||
| 1 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +/**  | 
            |
| 4 | 
                + * Interface for a callback for rotation  | 
            |
| 5 | 
                + * Created by ChenSL on 2015/9/16.  | 
            |
| 6 | 
                + */  | 
            |
| 7 | 
                +public interface IRotateListener {
               | 
            |
| 8 | 
                + /**  | 
            |
| 9 | 
                + * callback for rotation  | 
            |
| 10 | 
                + *  | 
            |
| 11 | 
                + * @param degree degree of rotation  | 
            |
| 12 | 
                + */  | 
            |
| 13 | 
                + void rotate(int degree, int pivotX, int pivotY);  | 
            |
| 14 | 
                +  | 
            |
| 15 | 
                + /**  | 
            |
| 16 | 
                + * MotionEvent.ACTION_POINTER_UP happens when two finger minus to only one  | 
            |
| 17 | 
                + * change the ImageView to 0,90,180,270  | 
            |
| 18 | 
                + */  | 
            |
| 19 | 
                + void upRotate(int pivotX, int pivotY);  | 
            |
| 20 | 
                +}  | 
            
                @@ -0,0 +1,27 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +public interface OnGestureListener {
               | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                + void onDrag(float dx, float dy);  | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                + void onFling(float startX, float startY, float velocityX,  | 
            |
| 23 | 
                + float velocityY);  | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                + void onScale(float scaleFactor, float focusX, float focusY);  | 
            |
| 26 | 
                +  | 
            |
| 27 | 
                +}  | 
            
                @@ -0,0 +1,113 @@  | 
            ||
| 1 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +import android.view.MotionEvent;  | 
            |
| 4 | 
                +  | 
            |
| 5 | 
                +/**  | 
            |
| 6 | 
                + * Handle ImageView rotate event with two fingers  | 
            |
| 7 | 
                + * Created by ChenSL on 2015/9/16.  | 
            |
| 8 | 
                + */  | 
            |
| 9 | 
                +public class RotateGestureDetector implements IRotateDetector {
               | 
            |
| 10 | 
                + private int mLastAngle = 0;  | 
            |
| 11 | 
                + private IRotateListener mListener;  | 
            |
| 12 | 
                + private boolean mIsRotate;  | 
            |
| 13 | 
                +  | 
            |
| 14 | 
                + /**  | 
            |
| 15 | 
                + * set rotation listener for callback  | 
            |
| 16 | 
                + *  | 
            |
| 17 | 
                + * @param listener a rotation listener  | 
            |
| 18 | 
                + */  | 
            |
| 19 | 
                +    public void setRotateListener(IRotateListener listener) {
               | 
            |
| 20 | 
                + this.mListener = listener;  | 
            |
| 21 | 
                + }  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                + @Override  | 
            |
| 24 | 
                +    public boolean onTouchEvent(MotionEvent event) {
               | 
            |
| 25 | 
                + return doRotate(event);  | 
            |
| 26 | 
                + }  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                + @Override  | 
            |
| 29 | 
                +    public boolean isRotating() {
               | 
            |
| 30 | 
                + return mIsRotate;  | 
            |
| 31 | 
                + }  | 
            |
| 32 | 
                +  | 
            |
| 33 | 
                + /**  | 
            |
| 34 | 
                + * handle rotation  | 
            |
| 35 | 
                + *  | 
            |
| 36 | 
                + * @param ev Motion event  | 
            |
| 37 | 
                + * @return always true.  | 
            |
| 38 | 
                + */  | 
            |
| 39 | 
                +    private boolean doRotate(MotionEvent ev) {
               | 
            |
| 40 | 
                +        if (ev.getPointerCount() != 2) {
               | 
            |
| 41 | 
                + return false;  | 
            |
| 42 | 
                + }  | 
            |
| 43 | 
                + //Calculate the angle between the two fingers  | 
            |
| 44 | 
                + int pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2;  | 
            |
| 45 | 
                + int pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2;  | 
            |
| 46 | 
                + float deltaX = ev.getX(0) - ev.getX(1);  | 
            |
| 47 | 
                + float deltaY = ev.getY(0) - ev.getY(1);  | 
            |
| 48 | 
                + double radians = Math.atan(deltaY / deltaX);  | 
            |
| 49 | 
                + //Convert to degrees  | 
            |
| 50 | 
                + int degrees = (int) (radians * 180 / Math.PI);  | 
            |
| 51 | 
                + /*  | 
            |
| 52 | 
                + * Must use getActionMasked() for switching to pick up pointer events.  | 
            |
| 53 | 
                + * These events have the pointer index encoded in them so the return  | 
            |
| 54 | 
                + * from getAction() won't match the exact action constant.  | 
            |
| 55 | 
                + */  | 
            |
| 56 | 
                +        switch (ev.getActionMasked()) {
               | 
            |
| 57 | 
                + case MotionEvent.ACTION_DOWN:  | 
            |
| 58 | 
                + mLastAngle = degrees;  | 
            |
| 59 | 
                + mIsRotate = false;  | 
            |
| 60 | 
                + break;  | 
            |
| 61 | 
                + case MotionEvent.ACTION_UP:  | 
            |
| 62 | 
                + mIsRotate = false;  | 
            |
| 63 | 
                + break;  | 
            |
| 64 | 
                + case MotionEvent.ACTION_POINTER_DOWN:  | 
            |
| 65 | 
                + mLastAngle = degrees;  | 
            |
| 66 | 
                + mIsRotate = false;  | 
            |
| 67 | 
                + break;  | 
            |
| 68 | 
                + case MotionEvent.ACTION_CANCEL:  | 
            |
| 69 | 
                + case MotionEvent.ACTION_POINTER_UP:  | 
            |
| 70 | 
                + mIsRotate = false;  | 
            |
| 71 | 
                + upRotate(pivotX, pivotY);  | 
            |
| 72 | 
                + mLastAngle = degrees;  | 
            |
| 73 | 
                + break;  | 
            |
| 74 | 
                + case MotionEvent.ACTION_MOVE:  | 
            |
| 75 | 
                + mIsRotate = true;  | 
            |
| 76 | 
                + int degreesValue = degrees - mLastAngle;  | 
            |
| 77 | 
                +                if (degreesValue > 45) {
               | 
            |
| 78 | 
                + //Going CCW across the boundary  | 
            |
| 79 | 
                + rotate(-5, pivotX, pivotY);  | 
            |
| 80 | 
                +                } else if (degreesValue < -45) {
               | 
            |
| 81 | 
                + //Going CW across the boundary  | 
            |
| 82 | 
                + rotate(5, pivotX, pivotY);  | 
            |
| 83 | 
                +                } else {
               | 
            |
| 84 | 
                + //Normal rotation, rotate the difference  | 
            |
| 85 | 
                + rotate(degreesValue, pivotX, pivotY);  | 
            |
| 86 | 
                + }  | 
            |
| 87 | 
                + //Save the current angle  | 
            |
| 88 | 
                + mLastAngle = degrees;  | 
            |
| 89 | 
                + break;  | 
            |
| 90 | 
                + }  | 
            |
| 91 | 
                + return true;  | 
            |
| 92 | 
                + }  | 
            |
| 93 | 
                +  | 
            |
| 94 | 
                + /**  | 
            |
| 95 | 
                + * to invoke the callback  | 
            |
| 96 | 
                + *  | 
            |
| 97 | 
                + * @param degree degree to rotate  | 
            |
| 98 | 
                + */  | 
            |
| 99 | 
                +    private void rotate(int degree, int pivotX, int pivotY) {
               | 
            |
| 100 | 
                +        if (mListener != null) {
               | 
            |
| 101 | 
                + mListener.rotate(degree, pivotX, pivotY);  | 
            |
| 102 | 
                + }  | 
            |
| 103 | 
                + }  | 
            |
| 104 | 
                +  | 
            |
| 105 | 
                + /**  | 
            |
| 106 | 
                + * to invoke the finger up action  | 
            |
| 107 | 
                + */  | 
            |
| 108 | 
                +    private void upRotate(int pivotX, int pivotY) {
               | 
            |
| 109 | 
                +        if (mListener != null) {
               | 
            |
| 110 | 
                + mListener.upRotate(pivotX, pivotY);  | 
            |
| 111 | 
                + }  | 
            |
| 112 | 
                + }  | 
            |
| 113 | 
                +}  | 
            
                @@ -0,0 +1,42 @@  | 
            ||
| 1 | 
                +package com.android.views.rotatephotoview.gestures;  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +/*******************************************************************************  | 
            |
| 4 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 5 | 
                + *  | 
            |
| 6 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 7 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 8 | 
                + * You may obtain a copy of the License at  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 11 | 
                + *  | 
            |
| 12 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 13 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 14 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 15 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 16 | 
                + * limitations under the License.  | 
            |
| 17 | 
                + *******************************************************************************/  | 
            |
| 18 | 
                +  | 
            |
| 19 | 
                +import android.content.Context;  | 
            |
| 20 | 
                +import android.os.Build;  | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                +public final class VersionedGestureDetector {
               | 
            |
| 23 | 
                +  | 
            |
| 24 | 
                + public static GestureDetector newInstance(Context context,  | 
            |
| 25 | 
                +                                              OnGestureListener listener) {
               | 
            |
| 26 | 
                + final int sdkVersion = Build.VERSION.SDK_INT;  | 
            |
| 27 | 
                + GestureDetector detector;  | 
            |
| 28 | 
                +  | 
            |
| 29 | 
                +        if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
               | 
            |
| 30 | 
                + detector = new CupcakeGestureDetector(context);  | 
            |
| 31 | 
                +        } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
               | 
            |
| 32 | 
                + detector = new EclairGestureDetector(context);  | 
            |
| 33 | 
                +        } else {
               | 
            |
| 34 | 
                + detector = new FroyoGestureDetector(context);  | 
            |
| 35 | 
                + }  | 
            |
| 36 | 
                +  | 
            |
| 37 | 
                + detector.setOnGestureListener(listener);  | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                + return detector;  | 
            |
| 40 | 
                + }  | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                +}  | 
            
                @@ -0,0 +1,35 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.log;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.util.Log;  | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                +/**  | 
            |
| 21 | 
                + * class that holds the {@link Logger} for this library, defaults to {@link LoggerDefault} to send logs to android {@link Log}
               | 
            |
| 22 | 
                + */  | 
            |
| 23 | 
                +public final class LogManager {
               | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                + private static Logger logger = new LoggerDefault();  | 
            |
| 26 | 
                +  | 
            |
| 27 | 
                +    public static void setLogger(Logger newLogger) {
               | 
            |
| 28 | 
                + logger = newLogger;  | 
            |
| 29 | 
                + }  | 
            |
| 30 | 
                +  | 
            |
| 31 | 
                +    public static Logger getLogger() {
               | 
            |
| 32 | 
                + return logger;  | 
            |
| 33 | 
                + }  | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                +}  | 
            
                @@ -0,0 +1,116 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.log;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +/**  | 
            |
| 19 | 
                + * interface for a logger class to replace the static calls to {@link android.util.Log}
               | 
            |
| 20 | 
                + */  | 
            |
| 21 | 
                +public interface Logger {
               | 
            |
| 22 | 
                + /**  | 
            |
| 23 | 
                +     * Send a {@link android.util.Log#VERBOSE} log message.
               | 
            |
| 24 | 
                + *  | 
            |
| 25 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 26 | 
                + * the class or activity where the log call occurs.  | 
            |
| 27 | 
                + * @param msg The message you would like logged.  | 
            |
| 28 | 
                + */  | 
            |
| 29 | 
                + int v(String tag, String msg);  | 
            |
| 30 | 
                +  | 
            |
| 31 | 
                + /**  | 
            |
| 32 | 
                +     * Send a {@link android.util.Log#VERBOSE} log message and log the exception.
               | 
            |
| 33 | 
                + *  | 
            |
| 34 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 35 | 
                + * the class or activity where the log call occurs.  | 
            |
| 36 | 
                + * @param msg The message you would like logged.  | 
            |
| 37 | 
                + * @param tr An exception to log  | 
            |
| 38 | 
                + */  | 
            |
| 39 | 
                + int v(String tag, String msg, Throwable tr);  | 
            |
| 40 | 
                +  | 
            |
| 41 | 
                + /**  | 
            |
| 42 | 
                +     * Send a {@link android.util.Log#DEBUG} log message.
               | 
            |
| 43 | 
                + *  | 
            |
| 44 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 45 | 
                + * the class or activity where the log call occurs.  | 
            |
| 46 | 
                + * @param msg The message you would like logged.  | 
            |
| 47 | 
                + */  | 
            |
| 48 | 
                + int d(String tag, String msg);  | 
            |
| 49 | 
                +  | 
            |
| 50 | 
                + /**  | 
            |
| 51 | 
                +     * Send a {@link android.util.Log#DEBUG} log message and log the exception.
               | 
            |
| 52 | 
                + *  | 
            |
| 53 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 54 | 
                + * the class or activity where the log call occurs.  | 
            |
| 55 | 
                + * @param msg The message you would like logged.  | 
            |
| 56 | 
                + * @param tr An exception to log  | 
            |
| 57 | 
                + */  | 
            |
| 58 | 
                + int d(String tag, String msg, Throwable tr);  | 
            |
| 59 | 
                +  | 
            |
| 60 | 
                + /**  | 
            |
| 61 | 
                +     * Send an {@link android.util.Log#INFO} log message.
               | 
            |
| 62 | 
                + *  | 
            |
| 63 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 64 | 
                + * the class or activity where the log call occurs.  | 
            |
| 65 | 
                + * @param msg The message you would like logged.  | 
            |
| 66 | 
                + */  | 
            |
| 67 | 
                + int i(String tag, String msg);  | 
            |
| 68 | 
                +  | 
            |
| 69 | 
                + /**  | 
            |
| 70 | 
                +     * Send a {@link android.util.Log#INFO} log message and log the exception.
               | 
            |
| 71 | 
                + *  | 
            |
| 72 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 73 | 
                + * the class or activity where the log call occurs.  | 
            |
| 74 | 
                + * @param msg The message you would like logged.  | 
            |
| 75 | 
                + * @param tr An exception to log  | 
            |
| 76 | 
                + */  | 
            |
| 77 | 
                + int i(String tag, String msg, Throwable tr);  | 
            |
| 78 | 
                +  | 
            |
| 79 | 
                + /**  | 
            |
| 80 | 
                +     * Send a {@link android.util.Log#WARN} log message.
               | 
            |
| 81 | 
                + *  | 
            |
| 82 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 83 | 
                + * the class or activity where the log call occurs.  | 
            |
| 84 | 
                + * @param msg The message you would like logged.  | 
            |
| 85 | 
                + */  | 
            |
| 86 | 
                + int w(String tag, String msg);  | 
            |
| 87 | 
                +  | 
            |
| 88 | 
                + /**  | 
            |
| 89 | 
                +     * Send a {@link android.util.Log#WARN} log message and log the exception.
               | 
            |
| 90 | 
                + *  | 
            |
| 91 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 92 | 
                + * the class or activity where the log call occurs.  | 
            |
| 93 | 
                + * @param msg The message you would like logged.  | 
            |
| 94 | 
                + * @param tr An exception to log  | 
            |
| 95 | 
                + */  | 
            |
| 96 | 
                + int w(String tag, String msg, Throwable tr);  | 
            |
| 97 | 
                +  | 
            |
| 98 | 
                + /**  | 
            |
| 99 | 
                +     * Send an {@link android.util.Log#ERROR} log message.
               | 
            |
| 100 | 
                + *  | 
            |
| 101 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 102 | 
                + * the class or activity where the log call occurs.  | 
            |
| 103 | 
                + * @param msg The message you would like logged.  | 
            |
| 104 | 
                + */  | 
            |
| 105 | 
                + int e(String tag, String msg);  | 
            |
| 106 | 
                +  | 
            |
| 107 | 
                + /**  | 
            |
| 108 | 
                +     * Send a {@link android.util.Log#ERROR} log message and log the exception.
               | 
            |
| 109 | 
                + *  | 
            |
| 110 | 
                + * @param tag Used to identify the source of a log message. It usually identifies  | 
            |
| 111 | 
                + * the class or activity where the log call occurs.  | 
            |
| 112 | 
                + * @param msg The message you would like logged.  | 
            |
| 113 | 
                + * @param tr An exception to log  | 
            |
| 114 | 
                + */  | 
            |
| 115 | 
                + int e(String tag, String msg, Throwable tr);  | 
            |
| 116 | 
                +}  | 
            
                @@ -0,0 +1,76 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.log;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.util.Log;  | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                +/**  | 
            |
| 21 | 
                + * Helper class to redirect {@link LogManager#logger} to {@link Log}
               | 
            |
| 22 | 
                + */  | 
            |
| 23 | 
                +public class LoggerDefault implements Logger {
               | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                + @Override  | 
            |
| 26 | 
                +    public int v(String tag, String msg) {
               | 
            |
| 27 | 
                + return Log.v(tag, msg);  | 
            |
| 28 | 
                + }  | 
            |
| 29 | 
                +  | 
            |
| 30 | 
                + @Override  | 
            |
| 31 | 
                +    public int v(String tag, String msg, Throwable tr) {
               | 
            |
| 32 | 
                + return Log.v(tag, msg, tr);  | 
            |
| 33 | 
                + }  | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                + @Override  | 
            |
| 36 | 
                +    public int d(String tag, String msg) {
               | 
            |
| 37 | 
                + return Log.d(tag, msg);  | 
            |
| 38 | 
                + }  | 
            |
| 39 | 
                +  | 
            |
| 40 | 
                + @Override  | 
            |
| 41 | 
                +    public int d(String tag, String msg, Throwable tr) {
               | 
            |
| 42 | 
                + return Log.d(tag, msg, tr);  | 
            |
| 43 | 
                + }  | 
            |
| 44 | 
                +  | 
            |
| 45 | 
                + @Override  | 
            |
| 46 | 
                +    public int i(String tag, String msg) {
               | 
            |
| 47 | 
                + return Log.i(tag, msg);  | 
            |
| 48 | 
                + }  | 
            |
| 49 | 
                +  | 
            |
| 50 | 
                + @Override  | 
            |
| 51 | 
                +    public int i(String tag, String msg, Throwable tr) {
               | 
            |
| 52 | 
                + return Log.i(tag, msg, tr);  | 
            |
| 53 | 
                + }  | 
            |
| 54 | 
                +  | 
            |
| 55 | 
                + @Override  | 
            |
| 56 | 
                +    public int w(String tag, String msg) {
               | 
            |
| 57 | 
                + return Log.w(tag, msg);  | 
            |
| 58 | 
                + }  | 
            |
| 59 | 
                +  | 
            |
| 60 | 
                + @Override  | 
            |
| 61 | 
                +    public int w(String tag, String msg, Throwable tr) {
               | 
            |
| 62 | 
                + return Log.w(tag, msg, tr);  | 
            |
| 63 | 
                + }  | 
            |
| 64 | 
                +  | 
            |
| 65 | 
                + @Override  | 
            |
| 66 | 
                +    public int e(String tag, String msg) {
               | 
            |
| 67 | 
                + return Log.e(tag, msg);  | 
            |
| 68 | 
                + }  | 
            |
| 69 | 
                +  | 
            |
| 70 | 
                + @Override  | 
            |
| 71 | 
                +    public int e(String tag, String msg, Throwable tr) {
               | 
            |
| 72 | 
                + return Log.e(tag, msg, tr);  | 
            |
| 73 | 
                + }  | 
            |
| 74 | 
                +  | 
            |
| 75 | 
                +  | 
            |
| 76 | 
                +}  | 
            
                @@ -0,0 +1,61 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.scrollerproxy;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.annotation.TargetApi;  | 
            |
| 19 | 
                +import android.content.Context;  | 
            |
| 20 | 
                +import android.widget.OverScroller;  | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                +@TargetApi(9)  | 
            |
| 23 | 
                +public class GingerScroller extends ScrollerProxy {
               | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                + protected final OverScroller mScroller;  | 
            |
| 26 | 
                +  | 
            |
| 27 | 
                +    public GingerScroller(Context context) {
               | 
            |
| 28 | 
                + mScroller = new OverScroller(context);  | 
            |
| 29 | 
                + }  | 
            |
| 30 | 
                +  | 
            |
| 31 | 
                + @Override  | 
            |
| 32 | 
                +    public boolean computeScrollOffset() {
               | 
            |
| 33 | 
                + return mScroller.computeScrollOffset();  | 
            |
| 34 | 
                + }  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + @Override  | 
            |
| 37 | 
                + public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,  | 
            |
| 38 | 
                +                      int overX, int overY) {
               | 
            |
| 39 | 
                + mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY);  | 
            |
| 40 | 
                + }  | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                + @Override  | 
            |
| 43 | 
                +    public void forceFinished(boolean finished) {
               | 
            |
| 44 | 
                + mScroller.forceFinished(finished);  | 
            |
| 45 | 
                + }  | 
            |
| 46 | 
                +  | 
            |
| 47 | 
                + @Override  | 
            |
| 48 | 
                +    public boolean isFinished() {
               | 
            |
| 49 | 
                + return mScroller.isFinished();  | 
            |
| 50 | 
                + }  | 
            |
| 51 | 
                +  | 
            |
| 52 | 
                + @Override  | 
            |
| 53 | 
                +    public int getCurrX() {
               | 
            |
| 54 | 
                + return mScroller.getCurrX();  | 
            |
| 55 | 
                + }  | 
            |
| 56 | 
                +  | 
            |
| 57 | 
                + @Override  | 
            |
| 58 | 
                +    public int getCurrY() {
               | 
            |
| 59 | 
                + return mScroller.getCurrY();  | 
            |
| 60 | 
                + }  | 
            |
| 61 | 
                +}  | 
            
                @@ -0,0 +1,33 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.scrollerproxy;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.annotation.TargetApi;  | 
            |
| 19 | 
                +import android.content.Context;  | 
            |
| 20 | 
                +  | 
            |
| 21 | 
                +@TargetApi(14)  | 
            |
| 22 | 
                +public class IcsScroller extends GingerScroller {
               | 
            |
| 23 | 
                +  | 
            |
| 24 | 
                +    public IcsScroller(Context context) {
               | 
            |
| 25 | 
                + super(context);  | 
            |
| 26 | 
                + }  | 
            |
| 27 | 
                +  | 
            |
| 28 | 
                + @Override  | 
            |
| 29 | 
                +    public boolean computeScrollOffset() {
               | 
            |
| 30 | 
                + return mScroller.computeScrollOffset();  | 
            |
| 31 | 
                + }  | 
            |
| 32 | 
                +  | 
            |
| 33 | 
                +}  | 
            
                @@ -0,0 +1,58 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.scrollerproxy;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.content.Context;  | 
            |
| 19 | 
                +import android.widget.Scroller;  | 
            |
| 20 | 
                +  | 
            |
| 21 | 
                +public class PreGingerScroller extends ScrollerProxy {
               | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                + private final Scroller mScroller;  | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                +    public PreGingerScroller(Context context) {
               | 
            |
| 26 | 
                + mScroller = new Scroller(context);  | 
            |
| 27 | 
                + }  | 
            |
| 28 | 
                +  | 
            |
| 29 | 
                + @Override  | 
            |
| 30 | 
                +    public boolean computeScrollOffset() {
               | 
            |
| 31 | 
                + return mScroller.computeScrollOffset();  | 
            |
| 32 | 
                + }  | 
            |
| 33 | 
                +  | 
            |
| 34 | 
                + @Override  | 
            |
| 35 | 
                + public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,  | 
            |
| 36 | 
                +                      int overX, int overY) {
               | 
            |
| 37 | 
                + mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);  | 
            |
| 38 | 
                + }  | 
            |
| 39 | 
                +  | 
            |
| 40 | 
                + @Override  | 
            |
| 41 | 
                +    public void forceFinished(boolean finished) {
               | 
            |
| 42 | 
                + mScroller.forceFinished(finished);  | 
            |
| 43 | 
                + }  | 
            |
| 44 | 
                +  | 
            |
| 45 | 
                +    public boolean isFinished() {
               | 
            |
| 46 | 
                + return mScroller.isFinished();  | 
            |
| 47 | 
                + }  | 
            |
| 48 | 
                +  | 
            |
| 49 | 
                + @Override  | 
            |
| 50 | 
                +    public int getCurrX() {
               | 
            |
| 51 | 
                + return mScroller.getCurrX();  | 
            |
| 52 | 
                + }  | 
            |
| 53 | 
                +  | 
            |
| 54 | 
                + @Override  | 
            |
| 55 | 
                +    public int getCurrY() {
               | 
            |
| 56 | 
                + return mScroller.getCurrY();  | 
            |
| 57 | 
                + }  | 
            |
| 58 | 
                +}  | 
            
                @@ -0,0 +1,48 @@  | 
            ||
| 1 | 
                +/*******************************************************************************  | 
            |
| 2 | 
                + * Copyright 2011, 2012 Chris Banes.  | 
            |
| 3 | 
                + *  | 
            |
| 4 | 
                + * Licensed under the Apache License, Version 2.0 (the "License");  | 
            |
| 5 | 
                + * you may not use this file except in compliance with the License.  | 
            |
| 6 | 
                + * You may obtain a copy of the License at  | 
            |
| 7 | 
                + *  | 
            |
| 8 | 
                + * http://www.apache.org/licenses/LICENSE-2.0  | 
            |
| 9 | 
                + *  | 
            |
| 10 | 
                + * Unless required by applicable law or agreed to in writing, software  | 
            |
| 11 | 
                + * distributed under the License is distributed on an "AS IS" BASIS,  | 
            |
| 12 | 
                + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
            |
| 13 | 
                + * See the License for the specific language governing permissions and  | 
            |
| 14 | 
                + * limitations under the License.  | 
            |
| 15 | 
                + *******************************************************************************/  | 
            |
| 16 | 
                +package com.android.views.rotatephotoview.scrollerproxy;  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +import android.content.Context;  | 
            |
| 19 | 
                +import android.os.Build.VERSION;  | 
            |
| 20 | 
                +import android.os.Build.VERSION_CODES;  | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                +public abstract class ScrollerProxy {
               | 
            |
| 23 | 
                +  | 
            |
| 24 | 
                +    public static ScrollerProxy getScroller(Context context) {
               | 
            |
| 25 | 
                +        if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
               | 
            |
| 26 | 
                + return new PreGingerScroller(context);  | 
            |
| 27 | 
                +        } else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
               | 
            |
| 28 | 
                + return new GingerScroller(context);  | 
            |
| 29 | 
                +        } else {
               | 
            |
| 30 | 
                + return new IcsScroller(context);  | 
            |
| 31 | 
                + }  | 
            |
| 32 | 
                + }  | 
            |
| 33 | 
                +  | 
            |
| 34 | 
                + public abstract boolean computeScrollOffset();  | 
            |
| 35 | 
                +  | 
            |
| 36 | 
                + public abstract void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY,  | 
            |
| 37 | 
                + int maxY, int overX, int overY);  | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                + public abstract void forceFinished(boolean finished);  | 
            |
| 40 | 
                +  | 
            |
| 41 | 
                + public abstract boolean isFinished();  | 
            |
| 42 | 
                +  | 
            |
| 43 | 
                + public abstract int getCurrX();  | 
            |
| 44 | 
                +  | 
            |
| 45 | 
                + public abstract int getCurrY();  | 
            |
| 46 | 
                +  | 
            |
| 47 | 
                +  | 
            |
| 48 | 
                +}  |