7

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

Alin
  • 14,809
  • 40
  • 129
  • 218

2 Answers2

3

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);
matiash
  • 54,791
  • 16
  • 125
  • 154
  • I am going to test this today, thank you for your answer. I have tried before to use a matrix like `matrix.setRotate(-mAngle, centerX, centerY)` and then mapPoints but it failed to work properly. – Alin Jul 28 '14 at 08:22
  • I need to use canvas.rotate because of other operations I am doing in the view. Rotating the canvas works pretty well for instance when I rotate an image, even dragging works for images, but I don't know why for words it doesn't. I am not sure I really understand your answer. – Alin Jul 28 '14 at 11:51
  • @Alin sorry I wasn't clear. What I meant was keep everything as you do, including canvas.rotate, but add this extra code. – matiash Jul 28 '14 at 17:28
  • I have added it but it does not have any effect... maybe I am doing something wrong – Alin Jul 28 '14 at 18:48
  • I forgot to mention that canvas.rotate is done around centerx and centery of the rectangle around all the words. – Alin Jul 28 '14 at 19:14
  • @Alin Maybe we're doing something differently, because it works in my machine (TM). Try this gist https://gist.github.com/matiash/4a7c4d6624fd33a31bd1 and/or please include more details about your code :) – matiash Jul 28 '14 at 19:37
  • Hi @matiash, sorry about the mix-up earlier. – Vikram Aug 10 '14 at 09:22
  • @matiash can u answer this question http://stackoverflow.com/questions/27877007/the-rectangle-is-not-working-after-rotating-the-canvas – Asthme Jan 10 '15 at 14:03
1

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.

Vikram
  • 51,313
  • 11
  • 93
  • 122
  • I really don't see the difference between this solution and the gist I posted (?) – matiash Jul 30 '14 at 17:19
  • It is indeed the same approach it was just a bit simpler, only on matrix with -angle – Alin Jul 31 '14 at 12:34
  • @matiash I just gave the gist you posted a sample run - works fine, save one unrelated issue (not important). Thing is, I went through your answer and saw that `Alin` didn't have success with it. So, I coded on my own with the idea that the rotation has to be neutralized (using negation of rotation angle). – Vikram Jul 31 '14 at 19:13
  • @Vikram can you answer this question http://stackoverflow.com/questions/27877007/the-rectangle-is-not-working-after-rotating-the-canvas – Asthme Jan 10 '15 at 14:02