Multi Touch in Android – Translate, Scale, and Rotate

Here’s a quick and easy implementation of Android’s multi touch feature – one finger to move, two to zoom, and three to rotate the image.

Assuming you have a basic understanding of 2D matrix transformations, the Matrix class in Android uses a 3×3 matrix to achieve all of the 2D transformations.

The source code and pre -built APK is available. See the end of this post.

Main activity – MultiTouch.java

package com.multitouch.example;

import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class MultiTouch extends Activity implements OnTouchListener {

    // these matrices will be used to move and zoom image
    private Matrix matrix = new Matrix();
    private Matrix savedMatrix = new Matrix();
    // we can be in one of these 3 states
    private static final int NONE = 0;
    private static final int DRAG = 1;
    private static final int ZOOM = 2;
    private int mode = NONE;
    // remember some things for zooming
    private PointF start = new PointF();
    private PointF mid = new PointF();
    private float oldDist = 1f;
    private float d = 0f;
    private float newRot = 0f;
    private float[] lastEvent = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ImageView view = (ImageView) findViewById(R.id.imageView);
        view.setOnTouchListener(this);
    }

    public boolean onTouch(View v, MotionEvent event) {
        // handle touch events here
        ImageView view = (ImageView) v;
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                savedMatrix.set(matrix);
                start.set(event.getX(), event.getY());
                mode = DRAG;
                lastEvent = null;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                oldDist = spacing(event);
                if (oldDist > 10f) {
                    savedMatrix.set(matrix);
                    midPoint(mid, event);
                    mode = ZOOM;
                }
                lastEvent = new float[4];
                lastEvent[0] = event.getX(0);
                lastEvent[1] = event.getX(1);
                lastEvent[2] = event.getY(0);
                lastEvent[3] = event.getY(1);
                d = rotation(event);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                lastEvent = null;
                break;
            case MotionEvent.ACTION_MOVE:
                if (mode == DRAG) {
                    matrix.set(savedMatrix);
                    float dx = event.getX() - start.x;
                    float dy = event.getY() - start.y;
                    matrix.postTranslate(dx, dy);
                } else if (mode == ZOOM) {
                    float newDist = spacing(event);
                    if (newDist > 10f) {
                        matrix.set(savedMatrix);
                        float scale = (newDist / oldDist);
                        matrix.postScale(scale, scale, mid.x, mid.y);
                    }
                    if (lastEvent != null && event.getPointerCount() == 3) {
                        newRot = rotation(event);
                        float r = newRot - d;
                        float[] values = new float[9];
                        matrix.getValues(values);
                        float tx = values[2];
                        float ty = values[5];
                        float sx = values[0];
                        float xc = (view.getWidth() / 2) * sx;
                        float yc = (view.getHeight() / 2) * sx;
                        matrix.postRotate(r, tx + xc, ty + yc);
                    }
                }
                break;
        }

        view.setImageMatrix(matrix);
        return true;
    }

    /**
     * Determine the space between the first two fingers
     */
    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);
    }

    /**
     * Calculate the mid point of the first two fingers
     */
    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);
    }

    /**
     * Calculate the degree to be rotated by.
     *
     * @param event
     * @return Degrees
     */
    private float rotation(MotionEvent event) {
        double delta_x = (event.getX(0) - event.getX(1));
        double delta_y = (event.getY(0) - event.getY(1));
        double radians = Math.atan2(delta_y, delta_x);
        return (float) Math.toDegrees(radians);
    }
}

An important function that is always used is postXx(). This function concats a new matrix of the type Xx to the existing matrix object. Using setXx() will reset the matrix’s Xx property.
Main layout – main.xml

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ImageView android:id="@+id/imageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/butterfly"
android:scaleType="matrix" />

</FrameLayout>

Browse and download the source code on GitHub.


Comments

