Options to disable pan and zoom gestures
diff --git a/README.md b/README.md
index f839bf3..3d4415e 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@
 * Pan while zooming
 * Seamless switch between pan and zoom
 * Fling momentum after panning
+* Options to disable pan and/or zoom gestures
 
 #### Overridable event detection
 * Supports `OnClickListener` and `OnLongClickListener`
diff --git a/library/res/values/attrs.xml b/library/res/values/attrs.xml
index 5ef45d1..8d7ad12 100644
--- a/library/res/values/attrs.xml
+++ b/library/res/values/attrs.xml
@@ -18,6 +18,8 @@
 
     <declare-styleable name="SubsamplingScaleImageView">
         <attr name="assetName" format="string"/>
+        <attr name="panEnabled" format="boolean"/>
+        <attr name="zoomEnabled" format="boolean"/>
     </declare-styleable>
 
 </resources>
\ No newline at end of file
diff --git a/library/src/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java b/library/src/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java
index 5ee4b85..eb71db2 100644
--- a/library/src/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java
+++ b/library/src/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java
@@ -69,6 +69,10 @@
     // Max scale allowed (prevent infinite zoom)
     private float maxScale = 2F;
 
+    // Gesture detection settings
+    private boolean panEnabled = true;
+    private boolean zoomEnabled = true;
+
     // Current scale and scale at start of zoom
     private float scale;
     private float scaleStart;
@@ -80,6 +84,7 @@
     // Source coordinate to center on, used when new position is set externally before view is ready
     private Float pendingScale;
     private PointF sPendingCenter;
+    private PointF sRequestedCenter;
 
     // Source image dimensions and orientation - dimensions relate to the unrotated image
     private int sWidth;
@@ -90,8 +95,8 @@
     private boolean isZooming;
     // Is one-finger panning in progress
     private boolean isPanning;
-    // Is an press in progress
-    private boolean isPressed;
+    // Max touches used in current gesture
+    private int maxTouchCount;
 
     // Fling detector
     private GestureDetector detector;
@@ -130,7 +135,7 @@
         this.handler = new Handler(new Handler.Callback() {
             public boolean handleMessage(Message message) {
                 if (message.what == MESSAGE_LONG_CLICK && onLongClickListener != null) {
-                    isPressed = false;
+                    maxTouchCount = 0;
                     SubsamplingScaleImageView.super.setOnLongClickListener(onLongClickListener);
                     performLongClick();
                     SubsamplingScaleImageView.super.setOnLongClickListener(null);
@@ -141,7 +146,7 @@
         this.detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
             @Override
             public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-                if (readySent && vTranslate != null && (Math.abs(e1.getX() - e2.getX()) > 50 || Math.abs(e1.getY() - e2.getY()) > 50) && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) && !isZooming) {
+                if (panEnabled && readySent && vTranslate != null && (Math.abs(e1.getX() - e2.getX()) > 50 || Math.abs(e1.getY() - e2.getY()) > 50) && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) && !isZooming) {
                     flingMomentum = new PointF(velocityX * 0.5f, velocityY * 0.5f);
                     flingFrom = new PointF(vTranslate.x, vTranslate.y);
                     flingStart = System.currentTimeMillis();
@@ -160,6 +165,12 @@
                     setImageAsset(assetName);
                 }
             }
+            if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_panEnabled)) {
+                setPanEnabled(typedAttr.getBoolean(styleable.SubsamplingScaleImageView_panEnabled, true));
+            }
+            if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_zoomEnabled)) {
+                setZoomEnabled(typedAttr.getBoolean(styleable.SubsamplingScaleImageView_zoomEnabled, true));
+            }
         }
     }
 
@@ -240,9 +251,10 @@
         vTranslateStart = null;
         pendingScale = 0f;
         sPendingCenter = null;
+        sRequestedCenter = null;
         isZooming = false;
         isPanning = false;
