title: android imageView: setting drag and pinch zoom parameters
body: <p>I am currently developing for Android (my first app) an application which lets users see the subway map and be able to pinch zoom and drag around.</p>
  
  <p>I am currently modifying the code found in Hello Android, 3rd Edition and got the pinch zooming and dragging to work. I'm using Matrix as my layout scale.</p>
  
  <p>However I now have 3 problems:</p>
  
  <ol>
  <li><p>I tried many things to limit the drag parameters but I can't seem to stop it being dragged off the parent view (and can actually disappear from view). I've tried setting layout parameters in the XML file and it just doesn't work.</p></li>
  <li><p>I can pinch zoom fine but I have trouble, again, limiting the amount of zoom. I'm trying to play around with setting a max_zoom and min_zoom to limit the scaling value (i will post my code after)</p></li>
  <li><p>I also have trouble trying to map a coordinate on my image so that people can click on certain parts (the whole point of this is to let users click a station on the map and view information about it)</p></li>
  </ol>
  
  <p>I have a feeling i'm having trouble because I'm using the matrix scale.</p>
  
  <p>Here is my current code:</p>
  
  <p><strong>Touch.java</strong></p>
  
  <pre><code>package org.example.touch;
  import android.app.Activity;
  import android.graphics.Bitmap;
  import android.graphics.Matrix;
  import android.graphics.PointF;
  import android.os.Bundle;
  import android.util.FloatMath;
  import android.util.Log;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.View.OnTouchListener;
  import android.widget.GridView;
  import android.widget.ImageView;
  
  public class Touch extends Activity implements OnTouchListener {
  private static final String TAG = "Touch";
  
  private static final float MIN_ZOOM = 1.0f;
  private static final float MAX_ZOOM = 5.0f;
  
  // These matrices will be used to move and zoom image
  Matrix matrix = new Matrix();
  Matrix savedMatrix = new Matrix();
  
  // We can be in one of these 3 states
  static final int NONE = 0;
  static final int DRAG = 1;
  static final int ZOOM = 2;
  int mode = NONE;
  
  // Remember some things for zooming
  PointF start = new PointF();
  PointF mid = new PointF();
  float oldDist = 1f;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);
     ImageView view = (ImageView) findViewById(R.id.imageView);
     //view.setLayoutParams(new GridView.LayoutParams(85, 85));
     view.setScaleType(ImageView.ScaleType.FIT_CENTER);
     view.setOnTouchListener(this);   
  }
  
  public boolean onTouch(View v, MotionEvent event) {
     ImageView view = (ImageView) v;
     view.setScaleType(ImageView.ScaleType.MATRIX);
     float scale;
  
     // Dump touch event to log
     dumpEvent(event);
  
     // Handle touch events here...
     switch (event.getAction() &amp; MotionEvent.ACTION_MASK) {
  
     case MotionEvent.ACTION_DOWN: //first finger down only
        savedMatrix.set(matrix);
        start.set(event.getX(), event.getY());
        Log.d(TAG, "mode=DRAG" );
        mode = DRAG;
        break;
     case MotionEvent.ACTION_UP: //first finger lifted
     case MotionEvent.ACTION_POINTER_UP: //second finger lifted
        mode = NONE;
        Log.d(TAG, "mode=NONE" );
        break;
     case MotionEvent.ACTION_POINTER_DOWN: //second finger down
        oldDist = spacing(event);
        Log.d(TAG, "oldDist=" + oldDist);
        if (oldDist &gt; 5f) {
           savedMatrix.set(matrix);
           midPoint(mid, event);
           mode = ZOOM;
           Log.d(TAG, "mode=ZOOM" );
        }
        break;
  
     case MotionEvent.ACTION_MOVE: 
        if (mode == DRAG) { //movement of first finger
           matrix.set(savedMatrix);
           if (view.getLeft() &gt;= -392){
              matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
           }
        }
        else if (mode == ZOOM) { //pinch zooming
           float newDist = spacing(event);
           Log.d(TAG, "newDist=" + newDist);
           if (newDist &gt; 5f) {
              matrix.set(savedMatrix);
              scale = newDist / oldDist; **//thinking i need to play around with this value to limit it**
              matrix.postScale(scale, scale, mid.x, mid.y);
           }
        }
        break;
     }
  
     // Perform the transformation
     view.setImageMatrix(matrix);
  
     return true; // indicate event was handled
  }
  
  private float spacing(MotionEvent event) {
     float x = event.getX(0) - event.getX(1);
     float y = event.getY(0) - event.getY(1);
     return FloatMath.sqrt(x * x + y * y);
  }
  
  private void midPoint(PointF point, MotionEvent event) {
     float x = event.getX(0) + event.getX(1);
     float y = event.getY(0) + event.getY(1);
     point.set(x / 2, y / 2);
  }
  
  /** Show an event in the LogCat view, for debugging */
  private void dumpEvent(MotionEvent event) {
     String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
        "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
     StringBuilder sb = new StringBuilder();
     int action = event.getAction();
     int actionCode = action &amp; MotionEvent.ACTION_MASK;
     sb.append("event ACTION_" ).append(names[actionCode]);
     if (actionCode == MotionEvent.ACTION_POINTER_DOWN
           || actionCode == MotionEvent.ACTION_POINTER_UP) {
        sb.append("(pid " ).append(
        action &gt;&gt; MotionEvent.ACTION_POINTER_ID_SHIFT);
        sb.append(")" );
     }
     sb.append("[" );
     for (int i = 0; i &lt; event.getPointerCount(); i++) {
        sb.append("#" ).append(i);
        sb.append("(pid " ).append(event.getPointerId(i));
        sb.append(")=" ).append((int) event.getX(i));
        sb.append("," ).append((int) event.getY(i));
        if (i + 1 &lt; event.getPointerCount())
           sb.append(";" );
     }
     sb.append("]" );
     Log.d(TAG, sb.toString());
  }
  }
  </code></pre>
  
  <p><strong>main.xml (rather simple nothing really complicated):</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" &gt;
  &lt;ImageView android:id="@+id/imageView"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:src="@drawable/map"
       android:scaleType="matrix" &gt;
  &lt;/ImageView&gt;
  &lt;/FrameLayout&gt;
  </code></pre>
  
  <p><strong>AndroidManifest.xml (only added the theme so there is no title bar and is full screen)</strong></p>
  
  <pre><code>&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.example.touch"
    android:versionCode="7"
    android:versionName="1.0" &gt;
  &lt;application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" &gt;
    &lt;activity android:name=".Touch"
          android:label="@string/app_name" &gt;
       &lt;intent-filter&gt;
          &lt;action android:name="android.intent.action.MAIN" /&gt;
          &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
       &lt;/intent-filter&gt;
    &lt;/activity&gt;
  &lt;/application&gt;
  &lt;uses-sdk android:minSdkVersion="3" android:targetSdkVersion="7" /&gt;
  &lt;/manifest&gt;
  </code></pre>