Search This Blog

Saturday, 7 April 2012

Pinching Zoom in android Image View or Bitmap

See Updated Tutorial


This is little bit complex article. In android , we can achieve pinch zoom with two or more than two finger. But it s little bit complex. I have developed it with the help of one GuitHub project.

Here we use Bitmap, Gesture, Matrix and other Bitmap Basic function. I have develop simply three classes.
main class is TouchImageView.java that is a Image-View.you can set this class Object anywhere

Make one project and use my classes

main class TouchImageView.java 

 package com.ahmad;  
 import android.content.Context;  
 import android.graphics.Bitmap;  
 import android.graphics.Matrix;  
 import android.graphics.PointF;  
 import android.util.FloatMath;  
 import android.util.Log;  
 import android.view.MotionEvent;  
 import android.view.View;  
 import android.widget.ImageView;  
 public class TouchImageView extends ImageView  
 {  
   private static final String TAG = "Touch";  
   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;  
   Context context;  
   public TouchImageView(Context context)  
   {  
     super(context);  
     super.setClickable(true);  
     this.context = context;  
     matrix.setTranslate(1f, 1f);  
     setImageMatrix(matrix);  
     setScaleType(ScaleType.MATRIX);  
     setOnTouchListener(new OnTouchListener()  
     {  
       @Override  
       public boolean onTouch(View v, MotionEvent rawEvent)  
       {  
         WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);  
         // Dump touch event to log  
         // if (Viewer.isDebug == true)  
         {  
           // dumpEvent(event);  
           //  
         }  
         // Handle touch events here...  
         switch (event.getAction() & MotionEvent.ACTION_MASK)  
         {  
           case MotionEvent.ACTION_DOWN:  
           savedMatrix.set(matrix);  
           start.set(event.getX(), event.getY());  
           Log.d(TAG, "mode=DRAG");  
           mode = DRAG;  
           break;  
           case MotionEvent.ACTION_POINTER_DOWN:  
           oldDist = spacing(event);  
           Log.d(TAG, "oldDist=" + oldDist);  
           if (oldDist > 10f)  
           {  
             savedMatrix.set(matrix);  
             midPoint(mid, event);  
             mode = ZOOM;  
             Log.d(TAG, "mode=ZOOM");  
           }  
           break;  
           case MotionEvent.ACTION_UP:  
           int xDiff = (int) Math.abs(event.getX() - start.x);  
           int yDiff = (int) Math.abs(event.getY() - start.y);  
           if (xDiff < 8 && yDiff < 8)  
           {  
             performClick();  
           }  
           case MotionEvent.ACTION_POINTER_UP:  
           mode = NONE;  
           Log.d(TAG, "mode=NONE");  
           break;  
           case MotionEvent.ACTION_MOVE:  
           if (mode == DRAG)  
           {  
             // ...  
             matrix.set(savedMatrix);  
             matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);  
           }  
           else if (mode == ZOOM)  
           {  
             float newDist = spacing(event);  
             Log.d(TAG, "newDist=" + newDist);  
             if (newDist > 10f)  
             {  
               matrix.set(savedMatrix);  
               float scale = newDist / oldDist;  
               matrix.postScale(scale, scale, mid.x, mid.y);  
             }  
           }  
           break;  
         }  
         setImageMatrix(matrix);  
         return true; // indicate event was handled  
       }  
     }  
     );  
   }  
   public void setImage(Bitmap bm, int displayWidth, int displayHeight)  
   {  
     super.setImageBitmap(bm);  
     //Fit to screen.  
     float scale;  
     if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth()))  
     {  
       scale = (float)displayWidth / (float)bm.getWidth();  
     }  
     else  
     {  
       scale = (float)displayHeight / (float)bm.getHeight();  
     }  
     savedMatrix.set(matrix);  
     matrix.set(savedMatrix);  
     matrix.postScale(scale, scale, mid.x, mid.y);  
     setImageMatrix(matrix);  
     // Center the image  
     float redundantYSpace = (float)displayHeight - (scale * (float)bm.getHeight()) ;  
     float redundantXSpace = (float)displayWidth - (scale * (float)bm.getWidth());  
     redundantYSpace /= (float)2;  
     redundantXSpace /= (float)2;  
     savedMatrix.set(matrix);  
     matrix.set(savedMatrix);  
     matrix.postTranslate(redundantXSpace, redundantYSpace);  
     setImageMatrix(matrix);  
   }  
   /** Show an event in the LogCat view, for debugging */  
   @SuppressWarnings("unused")  
   private void dumpEvent(WrapMotionEvent 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 & 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 >> MotionEvent.ACTION_POINTER_ID_SHIFT);  
       sb.append(")");  
     }  
     sb.append("[");  
     for (int i = 0; i < 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 < event.getPointerCount())  
       sb.append(";");  
     }  
     sb.append("]");  
     Log.d(TAG, sb.toString());  
   }  
   /** Determine the space between the first two fingers */  
   private float spacing(WrapMotionEvent event)  
   {  
     float x = event.getX(0) - event.getX(1);  
     float y = event.getY(0) - event.getY(1);  
     return FloatMath.sqrt(x * x + y * y);  
   }  
   /** Calculate the mid point of the first two fingers */  
   private void midPoint(PointF point, WrapMotionEvent event)  
   {  
     float x = event.getX(0) + event.getX(1);  
     float y = event.getY(0) + event.getY(1);  
     point.set(x / 2, y / 2);  
   }  
 }  

