2011-11-19 6 views
7

CAShapeLayerを使用して簡単でアニメーション化された円グラフを作成しようとしています。 0から指定されたパーセンテージにアニメーション化したい。私が使用してアニメーション化後アニメーションCAShapeLayer円

CGMutablePathRef piePath = CGPathCreateMutable(); 
CGPathMoveToPoint(piePath, NULL, self.frame.size.width/2, self.frame.size.height/2); 
CGPathAddLineToPoint(piePath, NULL, self.frame.size.width/2, 0); 
CGPathAddArc(piePath, NULL, self.frame.size.width/2, self.frame.size.height/2, radius, DEGREES_TO_RADIANS(-90), DEGREES_TO_RADIANS(-90), 0);   
CGPathAddLineToPoint(piePath, NULL, self.frame.size.width/2 + radius * cos(DEGREES_TO_RADIANS(-90)), self.frame.size.height/2 + radius * sin(DEGREES_TO_RADIANS(-90))); 

pie = [CAShapeLayer layer]; 
pie.fillColor = [UIColor redColor].CGColor; 
pie.path = piePath; 

[self.layer addSublayer:pie]; 

::私が使用してシェイプレイヤーを作成するには

明らか

CGMutablePathRef newPiePath = CGPathCreateMutable(); 
CGPathAddLineToPoint(newPiePath, NULL, self.frame.size.width/2, 0);  
CGPathMoveToPoint(newPiePath, NULL, self.frame.size.width/2, self.frame.size.height/2); 
CGPathAddArc(newPiePath, NULL, self.frame.size.width/2, self.frame.size.height/2, radius, DEGREES_TO_RADIANS(-90), DEGREES_TO_RADIANS(125), 0);     
CGPathAddLineToPoint(newPiePath, NULL, self.frame.size.width/2 + radius * cos(DEGREES_TO_RADIANS(125)), self.frame.size.height/2 + radius * sin(DEGREES_TO_RADIANS(125)));  

CABasicAnimation *pieAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
pieAnimation.duration = 1.0; 
pieAnimation.removedOnCompletion = NO; 
pieAnimation.fillMode = kCAFillModeForwards; 
pieAnimation.fromValue = pie.path; 
pieAnimation.toValue = newPiePath; 

[pie addAnimation:pieAnimation forKey:@"animatePath"]; 

、これは本当に奇妙な方法でアニメーション化されます。種類はちょうど最終状態に成長します。このアニメーションを円の方向に沿って行う簡単な方法はありますか?または、それはCAShapeLayerアニメーションの制限ですか?私はあなたの代わりにキーフレームアニメーションを作る示唆

+0

ないあなたの質問への答えが、あなたは、このに興味があるかもしれない:http://code.google.com/p/core-plot/ –

答えて

32

私はこの質問が長い間答えられていることを知っていますが、これはCAShapeLayerCAKeyframeAnimationの場合には良いとは思いません。 Core Animationは、私たちのためにアニメーショントゥイーンを行う能力を持っています。ここでは、私が効果を上手く達成するために使用するクラス(あなたが好きな場合は、UIViewのラップを付けています)です。

レイヤーサブクラスは、progressプロパティの暗黙のアニメーションを有効にしますが、ビュークラスは、そのセッターをアニメーションメソッドUIViewでラップします。 UIViewAnimationOptionBeginFromCurrentStateで0.0の長さのアニメーションを使用することによる面白い(そして最終的に有用な)副作用は、各アニメーションが前のアニメーションをキャンセルして、this(アニメーション)とthis(アニメーション化されていない、増加した)。

DZRoundProgressView.h

@interface DZRoundProgressLayer : CALayer 

@property (nonatomic) CGFloat progress; 

@end 

@interface DZRoundProgressView : UIView 

@property (nonatomic) CGFloat progress; 

@end 

DZRoundProgressView.m

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

@implementation DZRoundProgressLayer 

// Using Core Animation's generated properties allows 
// it to do tweening for us. 
@dynamic progress; 

// This is the core of what does animation for us. It 
// tells CoreAnimation that it needs to redisplay on 
// each new value of progress, including tweened ones. 
+ (BOOL)needsDisplayForKey:(NSString *)key { 
    return [key isEqualToString:@"progress"] || [super needsDisplayForKey:key]; 
} 

