javaandroidscaleandroid-custom-view

How to prevent a custom view from moving when scaling on Android?


I am implementing scaling functionality inside a custom view on Android, based on the tutorial from here. The scaling feature seems to be working fine, but I am facing an issue where the view moves to the origin (0, 0) when I scale down. I want the view to remain in its original position while scaling.

Here's the code for my custom view:

public class MyCustomView extends View {

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.0f;

    private Paint paint;

    public MyCustomView(Context context) {
        super(context);
        init(context);
    }

    public MyCustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float cx = getWidth()/2f;
        float cy = getHeight()/2f;

        canvas.save();
        canvas.scale(mScaleFactor, mScaleFactor);

        canvas.drawRect(cx - 100, cy - 100, cx + 100, cy + 100, paint);

        canvas.restore();
    }

    private class ScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            invalidate();
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);
        return true;
    }
}

Currently, when I scale down, the rectangle moves to the point (0, 0), instead of remaining at the center of the screen. I want to fix this issue and ensure that the view stays in its original position. How can I do this?

I have attached a GIF that shows the issue:

enter image description here


Solution

  • I guess that's because you are scaling the whole Canvas, doing so it shrinks towards origin (0,0).

    canvas.scale(mScaleFactor, mScaleFactor);

    You can solve this setting a pivot to the scale, like this as we discussed in the comments

    canvas.scale(mScaleFactor, mScaleFactor, cx, cy);