Motion class to help in touch event


 package com.ahmad;  
 import android.view.MotionEvent;  
 public class EclairMotionEvent extends WrapMotionEvent {  
   protected EclairMotionEvent(MotionEvent event) {  
       super(event);  
   }  
   public float getX(int pointerIndex) {  
       return event.getX(pointerIndex);  
   }  
   public float getY(int pointerIndex) {  
       return event.getY(pointerIndex);  
   }  
   public int getPointerCount() {  
       return event.getPointerCount();  
   }  
   public int getPointerId(int pointerIndex) {  
       return event.getPointerId(pointerIndex);  
   }  
 }  

Class to keep information about Pointer ID

 package com.ahmad;  
 import android.view.MotionEvent;  
 public class WrapMotionEvent {  
  protected MotionEvent event;  
  protected WrapMotionEvent(MotionEvent event) {  
  this.event = event;  
  }  
    static public WrapMotionEvent wrap(MotionEvent event) {  
  try {  
   return new EclairMotionEvent(event);  
  } catch (VerifyError e) {  
   return new WrapMotionEvent(event);  
  }  
  }  
  public int getAction() {  
  return event.getAction();  
  }  
  public float getX() {  
  return event.getX();  
  }  
  public float getX(int pointerIndex) {  
  verifyPointerIndex(pointerIndex);  
  return getX();  
  }  
  public float getY() {  
  return event.getY();  
  }  
  public float getY(int pointerIndex) {  
  verifyPointerIndex(pointerIndex);  
  return getY();  
  }  
  public int getPointerCount() {  
  return 1;  
  }  
  public int getPointerId(int pointerIndex) {  
  verifyPointerIndex(pointerIndex);  
  return 0;  
  }  
  private void verifyPointerIndex(int pointerIndex) {  
  if (pointerIndex > 0) {  
   throw new IllegalArgumentException(  
    "Invalid pointer index for Donut/Cupcake");  
  }  
  }  
 }  

Notable thing is that this pinch zoom will only work above Android 2.0. Lower version does not support multiple finger

