de-diff">
@@ -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
+}

Kodo/kodo - Gogs: Go Git Service

132 Commissions (e1b319d9a4c07ffd56112cc423a5f891bc51d7fe)

Auteur SHA1 Message Date
  Brightcells e1b319d9a4 'charset': 'utf8mb4' 10 ans auparavant
  Brightcells a8d890cb70 filter order by pay_status 10 ans auparavant
  Brightcells 03304ad48a add api guest_status_api & modify api guest_login_api 10 ans auparavant
  Brightcells c209e83d29 add delete_guest_entrance_control 10 ans auparavant
  Brightcells 63eaee0951 modify guest_login_api 10 ans auparavant
  Brightcells 70689e758c add lensman and user's balance 10 ans auparavant
  Brightcells 269576f8b9 add guest user 10 ans auparavant
  Brightcells 818e5ae2cd adjust page 10 ans auparavant
  Brightcells 10fdf9ba1f adjust page 10 ans auparavant
  Brightcells 805b221885 add content for contact_us.html 10 ans auparavant
  Brightcells 60b2bc0c09 add content for user_agreement.html 10 ans auparavant
  Brightcells 95c1d32792 put isort and pep8 in check.sh 10 ans auparavant
  Brightcells fabef63211 set line_length=200 for isort 10 ans auparavant
  Brightcells 084a5eece8 order ruler: date/self/7*thumbup_num+3*comment_num/id 10 ans auparavant
  Brightcells 81e5a71d7f GroupUserInfo order by id desc in group_list_api 10 ans auparavant
  Brightcells f72ccb1875 isort import 10 ans auparavant
  Brightcells de60d59c26 order ruler: date/self/thumbup_num/id 10 ans auparavant
  Brightcells f684a1d89c order ruler: date/self/thumbup_num 10 ans auparavant
  Brightcells 5bf645c33d change order ruler for paiai home, user_id self upload first 10 ans auparavant
  Brightcells 414e2cb1b0 change some {number} to {} 10 ans auparavant
  Brightcells d1ef8ec9e9 download page 10 ans auparavant
  Brightcells ee258aeb8b GroupPhotoOrderInfo value set error 10 ans auparavant
  Brightcells b7a21719c7 change message_list_api to return unread message num 10 ans auparavant
  Brightcells bc49c77a94 adjust settings of redis 10 ans auparavant
  Brightcells 6f4b4a7f5c unify fee(return and params) to fen 10 ans auparavant
  Brightcells 102152ca4a add api lensman_photo_bought & modify api wx_order_create_api and adjust return field 10 ans auparavant
  Brightcells cb9a21132f modify lesman_photo_price api 10 ans auparavant
  Brightcells 8aeca53563 record unionid for wx_authorize user 10 ans auparavant
  Brightcells 50ebe3bfcd add company name in website 10 ans auparavant
  Brightcells efaa518b96 add Only Once Function refresh_thumbnail 10 ans auparavant
  Brightcells d0f8b5ce5c larger image file 403 forbidden 10 ans auparavant
  Brightcells d64b3c216b add and return photo_thumbnail2 relative 10 ans auparavant
  Brightcells a0dfcb6f16 Alter field pay_status on orderinfo 10 ans auparavant
  Brightcells 98f968c53c add api lesman_photo_price/wx_order_list_api/wx_order_detail_api 10 ans auparavant
  Brightcells 0f425ae151 add api wx_order_query_api 10 ans auparavant
  Brightcells 097426009e Fix Bug: sign error when trade_type == 'APP' 10 ans auparavant
  Brightcells 0e1b1fb160 adjust WECHAT in settings.py & use django-logit 10 ans auparavant
  Brightcells fab8ef6c5e add term of service page 10 ans auparavant
  Brightcells 03c3611ad0 improve image clarity 10 ans auparavant
  Brightcells 3114315824 add pc official website 10 ans auparavant
  Brightcells 5695243a9a Fix Bug: creator of group not in GROUP_USERS_PASSED_SET 10 ans auparavant
  Brightcells ed5ee99a08 Fix Bug: get_group_info wrong use as get_group_users_info 10 ans auparavant
  Brightcells 5301e7237e unify join group logic 10 ans auparavant
  Brightcells 02373a6dff return complete group_photo_info for api message_list_api/message_type_list_api 10 ans auparavant
  Brightcells aa78f33cca Fix Bug: group admin request group_join_api 10 ans auparavant
  Brightcells 349e60836f Fix Bug: set() takes at least 3 arguments (2 given) & change r.set to r.setex 10 ans auparavant
  Brightcells 15772543dd add return field thumbup for api flyimg_upload_api/flyimg_list_api/session_detail_api/pai2_home_api 10 ans auparavant
  Brightcells f0113f2b5c Fix Bug: get group_info/group_users when not created 10 ans auparavant
  Brightcells a53a8a8312 modify session_detail_api to deal with group/group_user/group_photo 10 ans auparavant
  Brightcells ec142555be return group_from for pai2_home_api 10 ans auparavant