Anatol
Anatol

Reputation: 961

Android. Canvas scale and translate

I created custom view where you can touch and scale it. Most of work was created with the help of this post

Next i observe that if i want to zoom in image it is always zooming to top left corner. Here is my onDraw() method:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.translate(mPosX, mPosY);
    canvas.scale(mScaleFactor, mScaleFactor);

    // actual drawing

    canvas.getClipBounds(mRect);
    canvas.restore();
}

So this is normal behaviour for canvas.scale(px, py); I want from my view be scaling to center point(later i will take focalX, focalY coordinates, but now just to center). I decided to replace

canvas.scale(mScaleFactor, mScaleFactor); to

canvas.scale(mScaleFactor, mScaleFactor, getWidth()/2-mPosX, getHeight()/2-mPosY);

And now it is scaling perfectly at the mid of the image. But it somehow affects my mPosX, mPosY coordinates. I attach an image for better understanding.

Top left corner is eqivalent mPosX = 0, mPosY=0

Top left corner is eqivalent mPosX = 0, mPosY=0, and that's OK.

Next if i am zooming out next happens:

enter image description here

Now it decides that top left corner is eqivalent to mPosX=0,mPosY=0(top left corner). But in my logic it should be somehow like (mPosX=100, mPosY=130) or something like that.

So my problem is that i am sliding out from my actual content view. What should i do to prevent such behaviour?

Upvotes: 6

Views: 8215

Answers (2)

isuru
isuru

Reputation: 3565

I had same question regarding touch points calculation and referring this answer, I solved it my own way using metrics. Above answer provides better pinch zooming and dragging functionality but not correct touch points. Here is the full code and ignore my own variables.

package axis.nbapp;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import java.io.IOException;

public class NewView extends ViewGroup {
    float dx;
    float dy;
    float scaleFac;

    Region r;
    private Path mPath;
    private Paint mPaint;
    private Path mTransformedPath;
    Stencil stencil;
    private Paint cColor;
    boolean scaling;
    Canvas bmcanvas;
    SVG svg = null;
    int viewWidth;
    int viewHeight;
    float svgWidth;
    float svgHeight;

    // States.
    private static final byte NONE = 0;
    private static final byte DRAG = 1;
    private static final byte ZOOM = 2;

    private byte mode = NONE;

    // Matrices used to move and zoom image.
    private Matrix matrix = new Matrix();
    private Matrix matrixInverse = new Matrix();
    private Matrix savedMatrix = new Matrix();


    // Parameters for zooming.
    private PointF start = new PointF();
    private PointF mid = new PointF();
    private float oldDist = 1f;
    private float[] lastEvent = null;
    private long lastDownTime = 0l;

    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        mDispatchTouchEventWorkingArray[0] = ev.getX();
        mDispatchTouchEventWorkingArray[1] = ev.getY();
        mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
        ev.setLocation(mDispatchTouchEventWorkingArray[0], mDispatchTouchEventWorkingArray[1]);
        return super.dispatchTouchEvent(ev);
    }

    public NewView(Context context) {
        super(context);
        init(context);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);


    }

    public NewView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public NewView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }


    private void init(Context context) {

    }


    /**
     * Determine the space between the first two fingers
     */
    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * Calculate the mid point of the first two fingers
     */
    private void midPoint(PointF point, MotionEvent event) {
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
    }

    private float[] scaledPointsToScreenPoints(float[] a) {
        matrix.mapPoints(a);
        return a;
    }

    private float[] screenPointsToScaledPoints(float[] a) {
        matrixInverse.mapPoints(a);
        return a;
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        float[] values = new float[9];
        matrix.getValues(values);
        canvas.save();
        canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
        canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);



        super.dispatchDraw(canvas);
        canvas.restore();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        // handle touch events here
        mOnTouchEventWorkingArray[0] = event.getX();
        mOnTouchEventWorkingArray[1] = event.getY();

        mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);

        event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);

        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                savedMatrix.set(matrix);
                mode = DRAG;
                lastEvent = null;
                long downTime = event.getDownTime();
                if (downTime - lastDownTime < 300l) {
                    float density = getResources().getDisplayMetrics().density;
                    if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 40.f * density) {
                        savedMatrix.set(matrix);
                        mid.set(event.getX(), event.getY());
                        mode = ZOOM;
                        lastEvent = new float[4];
                        lastEvent[0] = lastEvent[1] = event.getX();
                        lastEvent[2] = lastEvent[3] = event.getY();
                    }
                    lastDownTime = 0l;
                } else {
                    lastDownTime = downTime;
                }
                start.set(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                oldDist = spacing(event);
                if (oldDist > 10f) {
                    savedMatrix.set(matrix);
                    midPoint(mid, event);
                    mode = ZOOM;
                }
                lastEvent = new float[4];
                lastEvent[0] = event.getX(0);
                lastEvent[1] = event.getX(1);
                lastEvent[2] = event.getY(0);
                lastEvent[3] = event.getY(1);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                lastEvent = null;
                break;
            case MotionEvent.ACTION_MOVE:
                final float density = getResources().getDisplayMetrics().density;
                if (mode == DRAG) {
                    matrix.set(savedMatrix);
                    dx = event.getX() - start.x;
                    dy = event.getY() - start.y;
                    matrix.postTranslate(dx, dy);
                    matrix.invert(matrixInverse);
                    if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
                        lastDownTime = 0l;
                    }
                } else if (mode == ZOOM) {
                    if (event.getPointerCount() > 1) {
                        float newDist = spacing(event);
                        if (newDist > 10f * density) {
                            matrix.set(savedMatrix);
                            float scale = (newDist / oldDist);
                            scaleFac = newDist;
                            matrix.postScale(scale, scale, mid.x, mid.y);
                            matrix.invert(matrixInverse);
                        }
                    } else {
                        matrix.set(savedMatrix);
                        float scale = event.getY() / start.y;
                        scaleFac = scale;
                        matrix.postScale(scale, scale, mid.x, mid.y);
                        matrix.invert(matrixInverse);
                    }
                }

                break;
        }


        float[] values = new float[9];
        matrix.getValues(values);


        //int x = ((int)(event.getX() - values[2]*values[Matrix.MSCALE_X]))/(int)values[0];
        //int y = ((int)(event.getY() - values[5]*values[Matrix.MSCALE_Y]))/(int)values[4];

        int x = ((int)(event.getX() / values[Matrix.MSCALE_X] - (values[Matrix.MTRANS_X]/values[Matrix.MSCALE_X])));
        int y = ((int)(event.getY() / values[Matrix.MSCALE_Y] - (values[Matrix.MTRANS_Y]/values[Matrix.MSCALE_Y])));


        invalidate();
        return true;
    }


}

Check onTouchEvent you can find transformed x, y touch points from following calculation.

 int x = ((int)(event.getX() / values[Matrix.MSCALE_X] - (values[Matrix.MTRANS_X]/values[Matrix.MSCALE_X])));
 int y = ((int)(event.getY() / values[Matrix.MSCALE_Y] - (values[Matrix.MTRANS_Y]/values[Matrix.MSCALE_Y])));

Upvotes: 5

Thpramos
Thpramos

Reputation: 441

Did you try just using getWidth()/2?

As I remember the pivot point parameter always ask for a point from 0 to width. And 0 is always on top left of the image.

So I would try just:

canvas.scale(mScaleFactor, mScaleFactor, getWidth()/2, getHeight()/2);

Upvotes: -1

Related Questions