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
- import android.content.Context;
- import android.graphics.Matrix;
- import android.graphics.PointF;
- import android.graphics.drawable.Drawable;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.view.ScaleGestureDetector;
- import android.view.View;
- import android.widget.ImageView;
-
- public class TouchImageView extends ImageView {
- Matrix matrix;
-
- static final int NONE = 0;
- static final int DRAG = 1;
- static final int ZOOM = 2;
-
- int mode = NONE;
-
-
- PointF last = new PointF();
- PointF start = new PointF();
- float minScale = 1f;
- float maxScale = 3f;
- float[] m;
- int viewWidth, viewHeight;
-
- static final int CLICK = 3;
-
- float saveScale = 1f;
-
- protected float origWidth, origHeight;
-
- int oldMeasuredWidth, oldMeasuredHeight;
-
- ScaleGestureDetector mScaleDetector;
-
- Context context;
-
- public TouchImageView(Context context) {
- super(context);
- sharedConstructing(context);
- }
- public TouchImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- sharedConstructing(context);
- }
-
- private void sharedConstructing(Context context) {
-
- super.setClickable(true);
-
- this.context = context;
-
- mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
-
- matrix = new Matrix();
-
- m = new float[9];
-
- setImageMatrix(matrix);
-
- setScaleType(ScaleType.MATRIX);
-
- setOnTouchListener(new OnTouchListener() {
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
-
- mScaleDetector.onTouchEvent(event);
-
- PointF curr = new PointF(event.getX(), event.getY());
-
- switch (event.getAction()) {
-
- case MotionEvent.ACTION_DOWN:
-
- last.set(curr);
-
- start.set(last);
-
- mode = DRAG;
-
- break;
-
- case MotionEvent.ACTION_MOVE:
-
- if (mode == DRAG) {
-
- float deltaX = curr.x - last.x;
-
- float deltaY = curr.y - last.y;
-
- float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
-
- float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
-
- matrix.postTranslate(fixTransX, fixTransY);
-
- fixTrans();
-
- last.set(curr.x, curr.y);
-
- }
-
- break;
-
- case MotionEvent.ACTION_UP:
-
- mode = NONE;
-
- int xDiff = (int) Math.abs(curr.x - start.x);
-
- int yDiff = (int) Math.abs(curr.y - start.y);
-
- if (xDiff < CLICK && yDiff < CLICK)
-
- performClick();
-
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
-
- mode = NONE;
-
- break;
-
- }
-
- setImageMatrix(matrix);
-
- invalidate();
-
- return true;
-
- }
-
- });
- }
-
- public void setMaxZoom(float x) {
-
- maxScale = x;
-
- }
-
- private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
-
- mode = ZOOM;
-
- return true;
-
- }
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
-
- float mScaleFactor = detector.getScaleFactor();
-
- float origScale = saveScale;
-
- saveScale *= mScaleFactor;
-
- if (saveScale > maxScale) {
-
- saveScale = maxScale;
-
- mScaleFactor = maxScale / origScale;
-
- } else if (saveScale < minScale) {
-
- saveScale = minScale;
-
- mScaleFactor = minScale / origScale;
-
- }
-
- if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
-
- matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
-
- else
-
- matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
-
- fixTrans();
-
- return true;
-
- }
-
- }
-
- void fixTrans() {
-
- matrix.getValues(m);
-
- float transX = m[Matrix.MTRANS_X];
-
- float transY = m[Matrix.MTRANS_Y];
-
- float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
-
- float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
-
- if (fixTransX != 0 || fixTransY != 0)
-
- matrix.postTranslate(fixTransX, fixTransY);
-
- }
-
-
-
- float getFixTrans(float trans, float viewSize, float contentSize) {
-
- float minTrans, maxTrans;
-
- if (contentSize <= viewSize) {
-
- minTrans = 0;
-
- maxTrans = viewSize - contentSize;
-
- } else {
-
- minTrans = viewSize - contentSize;
-
- maxTrans = 0;
-
- }
-
- if (trans < minTrans)
-
- return -trans + minTrans;
-
- if (trans > maxTrans)
-
- return -trans + maxTrans;
-
- return 0;
-
- }
-
- float getFixDragTrans(float delta, float viewSize, float contentSize) {
-
- if (contentSize <= viewSize) {
-
- return 0;
-
- }
-
- return delta;
-
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- viewWidth = MeasureSpec.getSize(widthMeasureSpec);
-
- viewHeight = MeasureSpec.getSize(heightMeasureSpec);
-
-
-
-
- if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
-
- || viewWidth == 0 || viewHeight == 0)
-
- return;
-
- oldMeasuredHeight = viewHeight;
-
- oldMeasuredWidth = viewWidth;
-
- if (saveScale == 1) {
-
-
-
- float scale;
-
- Drawable drawable = getDrawable();
-
- if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
-
- return;
-
- int bmWidth = drawable.getIntrinsicWidth();
-
- int bmHeight = drawable.getIntrinsicHeight();
-
- Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
-
- float scaleX = (float) viewWidth / (float) bmWidth;
-
- float scaleY = (float) viewHeight / (float) bmHeight;
-
- scale = Math.min(scaleX, scaleY);
-
- matrix.setScale(scale, scale);
-
-
-
- float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
-
- float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
-
- redundantYSpace /= (float) 2;
-
- redundantXSpace /= (float) 2;
-
- matrix.postTranslate(redundantXSpace, redundantYSpace);
-
- origWidth = viewWidth - 2 * redundantXSpace;
-
- origHeight = viewHeight - 2 * redundantYSpace;
-
- setImageMatrix(matrix);
-
- }
-
- fixTrans();
-
- }
-
- }
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
- import android.os.Bundle;
- import android.app.Activity;
- import android.view.Menu;
-
- public class MultiTouchActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- TouchImageView img = new TouchImageView(this);
- img.setImageResource(R.drawable.ice_age_2);
- img.setMaxZoom(4f);
- setContentView(img);
- }
- }
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.