UIPanGestureRecognizer 状态结束后动画中的小问题

Posted

技术标签:

【中文标题】UIPanGestureRecognizer 状态结束后动画中的小问题【英文标题】:Little hiccup in the animation after UIPanGestureRecognizer state is end 【发布时间】:2014-10-29 05:41:48 【问题描述】:

我正在尝试实现视图的拖动,当拖动停止时,视图滑到顶部并消失。我可以使用 UIPanGestureRecognizer 很好地拖动视图。但在那之后,我用动画让它滑出。问题是在拖动之后,在视图移动之前总是会有一点小问题......我四处搜索,无法弄清楚如何解决这个问题。代码如下:

- (void)moveView2:(UIPanGestureRecognizer *)pan     
    CGPoint delta = [pan translationInView:self.view];
    CGPoint newCenter = CGPointMake(_view2.center.x, _view2.center.y + delta.y);
    _view2.center = newCenter;
    [pan setTranslation:CGPointZero inView:self.view];
    if (pan.state == UIGestureRecognizerStateEnded) 
        CGPoint velocity = [pan velocityInView:self.view];
        NSLog(@"Velocity is %f, %f", velocity.x, velocity.y);
        // Here is the delay
        [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.5f   initialSpringVelocity:500 options:UIViewAnimationOptionCurveEaseInOut animations:^
        _view2.center = CGPointMake(_view2.center.x, -600);
     completion:nil];






#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *fbButton;

@property (weak, nonatomic) IBOutlet UIButton *twitterButton;

@property UIView *view1;
@property UIView *view2;

@property CGPoint startPoint;
@property CGPoint endPoint;
@property CGPoint originCenter;

@property double startTime;
@property double endTime;

@end

@implementation ViewController

- (void)viewDidLoad 
    [super viewDidLoad];
    CGRect frame = [UIScreen mainScreen].bounds;
    _view1 = [[UIView alloc] initWithFrame:frame];
    _view1.backgroundColor = [UIColor redColor];
    [self.view addSubview:_view1];
    _view2 = [[UIView alloc] initWithFrame:frame];
    _view2.backgroundColor = [UIColor greenColor];
    [self.view addSubview:_view2];
    UIPanGestureRecognizer *pan1 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveView2:)];
    [_view2 addGestureRecognizer:pan1];
    /*
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeUpDown:)];
    [swipe setDirection:UISwipeGestureRecognizerDirectionUp];
    [_view2 addGestureRecognizer:swipe];
     */


/*
- (void)swipeUpDown:(UISwipeGestureRecognizer *)swipe 
    if (swipe.direction == UISwipeGestureRecognizerDirectionUp) 
        NSLog(@"Swipe up");
        [UIView animateWithDuration:0.5 animations:^
            _view2.center = CGPointMake(_view2.center.x, -600);
        ];
    
*/

- (void)moveView2:(UIPanGestureRecognizer *)pan 
    CGPoint bigViewDelta = [pan translationInView:self.view];
    CGPoint newCenter = CGPointMake(_view2.center.x, _view2.center.y + bigViewDelta.y);
    _view2.center = newCenter;
    [pan setTranslation:CGPointZero inView:self.view];
    if (pan.state == UIGestureRecognizerStateEnded) 
        CGPoint velocityOfPan = [pan velocityInView:self.view];
        CGFloat velocityOfPanAbsolute = sqrt(velocityOfPan.x * velocityOfPan.x + velocityOfPan.y * velocityOfPan.y);
        // get simple points per second
        CGPoint currentPoint = _view2.center;
        CGPoint finalPoint = CGPointMake(_view2.center.x, -600);
        CGFloat distance = sqrt((finalPoint.x - currentPoint.x) * (finalPoint.x - currentPoint.x) + (finalPoint.y - currentPoint.y) * (finalPoint.y - currentPoint.y));
        // how far to travel
        CGFloat duration = 0.5;
        CGFloat animationVelocity = velocityOfPanAbsolute / (distance / duration);

        [UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:animationVelocity options:0 animations:^
            _view2.center = CGPointMake(_view2.center.x, -600);
         completion:nil];    


- (void)viewDidAppear:(BOOL)animated 
/*
    [UIView transitionFromView:_view2 toView:_view1 duration:2 options:UIViewAnimationOptionCurveEaseInOut completion:^(BOOL finished) 
    ];
*/

    /*
    // get the view that's currently showing
    UIView *currentView = _view2;
    // get the the underlying UIWindow, or the view containing the current view view
    UIView *theWindow = [currentView superview];
    // remove the current view and replace with myView1
    [currentView removeFromSuperview];
    //[theWindow addSubview:newView];
    // set up an animation for the transition between the views
    CATransition *animation = [CATransition animation];
    [animation setDuration:0.5];
    [animation setType:kCATransitionPush];
    [animation setSubtype:kCATransitionFromLeft];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [[theWindow layer] addAnimation:animation forKey:@"SwitchToView1"];
     */


- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.


@end

【问题讨论】:

【参考方案1】:

你的意思是动画发生前的延迟吗?嗯,我没有看到任何会导致这种情况的东西,但是 animateWithDuration 的参数非常非常奇怪。

