两个贝塞尔path形状之间的animation

我发现在两个状态之间animationUIImageView是非常棘手的:原始的矩形框架和用UIBezierPath创build的新形状。 有很多不同的技术提到,其中大部分不适合我。

首先是认识到使用UIView块animation不行; 显然不能在animateWithDuration:块中执行子图层animation。 (见这里和这里 )

这就离开了CAAnimation ,像CABasicAnimation这样的具体子类。 我很快就意识到,不能从没有CAShapeLayer的视图(例如在这里看到)创buildanimation。

而且它们不能只是任何两个形状的层path,而是“animation形状层的path只有当你从喜欢到喜欢animation时保证工作”(见这里 )

有了这个,就会出现更多世俗的问题,比如从fromValuefromValue (应该是CAShapeLayer还是CGPath ?),将animation添加到( layermask ?)等等。

似乎有这么多的变数, 哪个组合会给我我想要的animation?

第一个重点是构造两条贝塞尔path,所以矩形是更复杂形状的(平凡)模拟。

 // 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) 

更简单的“微不足道”的贝塞尔path,创build一个矩形,是完全一样的,但controlPoint点设置,使其似乎不在那里:

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

(尝试删除addQuadCurveToPoint行来获得一个非常奇怪的animation!)

最后,animation命令:

 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") 

如果有人有兴趣,这个项目就在这里 。

另一种方法是使用显示链接。 这就像一个计时器,除了它与显示更新协调。 然后,显示链接的处理程序根据在animation的任何特定点应该看起来的样子来修改视图。

例如,如果要将蒙版的四个angular的圆angular从0增加到50个点,则可以执行下面的操作,其中percent是介于0.0和1.0之间的值,表示animation的百分比:

 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) } } } 

显示链接方式的优点在于,它可以用来animation一些不可调的属性。 它也可以让你在animation过程中精确地指定临时状态。

如果您可以使用CAAnimationUIKit基于块的animation,那么可能是要走的路。 但是显示链接有时可以成为一个很好的后备方法。

我的灵感来自你的例子,尝试使用你的答案和一些链接中提到的技术来做一个圆形的方形animation。 我打算把这个扩展成一个更一般的多边形animation圈,但目前它只适用于正方形。 我有一个名为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; } 

从视图控制器中,我创build了这个图层的一个实例,并将其添加到一个简单的视图的图层,然后做一个button推动animation,

 #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]; } 

该项目可以在这里findhttp://jmp.sh/iK3kuVs 。