围绕中心设置弧形动画?

时间:2014-08-26 14:03:40

标签: ios animation core-graphics

我正在移植一些看起来有点像这样的动画代码:

- (void)drawRect:(CGRect)rect
{
    self.angle += 0.1;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);
    CGContextSetLineWidth(context, 2);
    CGContextSetLineCap(context, kCGLineCapButt);
    CGContextAddArc(context,
                 self.frame.size.height/2, self.frame.size.height/2, //center
                 self.frame.size.height/2 - 2, //radius
                 0.0 + self.angle, M_PI_4 + self.angle, //arc start/finish
                 NO);
    CGContextStrokePath(context);
}

问题是drawRect只在第一次绘制视图时被调用一次,因此弧的位置永远不会更新。

如何实现我想要的效果(弧线缓慢且连续地围绕中心点移动)?我能找到的大多数动画示例都是执行一次性动画(例如淡入淡出),但不是连续动画。

我也尝试了以下方面:

[arcView animateWithDuration:10.0f
         delay:1.0f
         options: UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState
         animations: ^(void){
             _arcView.transform = CGAffineTransformMakeRotation(self.angle++);
         }
         completion:NULL];

显示视图时,这似乎也没有做任何事情。

更多关于我的目标:我有一个我想要能够设置某些状态的视图,例如arcView.state = STATE_READY,并为此改变它的动画方式。这是从一个Android项目移植而来的,就像在视图上为draw方法添加逻辑一样简单,并且首选类似的东西。

3 个答案:

答案 0 :(得分:2)

有几点意见:

  1. 首先,drawRect可能不应该增加角度。 drawRect的目的是绘制一个帧,它不应该改变视图的状态。

  2. 如果您希望UIView子类重复更新angle并重绘自己,您可以设置一个定时器,或者更好的是CADisplayLink,它将被调用定期更新angle,然后调用[self setNeedsDisplay]更新视图:

    #import "MyView.h"
    
    @interface MyView ()
    
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic) CFTimeInterval startTime;
    
    @property (nonatomic) CGFloat revolutionsPerSecond;
    @property (nonatomic) CGFloat angle;
    
    @end
    
    @implementation MyView
    
    - (instancetype)initWithCoder:(NSCoder *)coder
    {
        self = [super initWithCoder:coder];
        if (self) {
            self.angle = 0.0;
            self.revolutionsPerSecond = 0.25; // i.e. go around once per every 4 seconds
            [self startDisplayLink];
        }
        return self;
    }
    
    - (void)startDisplayLink
    {
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
        self.startTime = CACurrentMediaTime();
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    - (void)stopDisplayLink
    {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    
    - (void)handleDisplayLink:(CADisplayLink *)displayLink
    {
        CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
    
        double revolutions;
        double percent = modf(elapsed * self.revolutionsPerSecond, &revolutions);
        self.angle = M_PI * 2.0 * percent;
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect
    {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);
        CGContextSetLineWidth(context, 2);
        CGContextSetLineCap(context, kCGLineCapButt);
        CGContextAddArc(context,
                        self.frame.size.width/2, self.frame.size.height/2, //center
                        MIN(self.frame.size.width, self.frame.size.height) / 2.0 - 2.0, //radius
                        0.0 + self.angle, M_PI_4 + self.angle, //arc start/finish
                        NO);
        CGContextStrokePath(context);
    }
    
    @end
    
  3. 更简单的方法是更新视图的transform属性,以便旋转它。在iOS 7及更高版本中,您使用弧添加视图,然后使用以下内容旋转它:

    [UIView animateKeyframesWithDuration:4.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat | UIViewAnimationOptionCurveLinear animations:^{
        [UIView addKeyframeWithRelativeStartTime:0.00 relativeDuration:0.25 animations:^{
            self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
        }];
        [UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.25 animations:^{
            self.view.transform = CGAffineTransformMakeRotation(M_PI);
        }];
        [UIView addKeyframeWithRelativeStartTime:0.50 relativeDuration:0.25 animations:^{
            self.view.transform = CGAffineTransformMakeRotation(M_PI_2 * 3.0);
        }];
        [UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
            self.view.transform = CGAffineTransformMakeRotation(M_PI * 2.0);
        }];
    } completion:nil];
    

    注意,我已将动画分为四个步骤,因为如果从0旋转到M_PI * 2.0,它将不会设置动画,因为它知道结束点与起点相同。因此,通过将其分解为四个步骤,它将连续执行四个动画中的每一个。如果在iOS的早期版本中进行操作,则执行与animateWithDuration类似的操作,但要重复此操作,则需要完成块来调用另一个旋转。类似于:https://stackoverflow.com/a/19408039/1271826

  4. 最后,如果您想要动画,例如,仅仅为弧的末端(即为弧的绘制设置动画),您可以将CABasicAnimationCAShapeLayer一起使用:

    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0) radius:MIN(self.view.bounds.size.width, self.view.bounds.size.height) / 2.0 - 2.0 startAngle:0 endAngle:M_PI_4 / 2.0 clockwise:YES];
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.frame = self.view.bounds;
    layer.path = path.CGPath;
    layer.lineWidth = 2.0;
    layer.strokeColor = [UIColor redColor].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;
    
    [self.view.layer addSublayer:layer];
    
    CABasicAnimation *animateStrokeEnd = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    animateStrokeEnd.duration  = 10.0;
    animateStrokeEnd.fromValue = @0.0;
    animateStrokeEnd.toValue   = @1.0;
    animateStrokeEnd.repeatCount = HUGE_VALF;
    [layer addAnimation:animateStrokeEnd forKey:@"strokeEndAnimation"];
    

    我知道这可能不是你想要的,但我提到它只是为了你可以把它添加到你的动画“工具带”。

