概要:
CALayer
的position
白话来讲:position
就是layer 自身的anchorPoint
相对于父superLayer
的坐标位置。
常规说来,在iOS上,一个图层的position位于父图层的左上角
具体概念参考:这将是你最后一次纠结position与anchorPoint
关于画曲线的角度参考图:
正文:
需求是一个扫描匹配的动画:
如上展示的是最终的效果。这里主要用的是一个自定义view
PulsingView
,重写 (void)drawRect:(CGRect)rect
方法,在这个方法内部去画相应的元素,主要包含:
-
1.外部扩散的脉冲layer。
-
2.雷达扫面的内圈layer。
-
3.雷达扫描的扇形layer。
首先是外部的脉冲扩散动画实现:
这个比较简单,参考网上的好多雷达扫描实现,PulsingView.m
文件中 代码如下:
[super drawRect:rect];
[[UIColor whiteColor] setFill];
UIRectFill(rect);
UIImage * image = [UIImage imageNamed:@"radar_background"];
[image drawInRect:rect];
CGFloat pulsingWidth = rect.size.width / 2.5;
CGFloat pulsingX = self.center.x - pulsingWidth * 0.5;
CGFloat pulsingY = self.center.y - pulsingWidth * 0.5;
// 脉冲layer
CALayer * pulsingLayer = [CALayer layer];
pulsingLayer.frame = CGRectMake(pulsingX, pulsingY, pulsingWidth, pulsingWidth);
pulsingLayer.borderColor = [UIColor colorWithRed:201/255.0 green:201/255.0 blue:201/255.0 alpha:1].CGColor;
pulsingLayer.cornerRadius = pulsingWidth * 0.5;
pulsingLayer.backgroundColor = [UIColor whiteColor].CGColor;//这里必须设置layer层的背景颜色,默认应该是透明的,导致设置的阴影颜色无法显示出来
pulsingLayer.shadowColor = [UIColor blackColor].CGColor;
pulsingLayer.shadowOffset = CGSizeMake(0, 0);
pulsingLayer.shadowOpacity = 1;
pulsingLayer.shadowRadius = 5;
[self.layer addSublayer:pulsingLayer];
// 缩放动画
CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.autoreverses = false;
scaleAnimation.fromValue = @1.0f;
scaleAnimation.toValue = @2.0f;
// 透明动画
CAKeyframeAnimation * opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.keyTimes = @[@0,@0.1,@0.2,@0.3,@0.4,@0.5,@0.6,@0.7,@0.8,@0.9,@1];
opacityAnimation.values = @[@1,@0.9,@0.8,@0.7,@0.6,@0.5,@0.4,@0.3,@0.2,@0.1,@0];
CAAnimationGroup * animationGroup = [CAAnimationGroup animation];
animationGroup.beginTime = CACurrentMediaTime() + 1;
animationGroup.fillMode = kCAFillModeBackwards;
animationGroup.repeatCount = HUGE_VAL;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animationGroup.duration = 2;
animationGroup.animations = @[scaleAnimation,opacityAnimation];
[pulsingLayer addAnimation:animationGroup forKey:@"pulse"];
这一步做完会出来如下效果:
接下来就是添加雷达的内圈layer,继续在上面的代码下面添加如下代码:
// 中间圆环
CALayer * middleCycle = [CALayer layer];
middleCycle.frame = CGRectMake(pulsingX, pulsingY, pulsingWidth, pulsingWidth);
middleCycle.cornerRadius = pulsingWidth * 0.5;
middleCycle.borderColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
middleCycle.backgroundColor = [UIColor whiteColor].CGColor;
middleCycle.shadowOffset = CGSizeMake(0, 0);
middleCycle.borderWidth = 1;
middleCycle.shadowRadius = 3;
middleCycle.shadowOpacity = 1;
middleCycle.shadowColor = [UIColor lightGrayColor].CGColor;
[self.layer addSublayer:middleCycle];
。这里的坑也是出现在这块地方。因为在外部引用PulsingView
的时候,设置的frame
是控制器view
的bounds
,此时运行出来的结果如下:
接下来就是最后一步,添加扇形的layer 并添加扫描动画(一开始扇形是打算做成渐变的效果的,不过这里没有完美实现出来,一开始使用了一个三方XHRadarView,单纯的引用里面的扇形view的类,这样就可以直接使用他内部的渐变color,不过出来并不是很完美,所以还是自己画扇形,先不做渐变效果了。)此时就出现了关于position问题导致的坑.
这里一开的思路还是和之前一样,继续在self.layer
上添加扇形的layer,以self.center
为圆心,画一个90度的曲线:
CGPoint fanshapedCenter = self.center;
UIBezierPath * fanshapedPath = [UIBezierPath bezierPath];
[fanshapedPath addArcWithCenter:fanshapedCenter radius:(pulsingWidth + 20) * 0.5 startAngle:3 * M_PI / 2 endAngle:0 clockwise:YES];
[fanshapedPath addLineToPoint:fanshapedCenter];
[fanshapedPath closePath];
CAShapeLayer * fanshapedLayer = [CAShapeLayer layer];
fanshapedLayer.path = fanshapedPath.CGPath;
fanshapedLayer.opacity = 0.7;
fanshapedLayer.fillColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
[self.layer addSublayer:fanshapedLayer];
此时运行出来静态情况下的都没问题:
然后添加旋转动画,让扇形绕着圆心进行自转:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: 3 * M_PI ];
rotationAnimation.duration = 8;
rotationAnimation.cumulative = YES;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.repeatCount = MAXFLOAT;
rotationAnimation.fillMode = kCAFillModeForwards;
[fanshapedLayer addAnimation:rotationAnimation forKey:@"rotation"];
然而运行出来的结果并没有和我们预期的一样:
仔细观察可以发现,扇形是在围绕着self的原点(0,0)点进行公转,一开始一直以为是画扇形的时候出的问题,是不是画法不对之类的,还试过直接用Quaritz2D 去画扇形,然而效果依然如此,经过各种查找,才发现是position 导致的问题,通过断点,打印一下
hanshapedLayer
可以看到如下信息:
Printing description of fanshapedLayer:
<CAShapeLayer:0x608000233640; position = CGPoint (0 0); bounds = CGRect (0 0; 0 0); allowsGroupOpacity = YES; fillColor = <CGColor 0x6080000bef00> [<CGColorSpace 0x600000228940> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.160784 0.596078 0.945098 1 ); opacity = 0.7; path = <CGPath 0x60800003bf20>>
可以看到hanshapedLayer的position 是 (0,0),也就是父控件的左上角原点,文章开头我们提到过,layer的position 就是自己anchorPoint 相对于父layer 的坐标,而layer自身旋转也是相对于anchorPoint 旋转,这也就解释了为什么我们上面会出现扇形绕着屏幕的最上角做公转。所以我们就需要将hanshapedLayer
的 position 移到中心点的位置:
fanshapedLayer.position = fanshapedCenter;
此时我们再来看运行结果:
此时扇形确实是绕着中心点开始转了,但是并不是我们想要的结果。。。此时内心是崩溃的。所以放弃了这种动画的方式,直接将扇形fanshapedLayer
添加到中间的内圈layer上,然后让内圈layer自转
,最后可以实现最终的效果。
// 扫描的扇形
UIBezierPath * fanshapedPath = [UIBezierPath bezierPath];
[fanshapedPath addArcWithCenter:CGPointMake(middleCycle.frame.size.width * 0.5, middleCycle.frame.size.height * 0.5) radius:(pulsingWidth + 30) * 0.5 startAngle:3 * M_PI / 2 endAngle:0 clockwise:YES];
[fanshapedPath addLineToPoint:CGPointMake(middleCycle.frame.size.width * 0.5, middleCycle.frame.size.height * 0.5)];
CAShapeLayer * fanshaped = [CAShapeLayer layer];
fanshaped.fillColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
fanshaped.opacity = 0.7;
[fanshapedPath closePath];
fanshaped.path = fanshapedPath.CGPath;
[middleCycle addSublayer:fanshaped];
然后将动画添加到middleCycle上:
[middleCycle addAnimation:rotationAnimation forKey:@"rotation"];
后来在简书上又看到以篇博客也遇到了公转的问题 CALayer旋转动画
参考这位 渣孩子
博主的作法,将画扇形的代码作如下修改:
[fanshapedPath addArcWithCenter:CGPointZero radius:(pulsingWidth + 20) * 0.5 startAngle:3 * M_PI / 2 endAngle:0 clockwise:YES];
[fanshapedPath addLineToPoint:CGPointZero];
现在原点位置画出扇形,然后再设置potisionfanshapedLayer.position = fanshapedCenter;
最后终于解决了这个问题。
最后,贴上draw 方法里的全部代码:
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
[super drawRect:rect];
[[UIColor whiteColor] setFill];
UIRectFill(rect);
UIImage * image = [UIImage imageNamed:@"radar_background"];
[image drawInRect:rect];
CGFloat pulsingWidth = rect.size.width / 2.5;
CGFloat pulsingX = self.center.x - pulsingWidth * 0.5;
CGFloat pulsingY = self.center.y - pulsingWidth * 0.5;
// 脉冲layer
CALayer * pulsingLayer = [CALayer layer];
pulsingLayer.frame = CGRectMake(pulsingX, pulsingY, pulsingWidth, pulsingWidth);
pulsingLayer.borderColor = [UIColor colorWithRed:201/255.0 green:201/255.0 blue:201/255.0 alpha:1].CGColor;
pulsingLayer.cornerRadius = pulsingWidth * 0.5;
pulsingLayer.backgroundColor = [UIColor whiteColor].CGColor;//这里必须设置layer层的背景颜色,默认应该是透明的,导致设置的阴影颜色无法显示出来
pulsingLayer.shadowColor = [UIColor blackColor].CGColor;
pulsingLayer.shadowOffset = CGSizeMake(0, 0);
pulsingLayer.shadowOpacity = 1;
pulsingLayer.shadowRadius = 5;
[self.layer addSublayer:pulsingLayer];
// 缩放动画
CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.autoreverses = false;
scaleAnimation.fromValue = @1.0f;
scaleAnimation.toValue = @2.0f;
// 透明动画
CAKeyframeAnimation * opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.keyTimes = @[@0,@0.1,@0.2,@0.3,@0.4,@0.5,@0.6,@0.7,@0.8,@0.9,@1];
opacityAnimation.values = @[@1,@0.9,@0.8,@0.7,@0.6,@0.5,@0.4,@0.3,@0.2,@0.1,@0];
CAAnimationGroup * animationGroup = [CAAnimationGroup animation];
animationGroup.beginTime = CACurrentMediaTime() + 1;
animationGroup.fillMode = kCAFillModeBackwards;
animationGroup.repeatCount = HUGE_VAL;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animationGroup.duration = 2;
animationGroup.animations = @[scaleAnimation,opacityAnimation];
[pulsingLayer addAnimation:animationGroup forKey:@"pulse"];
// 中间圆环
CALayer * middleCycle = [CALayer layer];
middleCycle.frame = CGRectMake(pulsingX, pulsingY, pulsingWidth, pulsingWidth);
middleCycle.cornerRadius = pulsingWidth * 0.5;
middleCycle.borderColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
middleCycle.backgroundColor = [UIColor whiteColor].CGColor;
middleCycle.shadowOffset = CGSizeMake(0, 0);
middleCycle.borderWidth = 1;
middleCycle.shadowRadius = 3;
middleCycle.shadowOpacity = 1;
middleCycle.shadowColor = [UIColor lightGrayColor].CGColor;
[self.layer addSublayer:middleCycle];
// 添加到self.layer上
CGPoint fanshapedCenter = self.center;
UIBezierPath * fanshapedPath = [UIBezierPath bezierPath];
[fanshapedPath addArcWithCenter:CGPointZero radius:(pulsingWidth + 20) * 0.5 startAngle:3 * M_PI / 2 endAngle:0 clockwise:YES];
[fanshapedPath addLineToPoint:CGPointZero];
[fanshapedPath closePath];
CAShapeLayer * fanshapedLayer = [CAShapeLayer layer];
fanshapedLayer.path = fanshapedPath.CGPath;
fanshapedLayer.opacity = 0.7;
fanshapedLayer.position = fanshapedCenter;
fanshapedLayer.fillColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
[self.layer addSublayer:fanshapedLayer];
// 添加到middleCycle上
// [fanshapedPath addArcWithCenter:CGPointMake(0,0) radius:(pulsingWidth + 20) * 0.5 startAngle:3 * M_PI / 2 endAngle:0 clockwise:YES];
// [fanshapedPath addLineToPoint:CGPointZero];
// CAShapeLayer * fanshaped = [CAShapeLayer layer];
// fanshaped.fillColor = [UIColor colorWithRed:41/255.0 green:152/255.0 blue:241/255.0 alpha:1].CGColor;
// fanshaped.opacity = 0.7;
// fanshaped.position = CGPointMake(middleCycle.frame.size.width * 0.5, middleCycle.frame.size.height * 0.5);
// [fanshapedPath closePath];
// fanshaped.path = fanshapedPath.CGPath;
// [middleCycle addSublayer:fanshaped];
// NSLog(@"fanshaped==== frame:%@,anchorPoint:%@,bounds:%@,position:%@",NSStringFromCGRect(fanshaped.frame),NSStringFromCGPoint(fanshaped.anchorPoint),NSStringFromCGRect(fanshaped.bounds),NSStringFromCGPoint(fanshaped.position));
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: 3 * M_PI ];
rotationAnimation.duration = 8;
rotationAnimation.cumulative = YES;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.repeatCount = MAXFLOAT;
rotationAnimation.fillMode = kCAFillModeForwards;
// [middleCycle addAnimation:rotationAnimation forKey:@"rotation"];
[fanshapedLayer addAnimation:rotationAnimation forKey:@"rotation"];
}