1

我想根据用户点击屏幕的速度创建一个在屏幕上上下移动的动画。我遇到的问题是我不知道如何创建无限循环,所以我正在触发一个会出现问题的计时器。这是我当前的代码。

-(void)setPosOfCider {
    CGFloat originalY = CGRectGetMinY(cider.frame);
    float oY = originalY;
    float posY = averageTapsPerSecond * 100;
    float dur = 0;
    dur = (oY - posY) / 100;
    [UIImageView animateWithDuration:dur animations:^(void) {
        cider.frame = CGRectMake(768, 1024 - posY, 768, 1024);
    }];
}

建议修复(不起作用):

   - (void)viewDidLoad
{
    [super viewDidLoad];


    // Do any additional setup after loading the view, typically from a nib.
    scroll.pagingEnabled = YES;
    scroll.scrollEnabled = YES;
    scroll.contentSize = CGSizeMake(768 * 3, 1024); // 3 pages wide.
    scroll.delegate = self;

    self.speedInPointsPerSecond = 200000;
    self.tapEvents = [NSMutableArray array];
}

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self startDisplayLink];
}

-(IBAction)tapped {
    [self.tapEvents addObject:[NSDate date]];

    // if less than two taps, no average speed

    if ([self.tapEvents count] < 1)
        return;

    // only average the last three taps

    if ([self.tapEvents count] > 2)
        [self.tapEvents removeObjectAtIndex:0];

    // now calculate the average taps per second of the last three taps

    NSDate *start = self.tapEvents[0];
    NSDate *end   = [self.tapEvents lastObject];

    self.averageTapsPerSecond = [self.tapEvents count] / [end timeIntervalSinceDate:start];
}

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    self.lastTime = CACurrentMediaTime();
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (CGFloat)yAxisValueBasedUponTapsPerSecond
{
    CGFloat y = 1024 - (self.averageTapsPerSecond * 100.0);

    return y;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    CFTimeInterval now = CACurrentMediaTime();
    CGFloat elapsed = now - self.lastTime;
    self.lastTime = now;
    if (elapsed <= 0) return;

    CGPoint center = self.cider.center;
    CGFloat destinationY = [self yAxisValueBasedUponTapsPerSecond];

    if (center.y == destinationY)
    {
        // we don't need to move it at all

        return;
    }
    else if (center.y > destinationY)
    {
        // we need to move it up

        center.y -= self.speedInPointsPerSecond * elapsed;
        if (center.y < destinationY)
            center.y = destinationY;
    }
    else
    {
        // we need to move it down

        center.y += self.speedInPointsPerSecond * elapsed;
        if (center.y > destinationY)
            center.y = destinationY;
    }

    self.cider.center = center;
}
4

2 回答 2

2

更简单的方法是UIViewAnimationOptionBeginFromCurrentState在使用基于块的动画时使用该选项:

[UIView animateWithDuration:2.0
                      delay:0.0
                    options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     // set the new frame
                 }
                 completion:NULL];

这会停止当前基于块的动画并从当前位置开始新的动画。请注意,有效的 iOS 8 及更高版本,现在这是默认行为,通常完全不需要UIViewAnimationOptionBeginFromCurrentState。事实上,默认行为不仅考虑了相关视图的位置,还考虑了当前速度,确保了平滑过渡。有关更多信息,请参阅 WWDC 2014 视频构建可中断和响应式交互。)


另一种方法是让您的点击(调整averageTapsPerSecond)停止任何现有动画,frame从表示层获取视图的当前(反映视图中间动画的状态),然后开始一个新动画:

// grab the frame from the presentation layer (which is the frame mid-animation)

CALayer *presentationLayer = self.viewToAnimate.layer.presentationLayer;
CGRect frame = presentationLayer.frame;

// stop the animation

[self.viewToAnimate.layer removeAllAnimations];

// set the frame to be location we got from the presentation layer

self.viewToAnimate.frame = frame;

