Multi-Touch Panning & Pinch Zoom Image View in Android Using Android Studio

Description

An Image View with touch can be used to make a great tool that provides zooming and panning an image inside it. So that the user can view very large images inside a small screen.

In Android, we all have seen the builtin Gallery. It provides pinch zoom & pan. But it is provided by the builtin Gallery. How can we provide that feature in our application? Here is how.

In this article, I am explaining how to use Multi-touch with Image View in Android to replicate the Gallery feature. So let's begin.

Step 1

Create a project with the following parameters.

Pinch-Zoom-Image-View-in-Android-using-Android-Studio.jpg

Pinch-Zoom-Image-View-in-Android-using-Android-Studio1.jpg

Step 2

Now, the following is the code to add multi-touch pinch, zoom and panning features to the Image View. Copy the entire code and paste it into your package or make a class file named "TouchImageView" and paste code inside it.

TouchImageView.java


  1. import android.content.Context;  
  2. import android.graphics.Matrix;  
  3. import android.graphics.PointF;  
  4. import android.graphics.drawable.Drawable;  
  5. import android.util.AttributeSet;  
  6. import android.util.Log;  
  7. import android.view.MotionEvent;
  8. import android.view.ScaleGestureDetector;  
  9. import android.view.View;  
  10. import android.widget.ImageView;  
  11.  
  12. public class TouchImageView extends ImageView {  
  13.     Matrix matrix;  
  14.     // We can be in one of these 3 states  
  15.     static final int NONE = 0;  
  16.     static final int DRAG = 1;  
  17.     static final int ZOOM = 2;  
  18.   
  19.     int mode = NONE;  
  20.  
  21.     // Remember some things for zooming  
  22.     PointF last = new PointF();  
  23.     PointF start = new PointF();  
  24.     float minScale = 1f;  
  25.     float maxScale = 3f;  
  26.     float[] m;
  27.     int viewWidth, viewHeight;  
  28.   
  29.     static final int CLICK = 3;  
  30.   
  31.     float saveScale = 1f;  
  32.   
  33.     protected float origWidth, origHeight;  
  34.   
  35.     int oldMeasuredWidth, oldMeasuredHeight;  
  36.  
  37.     ScaleGestureDetector mScaleDetector;  
  38.  
  39.     Context context;  
  40.  
  41.     public TouchImageView(Context context) {  
  42.         super(context);  
  43.         sharedConstructing(context);  
  44.     } 

  45.     public TouchImageView(Context context, AttributeSet attrs) {  
  46.         super(context, attrs);  
  47.         sharedConstructing(context);  
  48.     }  
  49.   
  50.     private void sharedConstructing(Context context) {  
  51.   
  52.         super.setClickable(true);  
  53.   
  54.         this.context = context;  
  55.   
  56.         mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());  
  57.   
  58.         matrix = new Matrix();  
  59.   
  60.         m = new float[9];  
  61.   
  62.         setImageMatrix(matrix);  
  63.   
  64.         setScaleType(ScaleType.MATRIX);  
  65.  
  66.         setOnTouchListener(new OnTouchListener() {  
  67.  
  68.             @Override  
  69.             public boolean onTouch(View v, MotionEvent event) {  
  70.   
  71.                 mScaleDetector.onTouchEvent(event);  
  72.   
  73.                 PointF curr = new PointF(event.getX(), event.getY());  
  74.   
  75.                 switch (event.getAction()) {  
  76.   
  77.                     case MotionEvent.ACTION_DOWN:  
  78.   
  79.                        last.set(curr);  
  80.   
  81.                         start.set(last);  
  82.   
  83.                         mode = DRAG;  
  84.   
  85.                         break;  
  86.  
  87.                     case MotionEvent.ACTION_MOVE:  
  88.   
  89.                         if (mode == DRAG) {  
  90.   
  91.                             float deltaX = curr.x - last.x;  
  92.   
  93.                             float deltaY = curr.y - last.y;  
  94.   
  95.                             float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);  
  96.   
  97.                             float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);  
  98.   
  99.                             matrix.postTranslate(fixTransX, fixTransY);  
  100.   
  101.                             fixTrans();  
  102.   
  103.                             last.set(curr.x, curr.y);  
  104.   
  105.                         }  
  106.   
  107.                         break;  
  108.   
  109.                     case MotionEvent.ACTION_UP:  
  110.   
  111.                         mode = NONE;  
  112.   
  113.                         int xDiff = (int) Math.abs(curr.x - start.x);  
  114.   
  115.                         int yDiff = (int) Math.abs(curr.y - start.y);  
  116.   
  117.                         if (xDiff < CLICK && yDiff < CLICK)  
  118.   
  119.                             performClick();  
  120.   
  121.                         break;  
  122.   
  123.                     case MotionEvent.ACTION_POINTER_UP:  
  124.   
  125.                         mode = NONE;  
  126.   
  127.                         break;  
  128.   
  129.                 }  
  130.   
  131.                 setImageMatrix(matrix);  
  132.   
  133.                 invalidate();  
  134.   
  135.                 return true// indicate event was handled  
  136.   
  137.             }  
  138.  
  139.         });
  140.     }  
  141.   
  142.     public void setMaxZoom(float x) {  
  143.   
  144.         maxScale = x;  
  145.   
  146.     }  
  147.   
  148.     private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {  
  149.   
  150.         @Override  
  151.         public boolean onScaleBegin(ScaleGestureDetector detector) {  
  152.   
  153.             mode = ZOOM;  
  154.   
  155.             return true;  
  156.   
  157.         }  

  158.         @Override  
  159.         public boolean onScale(ScaleGestureDetector detector) {  
  160.   
  161.             float mScaleFactor = detector.getScaleFactor();  
  162.   
  163.             float origScale = saveScale;  
  164.   
  165.             saveScale *= mScaleFactor;  
  166.   
  167.             if (saveScale > maxScale) {  
  168.   
  169.                 saveScale = maxScale;  
  170.   
  171.                 mScaleFactor = maxScale / origScale;  
  172.   
  173.             } else if (saveScale < minScale) {  
  174.   
  175.                 saveScale = minScale;  
  176.   
  177.                 mScaleFactor = minScale / origScale;  
  178.   
  179.             }  
  180.  
  181.             if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)  
  182.   
  183.                 matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);  
  184.   
  185.             else  
  186.   
  187.                 matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());  
  188.   
  189.             fixTrans();  
  190.   
  191.             return true;  
  192.   
  193.         }  
  194.   
  195.     }  
  196.   
  197.     void fixTrans() {  
  198.   
  199.         matrix.getValues(m);  
  200.   
  201.         float transX = m[Matrix.MTRANS_X];  
  202.   
  203.         float transY = m[Matrix.MTRANS_Y];  
  204.   
  205.         float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);  
  206.   
  207.         float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);  
  208.   
  209.         if (fixTransX != 0 || fixTransY != 0)  
  210.   
  211.             matrix.postTranslate(fixTransX, fixTransY);  
  212.   
  213.     }  
  214.   
  215.    
  216.   
  217.     float getFixTrans(float trans, float viewSize, float contentSize) {  
  218.   
  219.         float minTrans, maxTrans;  
  220.   
  221.         if (contentSize <= viewSize) {  
  222.   
  223.             minTrans = 0;  
  224.   
  225.             maxTrans = viewSize - contentSize;  
  226.   
  227.         } else {  
  228.   
  229.             minTrans = viewSize - contentSize;  
  230.   
  231.             maxTrans = 0;  
  232.   
  233.         }  
  234.   
  235.         if (trans < minTrans)  
  236.   
  237.             return -trans + minTrans;  
  238.   
  239.         if (trans > maxTrans)  
  240.   
  241.             return -trans + maxTrans;  
  242.   
  243.         return 0;  
  244.   
  245.     }  
  246.  
  247.     float getFixDragTrans(float delta, float viewSize, float contentSize) {  
  248.   
  249.         if (contentSize <= viewSize) {  
  250.   
  251.             return 0;  
  252.   
  253.         }  
  254.   
  255.         return delta;  
  256.   
  257.     }  
  258.   
  259.     @Override  
  260.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  261.   
  262.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  263.   
  264.         viewWidth = MeasureSpec.getSize(widthMeasureSpec);  
  265.   
  266.         viewHeight = MeasureSpec.getSize(heightMeasureSpec);  
  267.   
  268.         //  
  269.         // Rescales image on rotation  
  270.         //  
  271.         if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight  
  272.   
  273.                 || viewWidth == 0 || viewHeight == 0)  
  274.   
  275.             return;  
  276.   
  277.         oldMeasuredHeight = viewHeight;  
  278.   
  279.         oldMeasuredWidth = viewWidth;
  280.   
  281.         if (saveScale == 1) {  
  282.   
  283.             //Fit to screen.  
  284.   
  285.             float scale;  
  286.   
  287.             Drawable drawable = getDrawable();  
  288.   
  289.             if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)  
  290.   
  291.                 return;  
  292.   
  293.             int bmWidth = drawable.getIntrinsicWidth();  
  294.   
  295.             int bmHeight = drawable.getIntrinsicHeight();  
  296.   
  297.             Log.d("bmSize""bmWidth: " + bmWidth + " bmHeight : " + bmHeight);  
  298.   
  299.             float scaleX = (float) viewWidth / (float) bmWidth;  
  300.   
  301.             float scaleY = (float) viewHeight / (float) bmHeight;  
  302.   
  303.             scale = Math.min(scaleX, scaleY);  
  304.   
  305.             matrix.setScale(scale, scale);  
  306.   
  307.             // Center the image  
  308.   
  309.             float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);  
  310.   
  311.             float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);  
  312.   
  313.             redundantYSpace /= (float2;  
  314.   
  315.             redundantXSpace /= (float2;  
  316.   
  317.             matrix.postTranslate(redundantXSpace, redundantYSpace);  
  318.   
  319.             origWidth = viewWidth - 2 * redundantXSpace;  
  320.   
  321.             origHeight = viewHeight - 2 * redundantYSpace;  
  322.   
  323.             setImageMatrix(matrix);  
  324.   
  325.         }  
  326.   
  327.         fixTrans();  
  328.   
  329.     }  
  330.   

