【问题标题】:Animate an arc around centre?围绕中心制作圆弧?
【发布时间】:2014-08-27 01:06:36
【问题描述】:

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

- (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 项目移植过来的,在这个项目中,它就像在 View 上的 draw 方法中添加逻辑一样简单,并且最好使用一些类似的东西。

【问题讨论】:

    标签: ios animation core-graphics


    【解决方案1】:

    几个观察:

    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"];
      

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

    【讨论】:

      【解决方案2】:

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

      如果你使用像UIView的+animateWithDuration:delay:options:animations:completion:这样的方法来做你的动画,你可以在options参数中指定UIViewAnimationOptionRepeat,让动画无限重复。

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

      注意+animateWithDuration:delay:options:animations:completion:是一个类方法,所以你应该把它发送给UIView本身(或UIView的某个子类),而不是UIView的实例。有一个例子here。此外,该特定方法可能是也可能不是您正在做的事情的最佳选择——我之所以叫它,是因为它是使用视图动画的一个简单示例,它允许您指定重复选项。

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

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

      【讨论】:

      • animateWithDuration 不走运,我一定做错了什么,但我不知道是什么...
      • 我尝试过使用CABasicAnimation,但是当我调用[arcView animate] 时,我仍然无法让圆弧旋转。 Any idea what I'm doing wrong?。我所做的一切似乎都没有任何效果。
      【解决方案3】:

      所以这就是我最终的结果。花了很长时间才找到我需要的 "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"];
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-10
        • 1970-01-01
        • 2017-10-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多