61 responses to “Multi Touch in Android – Translate, Scale, and Rotate”

  1. This code make the image itself act within the ImageView boarders, but is it possible to make ImageView act? in addition if I add several views on the view group the last one is active and covers previous ones… any suggestions?

    1. Yes, it is possible. I have done so with another app.
      To bring the touched view to front, use bringToFront() on the view

      1. and how do you do that? as I guess it’s the matrix within the view that’s zoomed in, rotated and dragged, not the view itself. here is the code (copied from you :) ) of VustomImageView http://pastebin.com/65GBTgKW
        and here’s how I initialize it: http://pastebin.com/eqv8SpV9

        thnx in advace.

        1. Well, everything is pretty much the same. The main difference is that instead of view.setImageMatrix(matrix); on line 110(refer to your pastebin link), calculate the X and Y coordinates from the matrix, along with the scale factor and rotation, and use it directly on the view. A view has setX(), setY(), setScaleX(), setScaleY(), and setRotation(). More on these methods here: http://developer.android.com/reference/android/view/View.html

          1. i have used if (mode == DRAG) { view.setX(dx), view.setY(dy)} for drag function i can able to multiple images but drag is working not smooth

        2. http://pastebin.com/eqv8SpV9 In this like shows “This paste has been removed!” please help me how to scale and rotate multiple view inside a view

          1. zooming is flickering view.setScaleX(x), view.setScaleY(y). how to make the zooming is smooth

            1. unfortunately I failed to apply those settings to the view itself :(

              1. just i copied the above code and tried like this view.setScaleX and view.setScaleY. please if you find the solution help me how to scale and rotate smoothly.

                CODE BLOCK DELETED. Please paste code at pastebin.com

                1. ok, here is my code, I have two classes, first is the custom imageView using the above code: http://pastebin.com/bC2xw1D1
                  and main class, where I declare this custom class… and here is where I use it: http://pastebin.com/c66qJAW1
                  however, the problem is, that when I set newChild dimensions as fill_parent, each new child overlaps the previous one… and it I don’t set the dimens, the image is rotated/zoomed/moved withing the imageview bounds, so the action is performed on the matrix not the view itself :( I’m not that strong in coding, so if you find any solution, maybe you also could post it here :)))

  2. thnx to the author, it took me significant time to find this page :)))) and it saved a lot more :)

  3. Hey, I’m just interested – why didn’t you use GestureScaleDetector for this purpose? Is there any problem with GestureScaleDetector? Maybe I misunderstand but isn’t this detector serves the same purpose?

    Thanks.

    1. This example code is working perfectly as my need only addition thing i need to apply for multiple image view to drag , re-size and rotate. pls provide me some peace of code to add multiple images to drag, drop and scale

      1. You can Create a custom “ImageView” Class using the above code to perform scaling, rotation and moving. And then Insert the objects of that class in your “MainActivity” in FrameLayout or any ViewGroup. and as suggested by Jude, you can then Activate the specific ImageView by detecting the touch area.
        If you want any other help regarding the multiple Images. You can contact me at vikrantsaini1111@gmail.com, if Jude don’t mind. ;)

        1. I have created Custome ImageView and added multiple images dynamically.

          Issues: 1.The images is smaller that the image view.
          2. I can able to move , scale and rotate the images with in the image view.

          I need to move scale and rotate the images in the whole layout. how to do that. I have attached my code pls help me.

          Activity Class:
          public class ImageViewDrag extends Activity {

          private static final String TAG = “Touch”;

          LinearLayout selectedLayout ;

          int [] images = {R.drawable.r1 , R.drawable.r10};

          @Override
          public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_image_view_drah);

          selectedLayout = (LinearLayout)findViewById(R.id.addViews);
          View view = new View(this);
          for(int i =0; i <2 ;i ++)
          {

          DraggableView imageView = new DraggableView(this);

          LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(400, 400);

          imageView.setLayoutParams(vp);
          imageView.setImageResource(images[i]);
          if(i==0){
          imageView.setBackgroundColor(Color.GREEN);
          }
          else if(i==1)
          {
          imageView.setBackgroundColor(Color.BLUE);
          }
          imageView.setScaleType(ScaleType.MATRIX);

          selectedLayout.addView(imageView);

          }

          }

          }

          Custom Image View:

          public class DraggableView extends ImageView implements OnTouchListener {

          Matrix matrix = new Matrix();
          Matrix savedMatrix = new Matrix();
          PointF start = new PointF();
          PointF mid = new PointF();
          float oldDist = 1f;
          public DraggableView(Context context) {
          super(context);
          this.setOnTouchListener(this);
          }

          @Override
          public boolean onTouch(View v, MotionEvent event) {
          ImageView view = (ImageView) v;
          LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(200,200);

          switch (event.getAction() & MotionEvent.ACTION_MASK) {
          case MotionEvent.ACTION_DOWN:
          savedMatrix.set(matrix);
          start.set(event.getX(), event.getY());
          break;
          case MotionEvent.ACTION_UP:
          savedMatrix.set(matrix);

          break;
          case MotionEvent.ACTION_MOVE:
          matrix.set(savedMatrix);
          matrix.postTranslate(event.getX() – start.x, event.getY()
          – start.y);

          break;
          }

          view.setImageMatrix(matrix);

          return true;
          }

          }

  4. how to add multiple images in your code please explain it. i am new to android

    1. You’ll have to create and maintain a map for multiple images. Then find out which one was touched and make that the active one for operations.

  5. Thanx for the code. I implemented the code but the rotation is not working. The device supports multi touch. Can you tell me how to set the rotation to work with 2 fingers.

    1. No need to reply. I got it work. :)

      1. how u done rotation using two fingers

        1. Change the finger count to two from three.

  6. Its a good example. Nice work. Thanx.

  7. Could you please clarify, what is the purpose for ‘lastEvent’ array in the code? I don’t see when we its values…

    1. Hey Kirill, it must have crept in during testing of the code. You can safely remove it.

  8. nice work, but while in rotation image goes out of the view , so how can i set rotation without changing its position in matrix.using 2 finger. answer urgently required.

    1. Please refer to my other article, titled “Calculate the REAL scale factor and the angle of rotation from an Android Matrix”.

  9. Hi, I want to clarify what if I have 2 or more small images that I want to resize. How do I do? I tried your work but the imageview container does not change the width and height.

    1. Maintain a mapping between an imageview and it’s associated matrix. Then, when a pointer down event type occurs, figure out which imageview was touched, and set that as the active one to perform operations on.

  10. Thank you Jude for a working example. but I’m having a tough time implementing it on multiple imageviews. the fill_parent wont allow me to touch the imageview behind. hope you can help..

    1. Have you tried to set the ImageView’s dimensions in code rather than XML?

  11. […] got a tutorial of imageview transformation, w/c I really need. here is the link. It works fine but I need to have some modification. My problem here is, when I add the second […]

  12. […] am creating a zoom in zoom out application in android, and I got this link that demonstrate how to zoom in and zoom out. The code is working but the image view (width and […]

  13. Hi, your code works fine. But I need to do the same for multiple images also, In my case I have two images say Image-A and Image-B, It works fine when I Zoom the Image-A and drag it to a new location, the problem is when I touch the Image-B, then Image-B automatically moves to the X and Y position of Image-A with same zoom level of Image-A, and viceversa. I need to retain the zoom level and dragged position of all the images individually. Please provide me some solution. Thanks in advance.

    1. Hi Manoj,
      You will have to determine which ImageView was clicked. Then, apply the transformations only to that ImageView.

      1. Thanks for your reply Jude, I have determined which ImageView has been clicked by getTag() property of the ImageView, how to apply transformations to that particular ImageView, should we do it in MotionEvent.ACTION_DOWN event, Please explain

        1. Maintain a mapping between each ImageView and the other objects associated with it, such as Matrix. Then in the case that has the ACTION_DOWN line, set the current reference ImageView entity to the selected ImageView.

          1. ok Jude, I will give a try and let you know

          2. ok Jude, Thanks I will try it

    2. Hi, manoj. how did you do the multiple images? Im having a tough time there. I’m able to transfrom the image with the help of this tutorial but when I add a second image, it seems that the 1st image that I added is now untouchable because of the FILL_PARENT. and when I set it on WRAP_CONTENT, I can only transform it on its own height and width. How can I access the 1st image again? hope you can help.

      1. Try to set the ImageView’s dimensions in code, rather than XML. This way, you have more control over it.

  14. nice tutorial..but the rotation not-working

    1. Hey Aditya, does your phone support three fingers? In the example, I’ve set it to rotate only in the presence of three pointers.

  15. Any tips on dealing with border cases? I’m trying to restrict the images from being to be scrolled completely off screen.

    1. Ken,
      Absolutely. You can use the methods getX() and getY() on the ImageView object. These two methods return the position of the top left corner of the ImageView.

      Then you have two cases:
      1. If the ImageView is going out from the left or the top: return from the onTouch() method if the values are negative(the view goes to the extreme left or extreme top)
      2. If the ImageView is going out from the right or the bottom: return from onTouch() if imageView.getX() + view.getLayoutParams().width is greater than your device’s actual resolution(that is, the imageView is at the extreme right). You will have to do the same for the Y axis as well, which will take care of the ImageView going out from the bottom.

      Let me know if you need any further explanation. Would love to help you out.

      1. Are we actually moving the ImageView when we call setImageMatrix() ? I thought only the image was being moved but not actually the view itself. getX() and getY() on the ImageView object were unchanged when I was testing it.

        1. Yes, I forgot about that part. To get the X and Y of the current position of the ImageView, get it from the Matrix. You should get the translation values for X and Y, after calling getValues() on the Matrix.

        2. Something like this will get you what you’re looking for:

          float[] values = new float[9];
          matrix.getValues(values);
          int x = values[Matrix.MTRANS_X];
          int y = values[Matrix.MTRANS_Y];

          1. Note: You will need to add the ImageView’s getTop() value to the x and getLeft() to the y value to get the actual position of the image inside the ImageView.

        3. From http://developer.android.com/reference/android/view/View.html:

          “It is possible to retrieve the location of a view by invoking the methods getLeft() and getTop(). The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view. These methods both return the location of the view relative to its parent. For instance, when getLeft() returns 20, that means the view is located 20 pixels to the right of the left edge of its direct parent.”

          1. Thanks for all your help I finally figured it out.

            1. Your welcome :-)

  16. Wow amazing ~ i thought it doesn’t work but when i tried to control by 3 fingers. Rotation worked ~~~
    Appreciate ~ i understood how to make it ~

    1. Glad it helped. You can change it to use two fingers for both, rotation as well as scaling.

  17. Angeline Avatar
    Angeline

    Hi, I have tried your codes and it works perfectly. But can I know if there is anything I could change in your codes to make the rotation of the image not so sensitive? As a slight tilt of my fingers can cause the image to be rotate 360 degrees.

    1. Angeline,
      Have you tried compiling the github source? The rotation in not so sensitive, and it definitely works very well.

      The issue that your facing, very sensitive rotation, is one I face in one of my other apps, and I’ve not found a solution yet to it.

  18. hi,
    its a very useful tutorial but how can i modify this to work on a textview??

    1. i want to put the textview above imageview(frame layout) thats why i just want to apply these operations on textview and not imageview

      1. What have you tried? This should work on a TextView as well

  19. lins louis Avatar
    lins louis

    nice tutorial..but the rotation not-working in my device..does all device support more than two fingers?..

    1. Hi Lins,
      You need to check your device’s specifications to confirm that.