You can see that we have extended the ImageView so that all the features of the ImageView can be used in the current class.

Step 3

Search any large image (not too large otherwise the heap will not handle it) and put it in the "drawable-hdpi" directory. In my case the image is "ice_age_2.jpg".

Step 4

Open your main activity file and paste the following code into it.

MultiTouchActivity.java

  1. import android.os.Bundle;  
  2. import android.app.Activity;  
  3. import android.view.Menu;  
  4.   
  5. public class MultiTouchActivity extends Activity {  
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         //setContentView(R.layout.activity_main);  
  10.   
  11.         TouchImageView img = new TouchImageView(this);  
  12.         img.setImageResource(R.drawable.ice_age_2);  
  13.         img.setMaxZoom(4f);  
  14.         setContentView(img);  
  15.     }  


Step 5

Run your application.

Note: A real device is required because the emulator doesn't support Multi-Touch.

Pinch-Zoom-Image-View-in-Android-using-Android-Studio2.jpg

Now, touch the image with two fingers and zoom it by expanding your fingers. You can pan after zooming the image.

Pinch-Zoom-Image-View-in-Android-using-Android-Studio3.jpg

Summary

In this article, we learned how to use an ImageView and make another custom view. We also learned the use of a MultiTouch in a view as well as ScaleGestureDetector in our application.
 

Next Recommended Readings