Reader Level:
Articles

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

By Chintan Rathod on July 04, 2013
In this article, I am going to teach you can use Multi-touch with Image View in Android to replicate Gallery feature. So let’s begin.
  • 3
  • 0
  • 50780
Download Files:
 

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;

 

    // We can be in one of these 3 states

    static final int NONE = 0;

    static final int DRAG = 1;

    static final int ZOOM = 2;

    int mode = NONE;

 

    // Remember some things for zooming

    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; // indicate event was handled

            }

 

        });

    }

 

    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);

        

        //

        // Rescales image on rotation

        //

        if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight

                || viewWidth == 0 || viewHeight == 0)

            return;

        oldMeasuredHeight = viewHeight;

        oldMeasuredWidth = viewWidth;

 

        if (saveScale == 1) {

            //Fit to screen.

            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);

 

            // Center the image

            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);
        //setContentView(R.layout.activity_main);

        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.
 

Chintan Rathod

He completed his Master of Computer Application from Gujarat Technological University 2012. His area of Interests are Android and Java Technologies.

COMMENT USING

Trending up