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.
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.
Now, touch the image with two fingers and zoom it by expanding your fingers. You can pan after zooming the image.
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.