40 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. How can you implement the pan/pinch zoom limits ?

    ReplyDelete
    Replies
    1. Inside touch's Action_Move i have a code to zoom or drag.If you touch with one finger then it move image to show complete image else it will zoom.Here you can do two thing two to limit zoom level check image size (using matrix).On second way i am working so i will update as soon as i do

      Delete
    2. Thanks for such a great solution. I've been working on this, so I
      implemented limits for zooming and moving a picture. Hope it helps :)!

      ZOOMING LIMITS

      //You need this first:

      double max_zoom=3,min_zoom=0.4; //or put other limits, 3 and 0.4 is example

      //Right above the line "case MotionEvent.ACTION_POINTER_UP:" put this code:

      float f[] = new float[9];
      matrix.getValues(f);
      if (f[0]>max_zoom){
      matrix.postScale((float)max_zoom/f[0], (float)max_zoom/f[0], mid.x, mid.y);
      }
      if (f[0]bmp_picture.getWidth()*f[0]-marg_left){
      matrix.postTranslate(-(f[2]+bmp_picture.getWidth()*f[0])+marg_left, 0);
      }
      if (-f[5]>bmp_picture.getHeight()*f[0]-marg_top){
      matrix.postTranslate(0,-(f[5]+bmp_picture.getHeight()*f[0])+marg_top);
      }

      if (f[2]>win_w-marg_right){
      matrix.postTranslate(-(f[2]-win_w+marg_right),0);
      }

      if (f[5]>win_h-marg_botom){
      matrix.postTranslate(0,-(f[5]-win_h+marg_botom));
      }

      Delete
    3. Thank you for providing solution..I have not checked it hope it work correct.These days i am not getting time to post alternative solution.By the way thank you

      Delete
    4. I see in comment above the code I sent is missing, so I will send you separately code for limits for Zoom and for Move.


      ZOOMING LIMITS

      //You need this first:

      double max_zoom=3,min_zoom=0.4; //or put other limits, 3 and 0.4 is example

      //Right above the line "case MotionEvent.ACTION_POINTER_UP:" put this code:

      float f[] = new float[9];
      matrix.getValues(f);
      if (f[0]>max_zoom){
      matrix.postScale((float)max_zoom/f[0], (float)max_zoom/f[0], mid.x, mid.y);
      }
      if (f[0]<min_zoom){
      matrix.postScale((float)min_zoom/f[0],(float) min_zoom/f[0], mid.x, mid.y);
      }

      Delete
    5. MOVING LIMITS

      //First, you need to put this:

      int marg_left=50,marg_right=50,marg_top=50,marg_botom=50,win_w,win_h;
      Bitmap bmp_picture;

      //Next, you need to put 2 new arguments in set_Image to look just like this:

      public void setImage(Bitmap bm, int displayWidth, int displayHeight,int h,int w) {

      bmp_picture = bm;
      win_h = h;
      win_w = w;

      // h and w are the screen width and height.
      // I calculated them in other class like this:

      Display display = getWindowManager().getDefaultDisplay();
      h=display.getHeight()
      w=display.getWidth()

      //Finaly, right above the line "case MotionEvent.ACTION_POINTER_UP:" put this:

      if (-f[2]>bmp_picture.getWidth()*f[0]-marg_left){
      matrix.postTranslate(-(f[2]+bmp_picture.getWidth()*f[0])+marg_left, 0);
      }
      if (-f[5]>bmp_picture.getHeight()*f[0]-marg_top){
      matrix.postTranslate(0,-(f[5]+bmp_picture.getHeight()*f[0])+marg_top);
      }

      if (f[2]>win_w-marg_right){
      matrix.postTranslate(-(f[2]-win_w+marg_right),0);
      }

      if (f[5]>win_h-marg_botom){
      matrix.postTranslate(0,-(f[5]-win_h+marg_botom));
      }

      Delete
    6. And no problem, glad if I can make some help to you, as you did to me ;)!

      Delete
  3. Hi Tofeeq,

    I'm a newbie. I' appreciate some help. Your code is compiling perfectly well.
    I've got all three classes in my project.

    But I'm not sure on how to use it. Might sound stupid, but thats how it is.

    Could you give me a push in the right direction?

    Thanks,
    Sid

    ReplyDelete
    Replies
    1. Main Class is TouchImageView. This extends ImageView and will work as ImageView.Create Object of TouchImageView and add to your desired layout in your activity.

      Delete
  4. Cool. Works Well for images. How about layouts ?
    I want to apply pinch-zoom functionality for a "Frame Layout".

    setImageMatrix(matrix);

    //This is only for images, any thing similar we have for layouts?

    ReplyDelete
    Replies
    1. I had not did it yet ..but you have to create custom classes for achieving it.Its going to be tough.You should start from TouchImageView.Extends Frame Layout instead of Image View.

      Delete
  5. Hello,

    Can you share this codes in project files? Because I got errors when i had tried to build this project. Sorry for my english.

    Thank you.

    ReplyDelete
    Replies
    1. whenever i got time i will share code..:)

      Delete
  6. https://github.com/MikeOrtiz/TouchImageView.git

    ReplyDelete
  7. How to use the TouchImageView in an activity??

    ReplyDelete
  8. can u please share the TouchImageView class with the zoom and move limit?

    ReplyDelete
  9. i love ya man.. i have been behind this shit for the past 1 month!! u rock!!!!!

    ReplyDelete
    Replies
    1. hw did u use touchimageview in your activity??
      i have struck in dis for a week...
      pls help

      Delete
  10. Very nice tutorial, however is there a way to implement this to a webview?

    ReplyDelete
  11. hi, Can anyone post a link with the TouchImageView class with zoom and move limit?
    Please
    /R

    ReplyDelete
  12. How to use the TouchImageView in an activity??
    pls help its very urgent for me

    ReplyDelete
  13. Sorry for late reply. You can use TouchImageView like ImageView. if you want to use in xml then use like
    and then TouchImage touc=findViewByID(R.id.itsid).

    ReplyDelete
    Replies
    1. is it possible to zoom two imageviews in this??

      Delete
    2. Yes, you can two image view also.I am not explain it here more. Try it and let me know if it works

      Delete
    3. TouchImageView view = (TouchImageView ) findViewById(R.id.picture);
      view.setImageResource(R.drawable.neck1);


      i have put the above code i my activity....but i m getting error that failed the create activity..
      pls help its very urgent for me

      Delete
    4. Failed the create activity !!!!..explain your Error properly so that i can help you

      Delete
  14. Hi there,
    I'm a newbie trying to get this feature to work. I have managed to compile the above code without issue, but the software crashes when I try opening a TouchImageView.

    What should the contents of the layout XML be?

    I'm using the following code to call the activity...

    ...
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TouchImageView view = (TouchImageView ) findViewById(R.id.name_of_layout_XML);
    view.setImageResource(R.drawable.drawable_name);
    }
    ...

    My layout XML has the following;
    ...

    <?xml version="1.0" encoding="utf-8"?>
    <TouchImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:id="@+id/name_of _layout_XML"
    android:background="@drawable/name_of_drawable">
    </TouchImageView>
    ...

    Any help would be much appreciated :-)

    ReplyDelete
  15. Thank you for your code, but I had errors, could you please share code.

    Suleyman

    ReplyDelete
    Replies
    1. See GitHub URL in starting of tutorial.

      Delete
  16. I had problem with this kind of symbols: & ;
    like: if (oldDist > 10f)

    ReplyDelete
    Replies
    1. I did not get. Please be more specific where you got this issue

      Delete
  17. Hi, you are posting un related link here. That's why i mark them spam. Please take care of this.

    ReplyDelete

Feedback always help in improvement. If you have any query suggestion feel free to comment and Keep visiting my blog to encourage me to blogging

Android News and source code