CALayer position 和 anchorPoint 的问题

概要:

CALayerposition白话来讲:position就是layer 自身的anchorPoint相对于父superLayer的坐标位置。
常规说来,在iOS上,一个图层的position位于父图层的左上角
具体概念参考:这将是你最后一次纠结position与anchorPoint

关于画曲线的角度参考图:


正文:

需求是一个扫描匹配的动画:

hhhh.gif

如上展示的是最终的效果。这里主要用的是一个自定义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"];

这一步做完会出来如下效果:

hhhh1.gif

接下来就是添加雷达的内圈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是控制器viewbounds,此时运行出来的结果如下:

middleCycle.gif

接下来就是最后一步,添加扇形的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];

此时运行出来静态情况下的都没问题:

middleCyclestatic.gif

然后添加旋转动画,让扇形绕着圆心进行自转:

  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"];

然而运行出来的结果并没有和我们预期的一样:

middleCycle.gif

仔细观察可以发现,扇形是在围绕着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;

此时我们再来看运行结果:

middleCycle.gif

此时扇形确实是绕着中心点开始转了,但是并不是我们想要的结果。。。此时内心是崩溃的。所以放弃了这种动画的方式,直接将扇形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"];
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,421评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,066评论 5 13
  • 转载:http://www.jianshu.com/p/32fcadd12108 每个UIView有一个伙伴称为l...
    F麦子阅读 6,121评论 0 13
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,053评论 1 23
  • 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你...
    被吹落的风阅读 1,540评论 1 2