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.

Be Sociable, Share!

61 comments

  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. 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

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

              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. 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;
          }

          }

  3. 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.

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

  5. 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.

  6. 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.

  7. 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..

  8. 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. 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. 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.

  9. 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.”

  10. 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 ~

  11. 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.

    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

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

Comments are closed.