Android: как вращать элемент, касаясь его? - PullRequest
3 голосов
/ 14 ноября 2010

Я пытаюсь повернуть нарисованное внутри представления, касаясь его и двигая пальцем. Я предложил несколько решений, но ни одно из них не выглядит естественным для устройства.

Вот мой первый подход: в зависимости от того, где пользователь коснулся экрана и в каком направлении был перемещен палец, я изменяю вращение отрисовки, рассчитанное более или менее произвольно.

private void updateRotation(float x, float y, float oldX, float oldY) {
    int width = getWidth();
    int height = getHeight();

    float centerX = width / 2;
    float centerY = height / 2;

    float xSpeed = rotationSpeed(x, oldX, width);
    float ySpeed = rotationSpeed(y, oldY, height); 

    if ((y < centerY && x > oldX)
        || (y > centerY && x < oldX))
        rotation += xSpeed;
    else if ((y < centerY && x < oldX)
             || (y > centerY && x > oldX))
        rotation -= xSpeed;

    if ((x > centerX && y > oldY)
        || (x < centerX && y < oldY))
        rotation += ySpeed;
    else if ((x > centerX && y < oldY)
             || (x < centerX && y > oldY))
        rotation -= ySpeed;
}

private static float rotationSpeed(float pos, float oldPos, float max) {
    return (Math.abs(pos - oldPos) * 180) / max;
}

У этого подхода было несколько раздражающих побочных эффектов: иногда выдвигаемый объект вращался, когда палец не двигался, и вращение обычно было не таким быстрым, как палец пользователя.

Поэтому я выбросил этот код и начал со второго подхода. Я использую тригонометрию для вычисления фактического вращения, которое было бы эквивалентно движению пальца:

private void updateRotation(float x, float y, float oldX, float oldY) {
    float centerX = getWidth() / 2;
    float centerY = getHeight() / 2;

    float a = distance(centerX, centerY, x, y);
    float b = distance(centerX, centerY, oldX, oldY);
    float c = distance(x, y, oldX, oldY);

    double r = Math.acos((Math.pow(a, 2) + Math.pow(b, 2) - Math.pow(c, 2))
                         / (2 * a * b));

    if ((oldY < centerY && x < oldX)
        || (oldY > centerY && x > oldX)
        || (oldX > centerX && y < oldY)
        || (oldX < centerX && y > oldY))
        r *= -1; 

    rotation += (int) Math.toDegrees(r);
}

private float distance(float x1, float y1, float x2, float y2) {
    return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}

Хотя для меня это звучит как правильное решение, оно тоже не работает. Движение пальцев иногда игнорируется - может ли расчет быть слишком дорогим? Кроме того, вращение все еще немного быстрее, чем фактическое движение пальца.

В идеале, если я начну вращать рисование, касаясь определенной точки, эта самая точка должна всегда оставаться ниже пальца. Можете ли вы сказать мне, как этого добиться?

Edit:

Вот мое принятие предложения Снейлера. Мне пришлось переключить аргументы для метода atan2, чтобы он вращался в правильном направлении, теперь он прекрасно работает:

private void updateRotation(float x, float y) {
    double r = Math.atan2(x - getWidth() / 2, getHeight() / 2 - y);
    rotation = (int) Math.toDegrees(r);
}

1 Ответ

4 голосов
/ 15 ноября 2010

Это можно легко сделать, установив угол, созданный пальцем, и центр экрана. Подобно тому, что у вас есть выше во втором примере. В вашем onTouchEvent отправьте getX()/getY() этому методу:

    private double getDegreesFromTouchEvent(float x, float y){
        double delta_x = x - (Screen Width) /2;
        double delta_y = (Screen Height) /2 - y;
        double radians = Math.atan2(delta_y, delta_x);

        return Math.toDegrees(radians);
    }

Затем, когда вы рисуете (), вы можете просто вращать холст в соответствии с результатами. Очевидно, вы захотите использовать if (MotionEvent.getAction() == MotionEvent.ACTION_MOVE) для обновления угла.

...