核心动画 Core Animation(一、Layer基础)

核心动画(Core Animation)是iOS和OS X上图形渲染和动画的基础设施,用于为应用程序的视图和其他视觉元素设置动画。核心动画本身不是绘图系统。它是用于在硬件中合成和操纵应用程序内容的基础设施。这种基础设施的核心是层(layer)对象,用于管理和操纵您的内容。一个图层将您的内容捕获到位图中,这些位图可以通过图形硬件轻松操作。在大多数应用程序中,图层用作管理视图内容的一种方式,但也可以根据需要创建独立图层。
 基于layer层的绘图与传统的基于视图的绘图技术有很大的不同。使用基于视图的绘图,对视图本身的更改通常会导致调用视图的drawRect:方法以使用新参数重新绘制内容。但是以这种方式绘制是昂贵的,因为它是使用主线程上的CPU完成的。Core Animation可以通过在硬件中操作缓存的位图来实现相同或相似的效果,从而避免这种代价。

一、关于Layer的几个重要属性及方法

CALayer类中属性和方法有很多,大多数和使用View的方式相同,在此不多赘述,具体可以自行查看CALayer.h文件。下面主要介绍一下比较重要的属性方法。

1、属性:
  • position(CGPoint):位置,与view中的center差不多,默认情况是layer的中心。但是会受锚点(anchorPoint)影响而改变。
  • zPosition(CGFloat):layer的position在父类(super layer)上的Z轴分量,默认为零。
  • anchorPoint(CGPoint):锚点,使用的是以自身为参考的单位坐标系,默认为(0.5, 0.5)。当操纵层的属性position或transform属性时,锚点的影响最为明显。position属性总是相对于图层的锚点指定,并且对于应用于图层的任何转换也会相对于锚点发生。
图例
  • anchorPointZ(CGFloat):layer的anchorPoint在Z轴上的分量,默认为零。

  • transform(CATransform3D):作用于layer的3D变换。默认是单位矩阵CATransform3DIdentity。

    • 每个layer都有两个变换矩阵属性(transform、sublayerTransform),可以使用它们来操纵图层及其内容。CALayer的transform属性指定要既适用于层和其嵌入式子层的变换。通常,当要修改图层本身时,可以使用此属性。sublayerTransform属性定义了仅适用于子层的附加变换,最常用于向场景内容添加透视视觉效果。
    • 因为Core Animation值可以在三维中指定,所以每个坐标点有四个值必须乘以四乘四个矩阵(见下图)。核心动画提供了一套全面的功能,用于创建缩放,转换和旋转矩阵以及进行矩阵比较(具体可见CATransform3D.h文件,里面方法的使用可以参考CATransform3D -> 3D变换
      )。除了使用函数操纵变换之外,Core Animation扩展了键值编码支持,允许使用关键路径修改变换。
  • sublayerTransform(CATransform3D):当将内容呈现到接收器的输出中时,将3D变换应用于“sublayers”数组的每个成员。 通常用作投影矩阵以将透视和其他观看效果添加到模型。默认是单位矩阵。

  • sublayers(NSArray<CALayer >):子layer的集合。

  • masksToBounds(BOOL):是否沿着边界裁剪。

  • contents(id):layer的内容由包含要显示的视觉数据的位图组成。您可以通过以下三种方式之一提供该位图的内容:

    • 直接将图像对象直接分配给图层对象的contents属性。(这种技术最适合从未或很少改变的图层内容。注意必须为CGImageRef类型)
    • 将一个委托对象分配给图层,让代理绘制图层的内容。(此技术最适合可能会周期性更改并可由外部对象(如视图)提供的图层内容。)
    • 定义一个layer的子类并覆盖其绘图方法,自己提供图层内容。(如果您要创建自定义层子类,或者如果要更改图层的基本绘图行为,则此技术是适用的。)
  • contentsRect(CGRect):默认是{0, 0, 1,1}通俗来说就是将layer看成单位矩形,取其中的一部分。所有参数一般是[0-1]。

  • contentsGravity(NSString):决定分配给contents属性图像一什么方式呈现,一般分配给该属性的值分为两类:

    • 基于位置的重力常数允许将图像定向到图层边界矩形的特定边缘或角落,而 不缩放图像。具体见 PS-1
    • 基于缩放的重力常数允许来拉伸图像。具体见 PS-2
  • contentsScale(CGFloat):默认值是1.0,属性主要作用是适应Retina屏与非Retina屏的,如果是Retina屏则设置为2.0。一般可以这样设置[UIScreen mainScreen].scale

  • contentsCenter(CGFloat):默认是{0, 0, 1,1},在contentsRect的基础上确定缩放比例,主要决定因子是前两个元素,决定了原图纵横/现图纵横的比例,如:0.2则原图被放大五倍!!!

  • opaque(BOOL):由-drawInContext提供的图层内容是完全不透明的。 默认为NO。

  • opacity(float):layer的透明度,默认是1。

还有一些常见的属性:如frame, hidden, backgroundColor, cornerRadius, borderWidth, borderColor, shadowColor,shadowOpacity, shadowOffset, shadowRadius, shadowPath 等。

除此之外,核心动画对它所属的CAAnimation和CALayer类扩展了NSKeyValueCoding的协议 --- 必须使用setValue:forKeyPath:valueForKeyPath:方法来设置和获取这些字段,并增加了关键路径对CGPointCGRectCGSize,和CATransform3D类型的支持(表-1)。所以可以直接使用KVC对其 属性 进行赋取值,在这里需要特别注意对transform等结构体来使用keyPath的情形(表-2)

