Alin
Alin

Reputation: 14581

Dragging rotated text inside android canvas does not work as expected

There is something I am missing inhere so I hope you can share some light on me.

I am drawing some text inside canvas. For this I have a class Word

public class Word {
     private int    x;
     private int    y;
     private String text;
}

The app allows the user to rotate the text, and I handle the rotation withing onDraw

protected void onDraw(Canvas canvas) {
    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    canvas.rotate(angle, centerX, centerY)
    ...
    canvas.drawText(word.getText(), word.getX(), word.getY())
    ....
    canvas.restore();
}

The problem I get is when the user drags the canvas and there is a rotation set. When angle=0 the movement is going as expected.

@Override
    public boolean onTouchEvent(MotionEvent event) {
       case MotionEvent.ACTION_DOWN:
            initialX = (int) event.getX();
            initialY = (int) event.getY();
        break;
      case MotionEvent.ACTION_MOVE:
             int currentX = (int) event.getX();
             int currentY = (int) event.getY();
             int xMovement = currentX - initialX;
             int yMovement = currentY - initialY;

            dragWords(xMovement, yMovement);
   .....

and on dragWords for each word I do:

private void dragText(int xMovement, int yMovement){
for (Word word : words) {
    word.setX(word.getX() + xMovement);
    word.setY(word.getY() + yMovement);
}
invalidate();

}

When rotation angle is 0, moving up/down/left/right makes the words move by the same distance. As angle gets bigger, the words start to move in different dirrections, for instance at 60, it is starting to go diagonally up, when 180 it only moves up/down and not left/right.

I think I need to calculate some sort of a difference based on angle and add it to xMovement/yMovement... but how should I do this ?

LE: Here is an image on how it behaves: enter image description here The blue lines is how the text is moving on drag while the orange is the finger dragging on the screen. When angle is 0 it works quite well, when angle increases, it starts to move diagonally on left/right, while when angle is even bigger, it only moves up and down and does not respond to left/right

Upvotes: 7

Views: 2025

Answers (2)

Vikram
Vikram

Reputation: 51581

In the ellipses part in the following code:

dragWords(xMovement, yMovement);
.....   <<<--------------------- I hope you are updating initialX and initialY

initialX = currentX;
initialY = currentY;

Otherwise, your x and y values will not correspond correctly with the amount of distance moved during the touch gesture.

As user matiash indicated, you should use Matrix#mapPoints(float[]) to transform your x and y values. Declare and initialize a Matrix:

Matrix correctionMatrix;

// Your view's constructor
public MyView() {
    ....
    correctionMatrix = new Matrix();
}

Here's how your onDraw(Canvas) should look like:

@Override
protected void onDraw(Canvas canvas) {
    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    canvas.rotate(angle, centerX, centerY);
    ...

    // Neutralize the rotation
    correctionMatrix.setRotate(-angle, centerX, centerY);

    // Initialize a float array that holds the original coordinates
    float[] src = {word.getX(), word.getY()};

    // Load transformed values into `src` array
    correctionMatrix.mapPoints(src);

    // `src[0]` and `src[1]` hold the transformed `X` and `Y` coordinates
    canvas.drawText(word.text, src[0], src[1], somePaint);
    ....
    canvas.restore();
}

This should give you the desired results - movement in the X and Y axis irrespective of canvas rotation.

You can obviously move the call to setRotate(float, float, float) to a better place. You only need to call it once after changing the angle value.

Upvotes: 1

matiash
matiash

Reputation: 55360

If I understand correctly, the issue is that Canvas.rotate() does not only rotate the text direction, but rather the whole canvas. Therefore, the x-y coordinates of the words are also rotated from the specified pivot point.

In order to match the dragging movement, you can use a Matrix for this, more specifically the inverse matrix of the one you're using to rotate the canvas. It will be used to convert the x-y coordinates of the words to their original, pre-rotate locations.

For example, calculate this once, and update it whenever angle, centerX, or centerY changes.

// rotMatrix is the same operation applied on the canvas.
Matrix rotMatrix = new Matrix();
rotMatrix.postRotate(mAngle, centerX, centerY);

// Invert it to convert x, y to their transformed positions.
Matrix matrix = new Matrix();
rotMatrix.invert(matrix);

Then, when drawing each word:

int wordX = ... 
int wordY = ...
String text = ...

float[] coords = new float[] { wordX, wordY };
matrix.mapPoints(coords);
canvas.drawText(text, coords[0], coords[1], paint);

Upvotes: 3

Related Questions