基于UIPanGestureRecognizer速度的UIView动画

43
我想要像在iPhone内置的Photos应用程序中浏览图像一样,能够移动一个子视图到屏幕上并从屏幕上移开,所以如果当我放开手指时,子视图超过了屏幕的一半,则必须动画移出屏幕,但也必须支持滑动操作,因此如果滑动/平移速度足够快,即使它可能少于屏幕的一半,它也必须动画移出屏幕。
我的想法是使用UIPanGestureRecognizer,然后根据速度进行测试。这个方法行得通,但是如何根据视图当前位置和平移速度设置UIView移动的正确动画持续时间,以便看起来更加平滑?如果我设置一个固定值,与我的手指滑动速度相比,动画要么开始变慢,要么开始变快。
3个回答

60

文档说明:

平移手势的速度,以每秒点数表示。速度分为水平和垂直两个方向。

所以我认为,如果你想让你的视图在屏幕外移动xPoints(以pt为单位),你可以按以下方式计算该运动的持续时间:

CGFloat xPoints = 320.0;
CGFloat velocityX = [panRecognizer velocityInView:aView].x;
NSTimeInterval duration = xPoints / velocityX;
CGPoint offScreenCenter = moveView.center;
offScreenCenter.x += xPoints;
[UIView animateWithDuration:duration animations:^{
    moveView.center = offScreenCenter;
}];

你可能想要使用 + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion ,并尝试不同的 UIViewAnimationOptions


24

关于在 UIPanGestureRecognizer 中依赖速度的观察:我不知道你的经验如何,但我发现模拟器生成的速度并不是非常有用的。(在设备上还好,但在模拟器上有问题。)

如果您快速地移动手指并突然停下来,等待一会儿,然后才结束手势(例如,用户开始滑动,意识到这并不是他们想要的,所以他们停下来然后松开手指),velocityInView: 在手指离开屏幕时报告的速度似乎是在我停下来等待之前的快速速度,而在这个例子中正确的速度应该是零(或接近于零)。简而言之,报告的速度是在滑动结束前的速度,而不是滑动结束时的速度。

我最终自己手动计算了速度。(这似乎很愚蠢,但如果我真的想得到滑动的最终速度,我没有看到任何绕过它的方法。)总之,在状态为 UIGestureRecognizerStateChanged 时,我跟踪当前和先前的 translationInView CGPoint,以及时间,并在 UIGestureRecognizerStateEnded 状态下使用这些值来计算实际的最终速度。这样做效果很不错。

这里是我用于计算速度的代码。我碰巧没有使用速度来确定动画的速度,而是使用它来确定用户是否将视图拖动足够远或者快速地滑动,以使视图移动超过屏幕的一半,并触发视图之间的动画,但是计算最终速度的概念似乎也适用于这个问题。以下是代码:

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
    static CGPoint lastTranslate;   // the last value
    static CGPoint prevTranslate;   // the value before that one
    static NSTimeInterval lastTime;
    static NSTimeInterval prevTime;

    CGPoint translate = [gesture translationInView:self.view];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;
        prevTime = lastTime;
        prevTranslate = lastTranslate;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        prevTime = lastTime;
        prevTranslate = lastTranslate;
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;

        [self moveSubviewsBy:translate];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        CGPoint swipeVelocity = CGPointZero;

        NSTimeInterval seconds = [NSDate timeIntervalSinceReferenceDate] - prevTime;
        if (seconds)
        {
            swipeVelocity = CGPointMake((translate.x - prevTranslate.x) / seconds, (translate.y - prevTranslate.y) / seconds);
        }

        float inertiaSeconds = 1.0;  // let's calculate where that flick would take us this far in the future
        CGPoint final = CGPointMake(translate.x + swipeVelocity.x * inertiaSeconds, translate.y + swipeVelocity.y * inertiaSeconds);

        [self animateSubviewsUsing:final];
    }
}

它在iOS 6中无法工作,因为在UIGestureRecognizerStateEnded中,最终翻译和上次翻译是相同的,所以最终速度始终为0。 - an0
1
谢谢,我明白了。这是模拟器的问题。我向苹果提交了一个错误报告。 - an0
我已经修改了源代码(更改了一些变量名),希望避免这种混淆。这段代码本来就可以正常工作,但是新的变量名希望能使它更加清晰易懂。感谢您向苹果报告此错误。 - Rob
在UIGestureRecognizerStateEnded状态下,“translate”始终为(0,0)。要计算swipeVelocity,您可能想要减去(lastTranslate - prevTranslate),而不是转换。 - Simon
1
只是想指出,使用 static 变量将使值在应用程序生命周期内持久存在,甚至在另一个对象中也是如此。在使用它们之前,请仔细考虑。 - DCMaxxx
很棒的答案。如果能用Swift实现就太好了 :) - Edward

3
在大多数情况下,为UIView动画器设置UIViewAnimationOptionBeginFromCurrentState选项就足以使动画无缝继续进行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接