首先,UIViewAnimationOptionCurveEaseInOut 对我来说没有意义,因为这通常表示“慢慢开始,加快速度,然后慢慢停止”。这对于一个标准动画来说很好,您不希望动画的开始过于刺耳。但这在这种情况下没有意义,因为您可能希望它不要缓慢启动,而是使用您最初提供的任何速度。如果我使用任何option,我会使用UIViewAnimationOptionCurveEaseOut。即使这样也没有意义,因为是 usingSpringWithDamping 参数决定了动画结束时会发生什么。 (而且,最重要的是,您将其拖出屏幕,所以我不清楚您为什么关心退出曲线。)

其次,initialSpringVelocity 在我看来也不对。正如这个参数的文档所说:

初始弹簧速度。为了让动画顺利开始,请将此值与附加之前的视图速度相匹配。

1 的值对应于一秒内遍历的总动画距离。例如,如果总动画距离为 200 点,并且您希望动画的开始与 100 pt/s 的视图速度相匹配,则使用值 0.5

所以,500 表示您想在一秒钟内从当前位置到最终位置的距离旅行 500 倍。这速度快得离谱。

我会尝试将初始速度与手势的最终速度相匹配。因此,计算手势的绝对速度,然后将其除以要经过的距离除以动画的持续时间。

第三,如果您将其拖出屏幕,我不确定您为什么要使用 usingSpringWithDamping0.5。那是有弹性的。为什么你要让它在你正在使用的所有东西中弹出,因为它会停止在屏幕上并且你无论如何都看不到它?令我震惊的是,任何小于1.0 的值都有可能在一个或多个初始弹簧期间视图弹回可见屏幕的可能性。如果在屏幕外设置动画,我不会在这里使用小于 1.0 的任何东西。

综合起来,我得到了这样的结果:

CGPoint velocityOfPan = [pan velocityInView:self.view];
CGFloat velocityOfPanAbsolute = hypof(velocityOfPan.x, velocityOfPan.y);                // get simple points per second
CGPoint currentPoint = _view2.center.x;
CGPoint finalPoint = CGPointMake(_view2.center.x, -600);
CGFloat distance = hypof(finalPoint.x - currentPoint.x, finalPoint.y - currentPoint.y); // how far to travel
CGFloat duration = 0.5;
CGFloat animationVelocity = velocityOfPanAbsolute / (distance / duration);

[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:animationVelocity options:0 animations:^
    _view2.center = CGPointMake(_view2.center.x, -600);
 completion:nil];

说了这么多,虽然animateWithDuration 可能会被“缓入缓出”和戏剧性的initialSpringVelocity 组合弄糊涂,但我实际上不明白为什么会导致“打嗝” .

在这个动画之后你是否在做任何事情?有什么可能会阻塞主队列?


你的意思是手势被首次识别之前的小延迟?是的,这是平移手势识别器的一个功能。 (我认为它试图确定它是否真的是平底锅 vs 长按 vs 等)。

您可以通过使用minimumPressDuration 为零的UILongPressGestureRecognizee 来解决此问题(尽管您必须自己计算translationInView)。

【讨论】:

感谢您的回复。我的意思是 [UIView 动画] 延迟。当手势结束时,视图有一些速度。如何继续保持这种速度并滑出屏幕。目前,由于动画开始,有一些延迟。 @user1165560 好的。我仔细查看了您的动画调用,这看起来非常可疑。请参阅我修改后的答案。但是在动画开始之前我没有看到任何会导致延迟的东西,所以我怀疑问题出在其他地方(尽管无论如何你都应该修复animateWithDuration)。是否有任何东西可以阻止主队列(从而阻止动画开始)?此外,如果您从后台队列启动 UI 更改,您也会遇到令人费解的延迟。但我在这里没有看到任何会导致你描述的“打嗝”的东西。 感谢您的细心解释。我修改了代码。但我仍然看到延迟。我认为没有任何东西阻塞主队列。我只有_view2。问题是手势结束后不会立即进行向上滑动操作。手势结束后,_view2 会停止约 0.5 秒,然后向上滑动。我希望它在手势结束后立即向上滑动。我粘贴了所有代码。如果你愿意,你可以试试。我注释掉了滑动手势。基本上,我想要滑动的效果,除了用户可以先拖动,然后我会决定视图是否会出来。 我在我的机器上尝试了你的代码,手势完成后动画立即开始。听起来好像还有其他事情发生。你试过在设备上运行,根本没有连接到 Xcode?​​span> 我一直在模拟器上运行...也许这就是原因...我会在设备上尝试。

以上是关于UIPanGestureRecognizer 状态结束后动画中的小问题的主要内容,如果未能解决你的问题,请参考以下文章

UIPanGestureRecognizer 状态结束后动画中的小问题

UIPanGestureRecognizer中的Velocity和translation开始状态

在 UIButtons 上禁用 UIPanGestureRecognizer

UISegmentedControl 和 UIPanGestureRecognizer?

UIPanGestureRecognizer 的准确起始位置?

UIPanGestureRecognizer 碰撞