两个贝塞尔path形状之间的animation
我发现在两个状态之间animationUIImageView
是非常棘手的:原始的矩形框架和用UIBezierPath
创build的新形状。 有很多不同的技术提到,其中大部分不适合我。
首先是认识到使用UIView块animation不行; 显然不能在animateWithDuration:
块中执行子图层animation。 (见这里和这里 )
这就离开了CAAnimation
,像CABasicAnimation
这样的具体子类。 我很快就意识到,不能从没有CAShapeLayer
的视图(例如在这里看到)创buildanimation。
而且它们不能只是任何两个形状的层path,而是“animation形状层的path只有当你从喜欢到喜欢animation时保证工作”(见这里 )
有了这个,就会出现更多世俗的问题,比如从fromValue
和fromValue
(应该是CAShapeLayer
还是CGPath
?),将animation添加到( layer
或mask
?)等等。
似乎有这么多的变数, 哪个组合会给我我想要的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过程中精确地指定临时状态。
如果您可以使用CAAnimation
或UIKit
基于块的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 。