How to properly set fixTransX & fixTransY of TouchImageView to implement doubleTap to ZoomOut?
Asked Answered
F

1

4

I am using TouchImageView from here.

I am trying to implement ZoomOut for double tap. I have done it partially. But, I am facing some issues when zoomed out. If I zoom the Image & if I give a double tap, then image is not getting rendered properly.

Here is my modified version

TouchImageView.java

package com.bharath.downloadissuedemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
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;

    GestureDetector gestureDetector;
    ScaleGestureDetector mScaleDetector;

    Context context;

    public TouchImageView(Context context) {
        super(context);
        sharedConstructing(context);
        gestureDetector = new GestureDetector(getContext(), new GestureListener(this));
    }

    public TouchImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        sharedConstructing(context);
        gestureDetector = new GestureDetector(getContext(), new GestureListener(this));
    }

    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() & MotionEvent.ACTION_MASK) {
                    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 gestureDetector.onTouchEvent(event); // 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;
            System.out.println("Orig Scale = "+origScale);
            saveScale *= mScaleFactor;
            System.out.println("Sace Scale = "+saveScale);

            System.out.println("m Scale Factor = "+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);

        System.out.println("Translation X ="+fixTransX);
        System.out.println("Translation Y ="+fixTransY);
        ((ViewPagerInterface)getContext()).enable();
        if (fixTransX != 0 || fixTransY != 0)
        {           
            matrix.postTranslate(fixTransX, fixTransY);
        }
        else if (saveScale!=1.0) 
        {
            ((ViewPagerInterface)getContext()).disable();
        } 
    }

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

    private class GestureListener extends GestureDetector.SimpleOnGestureListener {

        public TouchImageView touchImageView=null;

        public GestureListener(TouchImageView tmv) {

            touchImageView=tmv;
        }
        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }
        // event when double tap occurs
        @Override
        public boolean onDoubleTap(MotionEvent e) 
        {           
            if (saveScale>1.0f) {
                Drawable d = getDrawable();
                Bitmap bmp = ((BitmapDrawable)d).getBitmap();
                System.out.println("bmp Width = "+bmp.getWidth());
                System.out.println("bmp Height = "+bmp.getHeight());
                System.out.println("Original Width = "+origWidth);
                System.out.println("Original Height = "+origHeight);
                System.out.println("View Width "+viewWidth);
                System.out.println("View height"+viewHeight);
                float scaleX= viewWidth/(origWidth*saveScale);
                float scaleY =viewHeight/(origHeight*saveScale);
                float finalScale = Math.min(scaleX, scaleY);

                matrix.postScale(finalScale, finalScale, viewWidth / 2, viewHeight / 2);
                saveScale=1.0f;

//              
//              float redundantYSpace = (float) viewHeight - (finalScale * (float) bmp.getHeight());
//              float redundantXSpace = (float) viewWidth - (finalScale * (float) bmp);
//              redundantYSpace /= (float) 2;
//              redundantXSpace /= (float) 2;
//              matrix.postTranslate(redundantXSpace, redundantYSpace);

                 matrix.getValues(m);
                 float transX = m[Matrix.MTRANS_X];
                 float transY = m[Matrix.MTRANS_Y];

                 float fixTransX = getFixTrans(transX, viewWidth, origWidth * finalScale);
                 float fixTransY = getFixTrans(transY, viewHeight, origHeight * finalScale);
//
                 System.out.println("Translation X ="+transX);
                 System.out.println("Translation Y ="+transY);

                 System.out.println("Fix X ="+fixTransX);
                 System.out.println("Fix Y ="+fixTransY);
                 ((ViewPagerInterface)getContext()).enable();
                 matrix.postTranslate(fixTransX, fixTransY);
            }

//          fixTrans();


//          System.out.println("Intrinsic Width "+bmp.getWidth());
//          System.out.println("Intrinsic Height "+bmp.getHeight());
//          RectF drawableRect = new RectF(0, 0,bmp.getWidth(),bmp.getHeight());
//          RectF viewRect = new RectF(0, 0,getWidth(), getHeight());
//          m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER);
//          setScale(,);
//          setScaleX(getWidth()/bmp.getWidth());
//          setScaleY( getHeight()/bmp.getHeight());

//setScaleX(scaleX)//           saveScale=1.0f;
//          setImageMatrix(m);

//          invalidate();
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {

//          System.out.println("SIngle Tap Occurred");
//          touchImageView.saveScale=1.0f;
//          touchImageView.invalidate();
            return super.onSingleTapConfirmed(e);
        }
    }
}

In the above code, please see the method

 public boolean onDoubleTap(MotionEvent e)

What was the mistake I made in the above method?

Foothill answered 17/12, 2012 at 16:57 Comment(0)
H
5

UPDATE

I've updated TouchImageView to include animated double tap zoom as well as fling. You can check it out on github.


The basic approach is to save the original matrix when it is calculated in onMeasure. We cannot simply set matrix = originalMatrix as this is just setting two pointers to point to the same object. We need to make a deep copy which is done with: matrix = new Matrix(originalMatrix); We also need to reset saveScale to 1f and call invalidate().

public class TouchImageView extends ImageView {

Matrix matrix;
Matrix originalMatrix;

// 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;
GestureDetector mGestureDetector;

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());
    mGestureDetector = new GestureDetector(context, new GestureListener());
    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);
            mGestureDetector.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;
    }
}

private class GestureListener extends GestureDetector.SimpleOnGestureListener {

    // event when double tap occurs
    @Override
    public boolean onDoubleTap(MotionEvent e) 
    {      
        matrix = new Matrix(originalMatrix);
        saveScale = 1f;
        setImageMatrix(matrix);
        invalidate();
        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);
        originalMatrix = new Matrix(matrix);

        origWidth = viewWidth - 2 * redundantXSpace;
        origHeight = viewHeight - 2 * redundantYSpace;
        setImageMatrix(matrix);
    }
    fixTrans();
}
}
Horatio answered 25/12, 2012 at 2:47 Comment(2)
This works perfectly. But I cannot still implement doubleTap to zoomIn to maximum(keeping tap position in center). Please HelpPhonics
@PankajVatsa I wanted to implement doubleTap to zoom in to maximum too.. did you manage to find a solution?Sofer

© 2022 - 2024 — McMap. All rights reserved.