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