// now, starting from that location, animate to the new frame

[UIView animateWithDuration:3.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
    self.viewToAnimate.frame = ...; // set the frame according to the averageTapsPerSecond
} completion:nil];

在 iOS 7 中,您将使用animateWithDuration带有initialSpringVelocity:参数的渲染,以确保从对象的当前移动到新动画的平滑过渡。有关示例,请参见上述WWDC 视频


原答案:

要根据每秒的点击次数调整视图,您可以使用 a CADisplayLink,它类似于 a NSTimer,但非常适合像这样的动画工作。底线,将您averageTapsPerSecond转换为y坐标,然后使用CADisplayLink动画将某些视图移动到该y坐标:

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

@interface ViewController ()

@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) CFTimeInterval lastTime;

@property (nonatomic) CGFloat speedInPointsPerSecond;

@property (nonatomic, strong) NSMutableArray *tapEvents;
@property (nonatomic) CGFloat averageTapsPerSecond;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.speedInPointsPerSecond = 200.0;
    self.tapEvents = [NSMutableArray array];

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    [self.view addGestureRecognizer:tap];
}

- (void)handleTap:(UITapGestureRecognizer *)gesture
{
    [self.tapEvents addObject:[NSDate date]];

    // if less than two taps, no average speed

    if ([self.tapEvents count] < 2)
        return;

    // only average the last three taps

    if ([self.tapEvents count] > 3)
        [self.tapEvents removeObjectAtIndex:0];

    // now calculate the average taps per second of the last three taps

    NSDate *start = self.tapEvents[0];
    NSDate *end   = [self.tapEvents lastObject];

    self.averageTapsPerSecond = [self.tapEvents count] / [end timeIntervalSinceDate:start];
}

- (CGFloat)yAxisValueBasedUponTapsPerSecond
{
    CGFloat y = 480 - self.averageTapsPerSecond * 100.0;

    return y;
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self startDisplayLink];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    [self stopDisplayLink];
}

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    self.lastTime = CACurrentMediaTime();
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    CFTimeInterval now = CACurrentMediaTime();
    CGFloat elapsed = now - self.lastTime;
    self.lastTime = now;
    if (elapsed <= 0) return;

    CGPoint center = self.viewToAnimate.center;
    CGFloat destinationY = [self yAxisValueBasedUponTapsPerSecond];

    if (center.y == destinationY)
    {
        // we don't need to move it at all

        return;
    }
    else if (center.y > destinationY)
    {
        // we need to move it up

        center.y -= self.speedInPointsPerSecond * elapsed;
        if (center.y < destinationY)
            center.y = destinationY;
    }
    else
    {
        // we need to move it down

        center.y += self.speedInPointsPerSecond * elapsed;
        if (center.y > destinationY)
            center.y = destinationY;
    }

    self.viewToAnimate.center = center;
}

@end

希望这能说明这个想法。

此外,要使用上述内容,请确保您已将 QuartzCore 框架添加到您的项目中(尽管在 Xcode 5 中,它似乎为您做到了)。


对于标准的等速动画,您可以使用以下内容:

您通常会使用重复(即UIViewAnimationOptionRepeat)和自动反转(即UIViewAnimationOptionAutoreverse)的动画来执行此操作:

[UIView animateWithDuration:1.0
                      delay:0.0
                    options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
                 animations:^{
                     cider.frame = ... // specify the end destination here
                 }
                 completion:nil];

对于一个简单的上下动画,这就是你所需要的。不需要计时器。

于 2013-09-23T18:43:40.130 回答
0

您需要在 Rob 的答案中添加一行。

[UIView animateWithDuration:dur delay:del options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
    [UIView setAnimationRepeatCount:HUGE_VAL];

    //Your code here

} completion:nil];

如果您需要停止该动画,请使用此行(记得添加 QuartzCore 框架):

[view.layer removeAllAnimations];
于 2013-09-23T19:42:29.897 回答