// This is the other crucial half to tweening. 
// The animation we return is compatible with that 
// used by UIView, but it also enables implicit 
// filling-up-the-pie animations. 
- (id)actionForKey:(NSString *) aKey { 
    if ([aKey isEqualToString:@"progress"]) { 
     CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:aKey]; 
     animation.fromValue = [self.presentationLayer valueForKey:aKey]; 
     return animation; 
    } 
    return [super actionForKey:aKey]; 
} 

// This is the gold; the drawing of the pie itself. 
// In this code, it draws in a "HUD"-y style, using 
// the same color to fill as the border. 
- (void)drawInContext:(CGContextRef)context { 
    CGRect circleRect = CGRectInset(self.bounds, 1, 1); 

    CGColorRef borderColor = [[UIColor whiteColor] CGColor]; 
    CGColorRef backgroundColor = [[UIColor colorWithWhite: 1.0 alpha: 0.15] CGColor]; 

    CGContextSetFillColorWithColor(context, backgroundColor); 
    CGContextSetStrokeColorWithColor(context, borderColor); 
    CGContextSetLineWidth(context, 2.0f); 

    CGContextFillEllipseInRect(context, circleRect); 
    CGContextStrokeEllipseInRect(context, circleRect); 

    CGFloat radius = MIN(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect)); 
    CGPoint center = CGPointMake(radius, CGRectGetMidY(circleRect)); 
    CGFloat startAngle = -M_PI/2; 
    CGFloat endAngle = self.progress * 2 * M_PI + startAngle; 
    CGContextSetFillColorWithColor(context, borderColor); 
    CGContextMoveToPoint(context, center.x, center.y); 
    CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); 
    CGContextClosePath(context); 
    CGContextFillPath(context); 

    [super drawInContext:context]; 
} 

@end 

@implementation DZRoundProgressView 
+ (Class)layerClass { 
    return [DZRoundProgressLayer class]; 
} 

- (id)init { 
    return [self initWithFrame:CGRectMake(0.0f, 0.0f, 37.0f, 37.0f)]; 
} 

- (id)initWithFrame:(CGRect)frame { 
    if ((self = [super initWithFrame:frame])) { 
     self.opaque = NO; 
     self.layer.contentsScale = [[UIScreen mainScreen] scale]; 
     [self.layer setNeedsDisplay]; 
    } 
    return self; 
} 

- (void)setProgress:(CGFloat)progress { 
    [(id)self.layer setProgress:progress]; 
} 

- (CGFloat)progress { 
    return [(id)self.layer progress]; 
} 

@end 
+0

男、このコードは素晴らしいです。すごい仕事! – marcio

+0

アニメーションが機能しなくなっています。サンプルコードへのリンクを付けることができますか? (GIT hub..or something) – abhi

+0

