照片反转功能

chengzhenyu 8 jaren geleden
bovenliggende
commit
c7500c2ced
24 gewijzigde bestanden met toevoegingen van 3134 en 4 verwijderingen
  1. 13 2
      app/src/main/java/ai/pai/client/adapter/FullScreenPhotoPageAdapter.java
  2. 1 1
      app/src/main/res/layout/item_pager_touchable_photo.xml
  3. 22 1
      views/src/main/java/com/android/views/PhotoView/TouchImageView.java
  4. 59 0
      views/src/main/java/com/android/views/rotatephotoview/Compat.java
  5. 100 0
      views/src/main/java/com/android/views/rotatephotoview/DefaultOnDoubleTapListener.java
  6. 285 0
      views/src/main/java/com/android/views/rotatephotoview/IPhotoView.java
  7. 284 0
      views/src/main/java/com/android/views/rotatephotoview/PhotoView.java
  8. 1373 0
      views/src/main/java/com/android/views/rotatephotoview/PhotoViewAttacher.java
  9. 149 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/CupcakeGestureDetector.java
  10. 92 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/EclairGestureDetector.java
  11. 73 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/FroyoGestureDetector.java
  12. 30 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/GestureDetector.java
  13. 24 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/IRotateDetector.java
  14. 20 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/IRotateListener.java
  15. 27 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/OnGestureListener.java
  16. 113 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/RotateGestureDetector.java
  17. 42 0
      views/src/main/java/com/android/views/rotatephotoview/gestures/VersionedGestureDetector.java
  18. 35 0
      views/src/main/java/com/android/views/rotatephotoview/log/LogManager.java
  19. 116 0
      views/src/main/java/com/android/views/rotatephotoview/log/Logger.java
  20. 76 0
      views/src/main/java/com/android/views/rotatephotoview/log/LoggerDefault.java
  21. 61 0
      views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/GingerScroller.java
  22. 33 0
      views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/IcsScroller.java
  23. 58 0
      views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/PreGingerScroller.java
  24. 48 0
      views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/ScrollerProxy.java

+ 13 - 2
app/src/main/java/ai/pai/client/adapter/FullScreenPhotoPageAdapter.java

@@ -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(){

+ 1 - 1
app/src/main/res/layout/item_pager_touchable_photo.xml

@@ -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"

+ 22 - 1
views/src/main/java/com/android/views/PhotoView/TouchImageView.java

@@ -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();

+ 59 - 0
views/src/main/java/com/android/views/rotatephotoview/Compat.java

@@ -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
+}

+ 100 - 0
views/src/main/java/com/android/views/rotatephotoview/DefaultOnDoubleTapListener.java

@@ -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>&nbsp;</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
+}

+ 285 - 0
views/src/main/java/com/android/views/rotatephotoview/IPhotoView.java

@@ -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
+}

+ 284 - 0
views/src/main/java/com/android/views/rotatephotoview/PhotoView.java

@@ -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
+}

+ 1373 - 0
views/src/main/java/com/android/views/rotatephotoview/PhotoViewAttacher.java

@@ -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
+}

+ 149 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/CupcakeGestureDetector.java

@@ -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
+}

+ 92 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/EclairGestureDetector.java

@@ -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
+}

+ 73 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/FroyoGestureDetector.java

@@ -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
+}

+ 30 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/GestureDetector.java

@@ -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
+}

+ 24 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/IRotateDetector.java

@@ -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
+}

+ 20 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/IRotateListener.java

@@ -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
+}

+ 27 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/OnGestureListener.java

@@ -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
+}

+ 113 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/RotateGestureDetector.java

@@ -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
+}

+ 42 - 0
views/src/main/java/com/android/views/rotatephotoview/gestures/VersionedGestureDetector.java

@@ -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
+}

+ 35 - 0
views/src/main/java/com/android/views/rotatephotoview/log/LogManager.java

@@ -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
+}

+ 116 - 0
views/src/main/java/com/android/views/rotatephotoview/log/Logger.java

@@ -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
+}

+ 76 - 0
views/src/main/java/com/android/views/rotatephotoview/log/LoggerDefault.java

@@ -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
+}

+ 61 - 0
views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/GingerScroller.java

@@ -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
+}

+ 33 - 0
views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/IcsScroller.java

@@ -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
+}

+ 58 - 0
views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/PreGingerScroller.java

@@ -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
+}

+ 48 - 0
views/src/main/java/com/android/views/rotatephotoview/scrollerproxy/ScrollerProxy.java

@@ -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
+}