Android Studio pinch scaling - ScaleGestureDetector - doesn't work in a custom View

23 views Asked by At

I have a custom view and I want to make the ability for a user to zoom in and out with pinching (like an image), but it doesn't work. I added the functions from here https://developer.android.com/develop/ui/views/touch-and-input/gestures/scale#basic-scaling-example.

But when I pinch it doesn't scale

I tried to add it to parent activity and it worked but I'm pretty sure ScaleGestureDetector shoud be working in a View itself. Also from what I understood after testing: Everything else works BUT ScaleDetector.onTouchEvent(event) just doesn't detect a pinch and doesn't start a function.

Here's a view class

public class SandboxView extends View {
    float scaleFactor = 1.0f;
    float canvasPosX = 0, canvasPosY = 0;
    Paint paint = new Paint();

    ArrayList<Rect> effectors = new ArrayList<>();

    public SandboxView(Context context) {
        super(context);
        onStart(context);
    }

    public SandboxView(Context context, AttributeSet attrs) {
        super(context, attrs);
        onStart(context);
    }

    public SandboxView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        onStart(context);
    }

    public void onStart(Context context) {
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        effectors.add(new Rect(0, 0, 10, getHeight()));
        effectors.add(new Rect(this.getWidth() - 10, 0, this.getWidth(), this.getHeight()));
        effectors.add(new Rect(0, 0, this.getWidth(), 10));
        effectors.add(new Rect(0, this.getHeight() - 10, this.getWidth(), this.getHeight()));

    }

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

        canvas.scale(scaleFactor, scaleFactor, this.getWidth() / 2.0f, this.getHeight() / 2.0f);

        for (Rect r : effectors) {
            canvas.drawRect(r, paint);
        }

        canvas.restore();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleDetector.onTouchEvent(event);

        super.onTouchEvent(event);
        performClick();

        return super.onTouchEvent(event);
    }

    public void scale(float f_x, float f_y, float factor) {
        scaleFactor *= factor;
        scaleFactor = Math.max(0.01f, Math.min(50.0f, scaleFactor));

        canvasPosX += f_x * (1 - factor);
        canvasPosY += f_y * (1 - factor);

        invalidate();
    }

    private ScaleGestureDetector mScaleDetector;

    private class ScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            Logger.getGlobal().info(detector.getFocusX() + " " + detector.getFocusY());
            scale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
            return true;
        }
    }

    @Override
    public boolean performClick() {
        return super.performClick();
    }
}

And an activity layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.example.a50beees.SandboxView
        android:id="@+id/sandbox_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

UPD: For some reason if I call

sandbox_view.mScaleDetector.onTouchEvent(event);

in the parent activity onTouchEvent it starts working, but I have to make Scale Detector public and it is obviously a bad coding still, what am I doing wrong???

UPD2: I checked events that both Activity.onTouchEvent and View.onTouchEvent get and turns out view doesn't recieve any actions apart from ACTION_DOWN, so when the scaling happens it doesn't get any ACTION_MOVE either, WHY????

UPD3: Well.... Turns out, that apparently you need to return true in your custom onTouchEvent to get any other action... Great

1

There are 1 answers

0
user23903156 On

If anybody else has this problem in the future: it seems it happens because ScaleGestureDetector needs at least ACTION_MOVE to be triggered at all, so if your onTouchEvent doesn't recieve these, scaling will never "hear" the gesture.

To recieve actions apart from ACTION_DOWN, your onTouchEvent in a custom View should return true (to avoid "double clicking" handle actions seperately with switch/case)

@Override
public boolean onTouchEvent(MotionEvent event) {
    mScaleDetector.onTouchEvent(event);

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            performClick();

            // code for click
        }
    }

    return true;
}

this is how it should look like