iPhone上的双指旋转手势?

时间:2009-08-19 22:46:49

标签: iphone objective-c gesture-recognition

我正在开发一款iPhone应用,你可以做很多不同的手势输入。目前有单指选择/拖动,两个手指滚动和两个手指捏缩放/缩小。我想添加两个手指旋转(你的手指旋转它们之间的一个点),但我无法弄清楚如何让它正常工作。所有其他手势都是线性的,因此它们只是使用点或交叉产品的问题。

我想我必须在每个手指的前两个点之间存储斜率,如果矢量之间的角度接近90°,那么就有可能旋转。如果下一个手指移动角度也接近90°,并且一个手指上的矢量方向发生了正变化并且发生了负面变化,那么你就有了一个旋转。问题是,我需要在这个手势和其他手势之间做一个非常清晰的区分 - 而且上面的内容还不够远。

有什么建议吗?

编辑:这是我用矢量分析方式做的方式(与下面关于匹配像素的建议相反,请注意我在这里使用我的Vector结构,你应该能够猜出每个函数的作用):

//First, find the vector formed by the first touch's previous and current positions.
struct Vector2f firstChange = getSubtractedVector([theseTouches get:0], [lastTouches get:0]);
//We're going to store whether or not we should scroll.
BOOL scroll = NO;

//If there was only one touch, then we'll scroll no matter what.
if ([theseTouches count] <= 1)
{
    scroll = YES;
}
//Otherwise, we might scroll, scale, or rotate.
else
{
    //In the case of multiple touches, we need to test the slope between the two touches.
    //If they're going in roughly the same direction, we should scroll. If not, zoom.
    struct Vector2f secondChange = getSubtractedVector([theseTouches get:1], [lastTouches get:1]);

    //Get the dot product of the two change vectors.
    float dotChanges = getDotProduct(&firstChange, &secondChange);

    //Get the 2D cross product of the two normalized change vectors.
    struct Vector2f normalFirst = getNormalizedVector(&firstChange);
    struct Vector2f normalSecond = getNormalizedVector(&secondChange);
    float crossChanges = getCrossProduct(&normalFirst, &normalSecond);

    //If the two vectors have a cross product that is less than cosf(30), then we know the angle between them is 30 degrees or less.
    if (fabsf(crossChanges) <= SCROLL_MAX_CROSS && dotChanges > 0)
    {
        scroll = YES;
    }
    //Otherwise, they're in different directions so we should zoom or rotate.
    else
    {
        //Store the vectors represented by the two sets of touches.
        struct Vector2f previousDifference = getSubtractedVector([lastTouches  get:1], [lastTouches  get:0]);
        struct Vector2f currentDifference  = getSubtractedVector([theseTouches get:1], [theseTouches get:0]);

        //Also find the normals of the two vectors.
        struct Vector2f previousNormal = getNormalizedVector(&previousDifference);
        struct Vector2f currentNormal  = getNormalizedVector(&currentDifference );

        //Find the distance between the two previous points and the two current points.
        float previousDistance = getMagnitudeOfVector(&previousDifference);
        float currentDistance  = getMagnitudeOfVector(&currentDifference );

        //Find the angles between the two previous points and the two current points.
        float angleBetween = atan2(previousNormal.y,previousNormal.x) - atan2(currentNormal.y,currentNormal.x);

        //If we had a short change in distance and the angle between touches is a big one, rotate.
        if ( fabsf(previousDistance - currentDistance) <= ROTATE_MIN_DISTANCE && fabsf(angleBetween) >= ROTATE_MAX_ANGLE)
        {
            if (angleBetween > 0)
            {
                printf("Rotate right.\n");
            }
            else
            {
                printf("Rotate left.\n");
            }
        }
        else
        {
            //Get the dot product of the differences of the two points and the two vectors.
            struct Vector2f differenceChange = getSubtracted(&secondChange, &firstChange);
            float dotDifference = getDot(&previousDifference, &differenceChange);
            if (dotDifference > 0)
            {
                printf("Zoom in.\n");
            }
            else
            {
                printf("Zoom out.\n");
            }
        }
    }
}

if (scroll)
{
    prinf("Scroll.\n");
}

您应该注意,如果您只是进行图像处理或直接旋转/缩放,那么上述方法应该没问题。但是,如果你像我一样并且你正在使用手势来导致需要花费时间来加载的东西,那么在你连续几次激活该手势之前,你很可能会想要避免这样做。每个与我的代码之间的差异仍然不是完全分开的,所以偶尔在一堆缩放中你会得到一个旋转,反之亦然。

2 个答案:

答案 0 :(得分:3)

我之前通过找到两个手指之间的先前和当前距离以及前一行和当前行之间的角度来做到这一点。 然后我为那个距离三角洲和角度θ选择了一些经验阈值,这对我来说非常好。

如果距离大于我的阈值,并且角度小于我的阈值,我缩放图像。否则我旋转它。 2手指滚动似乎很容易区分。

BTW如果您实际存储了值,则触摸已经存储了先前的点值。

CGPoint previousPoint1 = [self scalePoint:[touch1 previousLocationInView:nil]];
CGPoint previousPoint2 = [self scalePoint:[touch2 previousLocationInView:nil]];
CGPoint currentPoint1 = [self scalePoint:[touch1 locationInView:nil]];
CGPoint currentPoint2 = [self scalePoint:[touch2 locationInView:nil]];

答案 1 :(得分:2)

两根手指,都是移动的,相反的(ish)方向。什么姿势与此冲突?

捏合/缩放我猜是接近,但是捏合/缩放将开始远离中心点(如果从每条线向后追踪,你的线将平行并关闭),旋转最初会有平行线(向后追踪)将远离彼此,这些线将不断改变斜率(同时保持距离)。

编辑:你知道 - 这两个都可以用相同的算法解决。

不是计算线,而是计算每个手指下的像素。如果手指移动,则翻译图像,使两个初始像素仍在两个手指下。

这解决了所有双指动作,包括滚动。

双指滚动或缩放有时可能看起来有些不稳定,因为它也会执行其他操作,但这是地图应用程序似乎工作的方式(不包括它没有的旋转)。