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

In this article, we can learn to use Multi-touch with Image View in Android to replicate Gallery feature.

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 built-in Gallery. It provides pinch zoom & pan. But it is provided by the built-in 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 in Android

Pinch Zoom Image in Android

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.     int mode = NONE;  
  19.     // Remember some things for zooming  
  20.     PointF last = new PointF();  
  21.     PointF start = new PointF();  
  22.     float minScale = 1f;  
  23.     float maxScale = 3f;  
  24.     float[] m;  
  25.     int viewWidth, viewHeight;  
  26.     static final int CLICK = 3;  
  27.     float saveScale = 1f;  
  28.     protected float origWidth, origHeight;  
  29.     int oldMeasuredWidth, oldMeasuredHeight;  
  30.     ScaleGestureDetector mScaleDetector;  
  31.     Context context;  
  32.     public TouchImageView(Context context) {  
  33.         super(context);  
  34.         sharedConstructing(context);  
  35.     }  
  36.   
  37.     public TouchImageView(Context context, AttributeSet attrs) {  
  38.         super(context, attrs);  
  39.         sharedConstructing(context);  
  40.     }  
  41.     private void sharedConstructing(Context context) {  
  42.         super.setClickable(true);  
  43.         this.context = context;  
  44.         mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());  
  45.         matrix = new Matrix();  
  46.         m = new float[9];  
  47.         setImageMatrix(matrix);  
  48.         setScaleType(ScaleType.MATRIX);  
  49.         setOnTouchListener(new OnTouchListener() {  
  50.             @Override  
  51.             public boolean onTouch(View v, MotionEvent event) {  
  52.                 mScaleDetector.onTouchEvent(event);  
  53.                 PointF curr = new PointF(event.getX(), event.getY());  
  54.                 switch (event.getAction()) {  
  55.                     case MotionEvent.ACTION_DOWN:  
  56.                        last.set(curr);  
  57.                         start.set(last);  
  58.                         mode = DRAG;  
  59.                         break;  
  60.                     case MotionEvent.ACTION_MOVE:  
  61.                         if (mode == DRAG) {  
  62.                             float deltaX = curr.x - last.x;  
  63.                             float deltaY = curr.y - last.y;  
  64.                             float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);  
  65.                             float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);  
  66.                             matrix.postTranslate(fixTransX, fixTransY);  
  67.                             fixTrans();  
  68.                             last.set(curr.x, curr.y);  
  69.                         }  
  70.                         break;  
  71.                     case MotionEvent.ACTION_UP:  
  72.                         mode = NONE;  
  73.                         int xDiff = (int) Math.abs(curr.x - start.x);  
  74.                         int yDiff = (int) Math.abs(curr.y - start.y);  
  75.                         if (xDiff < CLICK && yDiff < CLICK)  
  76.                             performClick();  
  77.                         break;  
  78.                     case MotionEvent.ACTION_POINTER_UP:  
  79.                         mode = NONE;  
  80.                         break;  
  81.                 }  
  82.                 setImageMatrix(matrix);  
  83.                 invalidate();  
  84.                 return true// indicate event was handled  
  85.             }  
  86.         });  
  87.     }  
  88.     public void setMaxZoom(float x) {  
  89.         maxScale = x;  
  90.     }  
  91.     private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {  
  92.         @Override  
  93.         public boolean onScaleBegin(ScaleGestureDetector detector) {  
  94.             mode = ZOOM;  
  95.             return true;  
  96.         }  
  97.         @Override  
  98.         public boolean onScale(ScaleGestureDetector detector) {  
  99.             float mScaleFactor = detector.getScaleFactor();  
  100.             float origScale = saveScale;  
  101.             saveScale *= mScaleFactor;  
  102.             if (saveScale > maxScale) {  
  103.                 saveScale = maxScale;  
  104.                 mScaleFactor = maxScale / origScale;  
  105.             } else if (saveScale < minScale) {  
  106.                 saveScale = minScale;  
  107.                 mScaleFactor = minScale / origScale;  
  108.             }  
  109.             if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)  
  110.                 matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);  
  111.             else  
  112.                 matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());  
  113.             fixTrans();  
  114.             return true;  
  115.         }  
  116.     }  
  117.     void fixTrans() {  
  118.         matrix.getValues(m);  
  119.         float transX = m[Matrix.MTRANS_X];  
  120.         float transY = m[Matrix.MTRANS_Y];  
  121.         float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);  
  122.         float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);  
  123.         if (fixTransX != 0 || fixTransY != 0)  
  124.             matrix.postTranslate(fixTransX, fixTransY);  
  125.     }  
  126.     float getFixTrans(float trans, float viewSize, float contentSize) {  
  127.         float minTrans, maxTrans;  
  128.         if (contentSize <= viewSize) {  
  129.             minTrans = 0;  
  130.             maxTrans = viewSize - contentSize;  
  131.         } else {  
  132.             minTrans = viewSize - contentSize;  
  133.             maxTrans = 0;  
  134.         }  
  135.         if (trans < minTrans)  
  136.             return -trans + minTrans;  
  137.         if (trans > maxTrans)  
  138.             return -trans + maxTrans;  
  139.         return 0;  
  140.     }   
  141.     float getFixDragTrans(float delta, float viewSize, float contentSize) {    
  142.         if (contentSize <= viewSize) {  
  143.             return 0;  
  144.         }  
  145.         return delta;  
  146.     }  
  147.     @Override  
  148.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  149.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  150.         viewWidth = MeasureSpec.getSize(widthMeasureSpec);  
  151.         viewHeight = MeasureSpec.getSize(heightMeasureSpec);  
  152.         //  
  153.         // Rescales image on rotation  
  154.         //  
  155.         if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight  
  156.                 || viewWidth == 0 || viewHeight == 0)  
  157.             return;  
  158.         oldMeasuredHeight = viewHeight;  
  159.         oldMeasuredWidth = viewWidth;   
  160.         if (saveScale == 1) {  
  161.             //Fit to screen.  
  162.             float scale;  
  163.             Drawable drawable = getDrawable();  
  164.             if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)  
  165.                 return;  
  166.             int bmWidth = drawable.getIntrinsicWidth();  
  167.             int bmHeight = drawable.getIntrinsicHeight();  
  168.             Log.d("bmSize""bmWidth: " + bmWidth + " bmHeight : " + bmHeight);  
  169.             float scaleX = (float) viewWidth / (float) bmWidth;  
  170.             float scaleY = (float) viewHeight / (float) bmHeight;  
  171.             scale = Math.min(scaleX, scaleY);  
  172.             matrix.setScale(scale, scale);  
  173.             // Center the image  
  174.             float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);  
  175.             float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);  
  176.             redundantYSpace /= (float2;  
  177.             redundantXSpace /= (float2;  
  178.             matrix.postTranslate(redundantXSpace, redundantYSpace);  
  179.             origWidth = viewWidth - 2 * redundantXSpace;  
  180.             origHeight = viewHeight - 2 * redundantYSpace;  
  181.             setImageMatrix(matrix);  
  182.         }  
  183.         fixTrans();  
  184.     }  
  185. }  
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.     }  
  16. }  
Step 5

Run your application.

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

Pinch Zoom Image in Android

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

Pinch Zoom Image in Android

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.