-        isPressed = false;
+        maxTouchCount = 0;
         fullImageSampleSize = 0;
         vCenterStart = null;
         vDistStart = 0;
@@ -343,15 +355,19 @@
             case MotionEvent.ACTION_POINTER_1_DOWN:
             case MotionEvent.ACTION_POINTER_2_DOWN:
                 getParent().requestDisallowInterceptTouchEvent(true);
-                isPressed = true;
+                maxTouchCount = Math.max(maxTouchCount, touchCount);
                 if (touchCount >= 2) {
-                    // Start pinch to zoom. Calculate distance between touch points and center point of the pinch.
-                    float distance = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
-                    scaleStart = scale;
-                    vDistStart = distance;
-                    vTranslateStart = new PointF(vTranslate.x, vTranslate.y);
-                    vCenterStart = new PointF((event.getX(0) + event.getX(1))/2, (event.getY(0) + event.getY(1))/2);
-
+                    if (zoomEnabled) {
+                        // Start pinch to zoom. Calculate distance between touch points and center point of the pinch.
+                        float distance = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
+                        scaleStart = scale;
+                        vDistStart = distance;
+                        vTranslateStart = new PointF(vTranslate.x, vTranslate.y);
+                        vCenterStart = new PointF((event.getX(0) + event.getX(1))/2, (event.getY(0) + event.getY(1))/2);
+                    } else {
+                        // Abort all gestures on second touch
+                        maxTouchCount = 0;
+                    }
                     // Cancel long click timer
                     handler.removeMessages(MESSAGE_LONG_CLICK);
                 } else {
@@ -365,33 +381,44 @@
                 return true;
             case MotionEvent.ACTION_MOVE:
                 boolean consumed = false;
-                if (isPressed) {
+                if (maxTouchCount > 0) {
                     if (touchCount >= 2) {
                         // Calculate new distance between touch points, to scale and pan relative to start values.
                         vDistEnd = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                         vCenterEnd = new PointF((event.getX(0) + event.getX(1))/2, (event.getY(0) + event.getY(1))/2);
 
-                        if (distance(vCenterStart.x, vCenterEnd.x, vCenterStart.y, vCenterEnd.y) > 5 || Math.abs(vDistEnd - vDistStart) > 5 || isPanning) {
+                        if (zoomEnabled && (distance(vCenterStart.x, vCenterEnd.x, vCenterStart.y, vCenterEnd.y) > 5 || Math.abs(vDistEnd - vDistStart) > 5 || isPanning)) {
                             isZooming = true;
                             isPanning = true;
                             consumed = true;
 
                             scale = Math.min(maxScale, (vDistEnd / vDistStart) * scaleStart);
 
-                            // Translate to place the source image coordinate that was at the center of the pinch at the start
-                            // at the center of the pinch now, to give simultaneous pan + zoom.
-                            float vLeftStart = vCenterStart.x - vTranslateStart.x;
-                            float vTopStart = vCenterStart.y - vTranslateStart.y;
-                            float vLeftNow = vLeftStart * (scale/scaleStart);
-                            float vTopNow = vTopStart * (scale/scaleStart);
-                            vTranslate.x = vCenterEnd.x - vLeftNow;
-                            vTranslate.y = vCenterEnd.y - vTopNow;
+                            if (panEnabled) {
+                                // Translate to place the source image coordinate that was at the center of the pinch at the start
+                                // at the center of the pinch now, to give simultaneous pan + zoom.
+                                float vLeftStart = vCenterStart.x - vTranslateStart.x;
+                                float vTopStart = vCenterStart.y - vTranslateStart.y;
+                                float vLeftNow = vLeftStart * (scale/scaleStart);
+                                float vTopNow = vTopStart * (scale/scaleStart);
+                                vTranslate.x = vCenterEnd.x - vLeftNow;
+                                vTranslate.y = vCenterEnd.y - vTopNow;
+                            } else if (sRequestedCenter != null) {
+                                // With a center specified from code, zoom around that point.
+                                vTranslate.x = (getWidth()/2) - (scale * sRequestedCenter.x);
+                                vTranslate.y = (getHeight()/2) - (scale * sRequestedCenter.y);
+                            } else {
+                                // With no requested center, scale around the image center.
+                                vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2));
+                                vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2));
+                            }
 
                             fitToBounds();
                             refreshRequiredTiles(false);
                         }
                     } else if (!isZooming) {
-                        // One finger pan - translate the image
+                        // One finger pan - translate the image. We do this calculation even with pan disabled so click
+                        // and long click behaviour is preserved.
                         float dx = Math.abs(event.getX() - vCenterStart.x);
                         float dy = Math.abs(event.getY() - vCenterStart.y);
                         if (dx > 5 || dy > 5 || isPanning) {
@@ -406,11 +433,17 @@
                                 isPanning = true;
                             } else if (dx > 5) {
                                 // Haven't panned the image, and we're at the left or right edge. Switch to page swipe.
-                                isPressed = false;
+                                maxTouchCount = 0;
                                 handler.removeMessages(MESSAGE_LONG_CLICK);
                                 getParent().requestDisallowInterceptTouchEvent(false);
                             }
 
+                            if (!panEnabled) {
+                                vTranslate.x = vTranslateStart.x;
+                                vTranslate.y = vTranslateStart.y;
+                                getParent().requestDisallowInterceptTouchEvent(false);
+                            }
+
                             refreshRequiredTiles(false);
                         }
                     }