申し訳ありません!私はコードを更新しました。将来のアップデートは[オープンソースのコードで提供されています](https://github.com/zwaldowski/DZProgressController/blob/master/DZProgressController.m)。 – zwaldowski

4

pie.bounds = CGRectMake(-0.5 * radius, 
         -0.5 * radius, 
         radius, 
         radius); 

NSMutableArray *values = [NSMutableArray array]; 
for (int i = 0; i < nrSteps + 1; i++) 
{ 
    UIBezierPath *path = [UIBezierPath bezierPath]; 
    [path moveToPoint:CGPointZero]; 
    [path addLineToPoint:CGPointMake(radius * cosf(startAngle), 
            radius * sinf(startAngle))]; 
    [path addArcWithCenter:... 
        endAngle:startAngle + i * (endAngle - startAngle)/nrSteps 
          ...]; 
    [path closePath]; 
    [values addObject:(__bridge id)path.CGPath]; 
} 

基本的なアニメーションはスカラー/ベクトルのために良いです。しかし、それはあなたのパスを補間したいですか?

+0

ました非常に役立つスニペット、ありがとう! –

+0

良いソリューション、ありがとう – Slavik

1

クイックコピー/ this excellent Objective-C answerのスウィフト翻訳を貼り付けます。

class ProgressView: UIView { 

    required init(coder aDecoder: NSCoder) { 
     super.init(coder: aDecoder) 
     genericInit() 
    } 

    private func genericInit() { 
     self.opaque = false; 
     self.layer.contentsScale = UIScreen.mainScreen().scale 
     self.layer.setNeedsDisplay() 
    } 

    var progress : CGFloat = 0 { 
     didSet { 
      (self.layer as! ProgressLayer).progress = progress 
     } 
    } 

    override class func layerClass() -> AnyClass { 
     return ProgressLayer.self 
    } 

    func updateWith(progress : CGFloat) { 
     self.progress = progress 
    } 
} 

class ProgressLayer: CALayer { 

    @NSManaged var progress : CGFloat 

    override class func needsDisplayForKey(key: String!) -> Bool{ 

     return key == "progress" || super.needsDisplayForKey(key); 
    } 

    override func actionForKey(event: String!) -> CAAction! { 

     if event == "progress" { 
      let animation = CABasicAnimation(keyPath: event) 
      animation.duration = 0.2 
      animation.fromValue = self.presentationLayer().valueForKey(event) 
      return animation 
     } 

     return super.actionForKey(event) 
    } 

    override func drawInContext(ctx: CGContext!) { 

     if progress != 0 { 

      let circleRect = CGRectInset(self.bounds, 1, 1) 
      let borderColor = UIColor.whiteColor().CGColor 
      let backgroundColor = UIColor.clearColor().CGColor 

      CGContextSetFillColorWithColor(ctx, backgroundColor) 
      CGContextSetStrokeColorWithColor(ctx, borderColor) 
      CGContextSetLineWidth(ctx, 2) 

      CGContextFillEllipseInRect(ctx, circleRect) 
      CGContextStrokeEllipseInRect(ctx, circleRect) 

      let radius = min(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect)) 
      let center = CGPointMake(radius, CGRectGetMidY(circleRect)) 
      let startAngle = CGFloat(-(M_PI/2)) 
      let endAngle = CGFloat(startAngle + 2 * CGFloat(M_PI * Double(progress))) 

      CGContextSetFillColorWithColor(ctx, borderColor) 
      CGContextMoveToPoint(ctx, center.x, center.y) 
      CGContextAddArc(ctx, center.x, center.y, radius, startAngle, endAngle, 0) 
      CGContextClosePath(ctx) 
      CGContextFillPath(ctx) 
     } 
    } 
} 
1
スウィフト3では

class ProgressView: UIView { 

    required init(coder aDecoder: NSCoder) { 
     super.init(coder: aDecoder)! 
     genericInit() 
    } 

    private func genericInit() { 
     self.isOpaque = false; 
     self.layer.contentsScale = UIScreen.main.scale 
     self.layer.setNeedsDisplay() 
    } 

    var progress : CGFloat = 0 { 
     didSet { 
      (self.layer as! ProgressLayer).progress = progress 
     } 
    } 

    override class var layerClass: AnyClass { 
     return ProgressLayer.self 
    } 

    func updateWith(progress : CGFloat) { 
     self.progress = progress 
    } 
} 

class ProgressLayer: CALayer { 

    @NSManaged var progress : CGFloat 

    override class func needsDisplay(forKey key: String) -> Bool{ 

     return key == "progress" || super.needsDisplay(forKey: key); 
    } 

    override func action(forKey event: String) -> CAAction? { 

     if event == "progress" { 

      let animation = CABasicAnimation(keyPath: event) 
      animation.duration = 0.2 
      animation.fromValue = self.presentation()?.value(forKey: event) ?? 0 
      return animation 

     } 

     return super.action(forKey: event) 
    } 

    override func draw(in ctx: CGContext) { 

     if progress != 0 { 

      let circleRect = self.bounds.insetBy(dx: 1, dy: 1) 
      let borderColor = UIColor.white.cgColor 
      let backgroundColor = UIColor.red.cgColor 

      ctx.setFillColor(backgroundColor) 
      ctx.setStrokeColor(borderColor) 
      ctx.setLineWidth(2) 

      ctx.fillEllipse(in: circleRect) 
      ctx.strokeEllipse(in: circleRect) 

      let radius = min(circleRect.midX, circleRect.midY) 
      let center = CGPoint(x: radius, y: circleRect.midY) 
      let startAngle = CGFloat(-(Double.pi/2)) 
      let endAngle = CGFloat(startAngle + 2 * CGFloat(Double.pi * Double(progress))) 

      ctx.setFillColor(borderColor) 
      ctx.move(to: CGPoint(x:center.x , y: center.y)) 
      ctx.addArc(center: CGPoint(x:center.x, y: center.y), radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) 
      ctx.closePath() 
      ctx.fillPath() 
     } 
    } 
} 
関連する問題