表-1

C类 包装类
CGPoint /CGSize/CGRect/CATransform3D NSValue

表-2

字段路径 包装类及描述
transform.translation NSValue(包含CGSize数据类型) , 在x和y轴上平移的量
transform. translation.x NSNunber , 沿x轴平移
transform. translation.y NSNunber , 沿y轴平移
transform. translation.z NSNunber , 沿z轴平移
transform. scale NSNunber , xyz三个比例因子的平均值
transform. scale.x NSNunber , 沿x轴缩放
transform. scale.y NSNunber , 沿y轴缩放
transform. scale.z NSNunber , 沿z轴缩放
transform. rotation NSNunber , 沿z轴旋转的弧度,与transform. rotation.z相同
transform. rotation.x NSNunber , 沿x轴旋转的弧度
transform. rotation.y NSNunber , 沿y轴旋转的弧度
transform. rotation.z NSNunber , 沿z轴旋转的弧度
*** ***
position NSValue
position.x NSNunber
position.y NSNunber
*** ***
bounds/frame NSValue
bounds.origin 同position
bounds. size NSValue
bounds. size.width NSNumber
bounds. size.height NSNumber

PS-1:基于位置的重力常数:

CA_EXTERN NSString * const kCAGravityCenter
CA_EXTERN NSString * const kCAGravityTop
CA_EXTERN NSString * const kCAGravityBottom
CA_EXTERN NSString * const kCAGravityLeft
CA_EXTERN NSString * const kCAGravityRight
CA_EXTERN NSString * const kCAGravityTopLeft
CA_EXTERN NSString * const kCAGravityTopRight
CA_EXTERN NSString * const kCAGravityBottomLeft
CA_EXTERN NSString * const kCAGravityBottomRight
PS-1

PS-2:基于缩放的重力常数:

CA_EXTERN NSString * const kCAGravityResize
CA_EXTERN NSString * const kCAGravityResizeAspect
CA_EXTERN NSString * const kCAGravityResizeAspectFill
PS-2
2、方法:
  • + (nullable id)defaultValueForKey:(NSString *)key;:在使用KVC时为键提供默认值。一般进行复写来覆盖原有方法进行默认值设置。

  • - (void)display;:重新加载图层的内容。 用法:如果实现了委托方法,默认会调用displayLayer:委托方法。否则,display方法会调用drawInContext方法,然后更新图层的“contents”属性。但是一般不主动调用!

  • - (void)setNeedsDisplay;- (void)setNeedsDisplayInRect:(CGRect)r;:与上面的方法差不多,但是可以主动调用。如果设置了rect,则只有该层的该区域无效。

  • - (void)displayIfNeeded;:绘图系统在需要时自动调用,如果已经调用了setNeedsDisplay,该方法无效。

  • - (void)drawInContext:(CGContextRef)ctx;默认的display方法会创建一个视图图形上下文并将其传递给drawInContext:方法,与[UIView drawRect:]方法相似。


代理方法:

  • - (void)displayLayer:(CALayer *)layer;如果实现了代理同时定义了此方法则,-display方法会调用此方法,该实现负责创建位图并将其分配给图层的contents属性。

  • - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox; -drawInContext调用此方法,创建一个图形上下文来绘制该位图,然后调用委托方法来填充位图。如果-displayLayer: 方法存在,则该方法不调用,无效。

  • - (void)layerWillDraw:(CALayer *)layer:新加的方法,如果定义,则由-display方法的默认实现调用。 允许代理在-drawLayer之前配置影响内容的任何图层状态。同样,如果-displayLayer: 方法存在,则该方法不调用,无效。

所以综以上方法总结layer方法响应链有两种:

  • [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:] 。
  • [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]。

关于动画的方法:

  • (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;

  • (void)removeAllAnimations;

  • (void)removeAnimationForKey:(NSString *)key;

示例代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    _blueLayer = [CALayer layer];
    [_blueLayer setValue:(__bridge id)[UIColor blueColor].CGColor forKeyPath:@"backgroundColor"];
    [_blueLayer setValue:[NSValue valueWithCGPoint:CGPointZero] forKeyPath:@"anchorPoint"];
    [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
    [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];
    [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(50, 100, 200, 350)] forKeyPath:@"frame"];
    _blueLayer.delegate = self;
    [self.view.layer addSublayer:_blueLayer];
}
- (void)displayLayer:(CALayer *)layer
{
    if (once) {
        [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 0.7)] forKeyPath:@"contentsRect"];
        [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];

    }else{
        [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
        [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"girl"] CGImage] forKeyPath:@"contents"];
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [_blueLayer setValue:kCAGravityResizeAspect forKeyPath:@"contentsGravity"];
    if (once) {
        once = NO;
        [UIView animateWithDuration:10.0 animations:^{
            [_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.x"];
            [_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.y"];
        }];
    }else{
        [UIView animateWithDuration:10.0 animations:^{
            [_blueLayer setValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] forKeyPath:@"transform"];
        }];
        once = YES;
    }
    [_blueLayer setNeedsDisplay];
}
- (void)dealloc
{
    // 在这里代理一定要置空!否则控制器无法释放
    _blueLayer.delegate = nil;
}
效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容