@@ -425,7 +458,7 @@
             case MotionEvent.ACTION_POINTER_UP:
             case MotionEvent.ACTION_POINTER_2_UP:
                 handler.removeMessages(MESSAGE_LONG_CLICK);
-                if (isPressed && (isZooming || isPanning)) {
+                if (maxTouchCount > 0 && (isZooming || isPanning)) {
                     if (isZooming && touchCount == 2) {
                         // Convert from zoom to pan with remaining touch
                         isPanning = true;
@@ -443,16 +476,18 @@
                     if (touchCount < 2) {
                         // End panning when no touch points
                         isPanning = false;
+                        maxTouchCount = 0;
                     }
                     // Trigger load of tiles now required
                     refreshRequiredTiles(true);
                     return true;
-                } else if (isPressed) {
+                } else if (maxTouchCount == 1) {
                     performClick();
                 }
                 if (touchCount == 1) {
                     isZooming = false;
                     isPanning = false;
+                    maxTouchCount = 0;
                 }
                 return true;
         }
@@ -1083,6 +1118,7 @@
     public void setScaleAndCenter(float scale, PointF sCenter) {
         this.pendingScale = scale;
         this.sPendingCenter = sCenter;
+        this.sRequestedCenter = sCenter;
         invalidate();
     }
 
@@ -1144,4 +1180,37 @@
         return null;
     }
 
+    /**
+     * Returns true if zoom gesture detection is enabled.
+     */
+    public boolean isZoomEnabled() {
+        return zoomEnabled;
+    }
+
+    /**
+     * Enable or disable zoom gesture detection. Disabling zoom locks the the current scale.
+     */
+    public void setZoomEnabled(boolean zoomEnabled) {
+        this.zoomEnabled = zoomEnabled;
+    }
+
+    /**
+     * Returns true if pan gesture detection is enabled.
+     */
+    public boolean isPanEnabled() {
+        return panEnabled;
+    }
+
+    /**
+     * Enable or disable pan gesture detection. Disabling pan causes the image to be centered.
+     */
+    public void setPanEnabled(boolean panEnabled) {
+        this.panEnabled = panEnabled;
+        if (!panEnabled && vTranslate != null) {
+            vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2));
+            vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2));
+            refreshRequiredTiles(true);
+            invalidate();
+        }
+    }
 }