答案 1 :(得分:1)

  

我能找到的大多数动画示例都是执行一次性的   动画(如淡入),但不是连续的。

如果您使用UIView的+animateWithDuration:delay:options:animations:completion:之类的方法来制作动画,则可以在UIViewAnimationOptionRepeat参数中指定options以使动画无限重复。

从根本上说,对大多数动画使用-drawRect:是错误的方法。正如您所发现的那样,-drawRect:仅在图形系统真正需要重绘视图时调用,这是一个相对昂贵的过程。尽可能地,你应该使用Core Animation来制作你的动画,特别是对于这样的东西,其中视图本身不需要重绘,但它的像素需要以某种方式进行转换。

请注意,+animateWithDuration:delay:options:animations:completion:是一种类方法,因此您应将其发送到UIView本身(或UIView的某个子类),而不是UIView的实例。有一个例子here。此外,该特定方法可能或可能不是您正在做的最佳选择 - 我只是将其调出来,因为它是一个使用带有视图的动画的简单示例,它允许您指定重复选项。

我不确定你的动画有什么问题(除了上面描述的错误的接收器之外),但是我会避免使用++运算符来修改角度。角度以弧度指定,因此增加1可能不是您想要的。相反,尝试将π/ 2添加到当前旋转,如下所示:

_arcView.transform = CAAffineTransformRotate(_arcView.transform, M_PI_2);

答案 2 :(得分:-1)

所以这就是我最终的结果。我花了很长时间来完成我需要"translation.rotation"而不只是"rotation" ...

@implementation arcView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    self.state = 0;
    if (self) {
        self.layer.contents = (__bridge id)([[self generateArc:[UIColor redColor].CGColor]CGImage]);
    }
    return self;
}
-(UIImage*)generateArc:(CGColorRef)color{
    UIGraphicsBeginImageContext(self.frame.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetStrokeColorWithColor(context, color);
    CGContextSetLineWidth(context, 2);
    CGContextSetLineCap(context, kCGLineCapButt);
    CGContextAddArc(context,
                    self.frame.size.height/2, self.frame.size.height/2,
                    self.frame.size.height/2 - 2,0.0,M_PI_4,NO);
    CGContextStrokePath(context);
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result;
}
-(void)spin{
    [self.layer removeAnimationForKey:@"rotation"];
    CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    NSNumber *current =[[self.layer presentationLayer] valueForKeyPath:@"transform.rotation"];
    rotate.fromValue = current;
    rotate.toValue = [NSNumber numberWithFloat:current.floatValue + M_PI * (self.state*2 - 1)];
    rotate.duration = 3.0;
    rotate.repeatCount = INT_MAX;
    rotate.fillMode = kCAFillModeForwards;
    rotate.removedOnCompletion = NO;
    [self.layer addAnimation:rotate forKey:@"rotation"];
}