简体   繁体   中英

Android rotate view around dynamic pivot

I'm developing a ViewGroup layout which is able to detect touch events and scale/rotate/pan it's child view based on touch events. My problem is that I would like the child to rotate/zoom around the point between the user's fingers two fingers, performing the gesture, not around its center.

I've got the gesture recognizers in place and I am already able to scale/pan/rotate around the center of the child. I think the main issue is transforming the pivot point (which in my case would be the midpoint between the user's first and second pointers) to the child's current rotation. The first time I set the pivot, it works nicely, but as soon as I rotate the view, lift fingers and then try to rotate around another pivot, the view jumps around. Here's how it looks (only with rotation):

在此处输入图片说明

So when the first rotation happens it works as expected and it rotates around the point between the two fingers. However, once rotated, the next rotation jumps to a new location. Here's the relevant code:

@Override
public boolean onRotationBegin(RotationGestureDetector detector) {
    float[] coordinates = screenPointsToScaledPoints(new float[]{detector.getFocusX(), detector.getFocusY()});
    pivotX = coordinates[0];
    pivotY = coordinates[1];
    child().setPivotX(pivotX);
    child().setPivotY(pivotY);
    return true;
}

@Override
public boolean onRotate(RotationGestureDetector detector) {
    rotation += detector.getRotationDelta();
    return true;
}

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

private void applyScaleAndTranslation() {
    View child = child();
    child.setRotation(rotation);
    mRotateMatrix.setRotate(rotation, pivotX, pivotY);
    mRotateMatrix.invert(mRotateMatrixInverse);
}

Any ideas what I could be missing?

Okay so, thanks to @pskink I've managed to get to the answer. The key was here - Android ImageView Scaling and translating issue

My main mistake was that I was using separate matrices for scaling, moving and rotating whereas in the suggested answer there is only one which contains all operations, which is what I ended up doing. Here's the interesting part of the code:

private Matrix mMatrix = new Matrix();
private Matrix mMatrixInverse = new Matrix();

@Override
protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.concat(mMatrix);
    super.dispatchDraw(canvas);
    canvas.restore();
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    doubleFingers = ev.getPointerCount() == 2;
    if (!doubleFingers) {
        // Map touch coordinates to our matrix to draw correctly in the canvas.
        float[] mapped = screenPointsToScaledPoints(new float[]{ev.getX(0), ev.getY(0)});
        ev.setLocation(mapped[0], mapped[1]);
    }
    return super.dispatchTouchEvent(ev);
}

private float[] screenPointsToScaledPoints(float[] a){
    mMatrix.invert(mMatrixInverse);
    mMatrixInverse.mapPoints(a);
    return a;
}

@Override
public boolean onScale(ScaleGestureDetector detector) {
    float scaleFactor = detector.getScaleFactor();
    if (scale >= MIN_CANVAS_ZOOM) {
        scale *= scaleFactor;
        scale = Math.max(MIN_CANVAS_ZOOM, Math.min(scale, MAX_CANVAS_ZOOM));
        mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
    }
    return true;
}

@Override
public boolean onRotate(RotationGestureDetector detector) {
    rotation += detector.getRotationDelta();
    mMatrix.postRotate(detector.getRotationDelta(), detector.getFocusX(), detector.getFocusY());
    return true;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    scaleDetector.onTouchEvent(event);
    rotationDetector.onTouchEvent(event);
    // Lots of business logic
    dX = coordinates[0] - mLastTouchX;
    dY = coordinates[1] - mLastTouchY;
    mLastTouchX = coordinates[0];
    mLastTouchY = coordinates[1];
    mMatrix.postTranslate(dX, dY);
    invalidate();
    return false;
}

In my case I have to manually detect two-finger pan because the one-finger event is used to paint on an OpenGL surface, hence the translation from real-world to scaled matrix coordinates. Here's the result:

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM