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