2015-02-10 62 views
6

我发现在两个状态之间动画UIImageView是棘手的:它的原始矩形框和用UIBezierPath创建的新形状。提到了许多不同的技术,其中大部分都不适合我。两个贝塞尔路径形状之间的动画

首先是使用UIView块动画不能工作的认识;显然不能在animateWithDuration:块中执行子图层动画。 (见herehere

剩下CAAnimation,具体的子类如CABasicAnimation。我很快就意识到,从不具有CAShapeLayer的视图(例如参见here)的视图无法生成动画。

而且他们不能只是任意两个形状图层路径,而是“动画形状图层的路径时,才能保证当你从喜欢动画喜欢的工作”(见here

随着在地方,来了更现实的问题,比如如何使用fromValuetoValue(他们应该是一个CAShapeLayerCGPath?)什么的动画添加到(在layer,或者mask?)等

似乎有这么多变数;哪种组合会给我我想要的动画?

回答

11

第一个重点是构造两个贝塞尔路径,所以矩形是一个(平凡)类似于更复杂的形状。

// the complex bezier path 
let initialPoint = CGPoint(x: 0, y: 0) 
let curveStart = CGPoint(x: 0, y: (rect.size.height) * (0.2)) 
let curveControl = CGPoint(x: (rect.size.width) * (0.6), y: (rect.size.height) * (0.5)) 
let curveEnd = CGPoint(x: 0, y: (rect.size.height) * (0.8)) 
let firstCorner = CGPoint(x: 0, y: rect.size.height) 
let secondCorner = CGPoint(x: rect.size.width, y: rect.size.height) 
let thirdCorner = CGPoint(x: rect.size.width, y: 0) 

var myBezierArc = UIBezierPath() 
myBezierArc.moveToPoint(initialPoint) 
myBezierArc.addLineToPoint(curveStart) 
myBezierArc.addQuadCurveToPoint(curveEnd, controlPoint: curveControl) 
myBezierArc.addLineToPoint(firstCorner) 
myBezierArc.addLineToPoint(secondCorner) 
myBezierArc.addLineToPoint(thirdCorner) 

比较简单的“小事”贝塞尔路径,即创建一个矩形,是完全一样的,但controlPoint被设定,它似乎不应该存在:

let curveControl = CGPoint(x: 0, y: (rect.size.height) * (0.5)) 

(尝试取出addQuadCurveToPoint线得到一个很奇怪的动画)

最后,动画命令:

let myAnimation = CABasicAnimation(keyPath: "path") 

if (isArcVisible == true) { 
    myAnimation.fromValue = myBezierArc.CGPath 
    myAnimation.toValue = myBezierTrivial.CGPath 
} else { 
    myAnimation.fromValue = myBezierTrivial.CGPath 
    myAnimation.toValue = myBezierArc.CGPath 
}  
myAnimation.duration = 0.4 
myAnimation.fillMode = kCAFillModeForwards 
myAnimation.removedOnCompletion = false 

myImageView.layer.mask.addAnimation(myAnimation, forKey: "animatePath") 

如果有人有兴趣,该项目是here

+0

感谢您的例子,以及您的问题中的所有链接。阅读围绕这个问题的讨论很有意思,也很有帮助。 – rdelmar 2015-02-10 17:12:57

+0

如何在动画命令中设置tovalue(myBezierTrivial.CGPath)? – ABJ 2018-01-01 06:08:20

3

我的灵感来自于您的示例,尝试使用您的答案中提及的技术和一些链接来圈选动画。我打算将其扩展为多边形动画的更一般的圆,但目前它只适用于正方形。我有一个名为RDPolyCircle(CAShapeLayer的子类)的类,它负担繁重。下面是它的代码,

@interface RDPolyCircle() 
@property (strong,nonatomic) UIBezierPath *polyPath; 
@property (strong,nonatomic) UIBezierPath *circlePath; 
@end 

@implementation RDPolyCircle { 
    double cpDelta; 
    double cosR; 
} 

-(instancetype)initWithFrame:(CGRect) frame numberOfSides:(NSInteger)sides isPointUp:(BOOL) isUp isInitiallyCircle:(BOOL) isCircle { 
    if (self = [super init]) { 
     self.frame = frame; 
     _isPointUp = isUp; 
     _isExpandedPolygon = !isCircle; 

     double radius = (frame.size.width/2.0); 
     cosR = sin(45 * M_PI/180.0) * radius; 
     double fractionAlongTangent = 4.0*(sqrt(2)-1)/3.0; 
     cpDelta = fractionAlongTangent * radius * sin(45 * M_PI/180.0); 

     _circlePath = [self createCirclePathForFrame:frame]; 
     _polyPath = [self createPolygonPathForFrame:frame numberOfSides:sides]; 
     self.path = (isCircle)? self.circlePath.CGPath : self.polyPath.CGPath; 
     self.fillColor = [UIColor clearColor].CGColor; 
     self.strokeColor = [UIColor blueColor].CGColor; 
     self.lineWidth = 6.0; 
    } 
    return self; 
} 


-(UIBezierPath *)createCirclePathForFrame:(CGRect) frame { 

    CGPoint ctr = CGPointMake(frame.origin.x + frame.size.width/2.0, frame.origin.y + frame.size.height/2.0); 

    // create a circle using 4 arcs, with the first one symmetrically spanning the y-axis 
    CGPoint leftUpper = CGPointMake(ctr.x - cosR, ctr.y - cosR); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y - cpDelta); 

    CGPoint rightUpper = CGPointMake(ctr.x + cosR, ctr.y - cosR); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y - cpDelta); 
    CGPoint cp3 = CGPointMake(rightUpper.x + cpDelta, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(ctr.x + cosR, ctr.y + cosR); 
    CGPoint cp4 = CGPointMake(rightLower.x + cpDelta, rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y + cpDelta); 

    CGPoint leftLower = CGPointMake(ctr.x - cosR, ctr.y + cosR); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y + cpDelta); 
    CGPoint cp7 = CGPointMake(leftLower.x - cpDelta, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x - cpDelta, leftUpper.y + cpDelta); 

    UIBezierPath *circle = [UIBezierPath bezierPath]; 
    [circle moveToPoint:leftUpper]; 
    [circle addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [circle addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [circle addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [circle addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [circle closePath]; 
    circle.lineCapStyle = kCGLineCapRound; 
    return circle; 
} 


-(UIBezierPath *)createPolygonPathForFrame:(CGRect) frame numberOfSides:(NSInteger) sides { 
    CGPoint leftUpper = CGPointMake(self.frame.origin.x, self.frame.origin.y); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y); 

    CGPoint rightUpper = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y); 
    CGPoint cp3 = CGPointMake(rightUpper.x, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp4 = CGPointMake(rightLower.x , rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y); 

    CGPoint leftLower = CGPointMake(self.frame.origin.x, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y); 
    CGPoint cp7 = CGPointMake(leftLower.x, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x, leftUpper.y + cpDelta); 

    UIBezierPath *square = [UIBezierPath bezierPath]; 
    [square moveToPoint:leftUpper]; 
    [square addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [square addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [square addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [square addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [square closePath]; 
    square.lineCapStyle = kCGLineCapRound; 
    return square; 
} 


-(void)toggleShape { 
    if (self.isExpandedPolygon) { 
     [self restore]; 
    }else{ 

     CABasicAnimation *expansionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
     expansionAnimation.fromValue = (__bridge id)(self.circlePath.CGPath); 
     expansionAnimation.toValue = (__bridge id)(self.polyPath.CGPath); 
     expansionAnimation.duration = 0.5; 
     expansionAnimation.fillMode = kCAFillModeForwards; 
     expansionAnimation.removedOnCompletion = NO; 

     [self addAnimation:expansionAnimation forKey:@"Expansion"]; 
     self.isExpandedPolygon = YES; 
    } 
} 


-(void)restore { 
    CABasicAnimation *contractionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
    contractionAnimation.fromValue = (__bridge id)(self.polyPath.CGPath); 
    contractionAnimation.toValue = (__bridge id)(self.circlePath.CGPath); 
    contractionAnimation.duration = 0.5; 
    contractionAnimation.fillMode = kCAFillModeForwards; 
    contractionAnimation.removedOnCompletion = NO; 

    [self addAnimation:contractionAnimation forKey:@"Contraction"]; 
    self.isExpandedPolygon = NO; 
} 

从视图控制器,我创建这个层的一个实例,并将其添加到一个简单视图的图层,然后做一个按钮推动画,

#import "ViewController.h" 
#import "RDPolyCircle.h" 

@interface ViewController() 
@property (weak, nonatomic) IBOutlet UIView *circleView; // a plain UIView 150 x 150 centered in the superview 
@property (strong,nonatomic) RDPolyCircle *shapeLayer; 
@end 

@implementation ViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    // currently isPointUp and numberOfSides are not implemented (the shape created has numberOfSides=4 and isPointUp=NO) 
    // isInitiallyCircle is implemented 
    self.shapeLayer = [[RDPolyCircle alloc] initWithFrame:self.circleView.bounds numberOfSides: 4 isPointUp:NO isInitiallyCircle:YES]; 
    [self.circleView.layer addSublayer:self.shapeLayer]; 
} 


- (IBAction)toggleShape:(UIButton *)sender { 
    [self.shapeLayer toggleShape]; 
} 

该项目可在这里找到,http://jmp.sh/iK3kuVs

+0

我可以分享我简单的'func bezierPathWithPolygonInRect(rect:CGRect,numberOfSides:Int) - > UIBezierPath {'方法 – coco 2015-02-11 15:51:05

9

另一种方法是使用显示链接。它就像一个计时器,除了它与显示更新协调。然后您可以根据显示链接的处理程序在动画的任何特定点处根据它应该看起来的样子来修改视图。例如,如果要将蒙版边角的圆角从0到50点进行动画制作,则可以执行以下操作,其中percent的值介于0.0和1.0之间,表示动画的百分比是完成:

let path = UIBezierPath(rect: imageView.bounds) 
let mask = CAShapeLayer() 
mask.path = path.CGPath 
imageView.layer.mask = mask 

let animation = AnimationDisplayLink(duration: 0.5) { percent in 
    let cornerRadius = percent * 50.0 
    let path = UIBezierPath(roundedRect: self.imageView.bounds, cornerRadius: cornerRadius) 
    mask.path = path.CGPath 
} 

其中:

class AnimationDisplayLink : NSObject { 
    var animationDuration: CGFloat 
    var animationHandler: (percent: CGFloat) ->() 
    var completionHandler: (() ->())? 

    private var startTime: CFAbsoluteTime! 
    private var displayLink: CADisplayLink! 

    init(duration: CGFloat, animationHandler: (percent: CGFloat)->(), completionHandler: (()->())? = nil) { 
     animationDuration = duration 
     self.animationHandler = animationHandler 
     self.completionHandler = completionHandler 

     super.init() 

     startDisplayLink() 
    } 

    private func startDisplayLink() { 
     startTime = CFAbsoluteTimeGetCurrent() 
     displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:") 
     displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) 
    } 

    private func stopDisplayLink() { 
     displayLink.invalidate() 
     displayLink = nil 
    } 

    func handleDisplayLink(displayLink: CADisplayLink) { 
     let elapsed = CFAbsoluteTimeGetCurrent() - startTime 
     var percent = CGFloat(elapsed)/animationDuration 

     if percent >= 1.0 { 
      stopDisplayLink() 
      animationHandler(percent: 1.0) 
      completionHandler?() 
     } else { 
      animationHandler(percent: percent) 
     } 
    } 
} 

美德显示链接的方法之一是,它可以被用来制作动画的一些属性,否则unanimatable。它还可以让你在动画期间精确地指定临时状态。

如果你可以使用CAAnimationUIKit基于块的动画,这可能是一条路。但显示链接有时可能是一种很好的后备方法。

+0

当CAAnimation和UIKit不能处理某些事情时,这可能会非常方便! – coco 2015-02-11 15:52:45