iOS动画相关笔记

CALayer的属性

1 contents

contents属性用来绘制寄宿图,它接受一个id类型,但是实际上接受的是CGImageRef,一个指向CGImage的指针,但是直接使用UIImage的CGImage属性是不行的,因为它需要一个cocoa对象而不是CoreFoundation对象,可以利用以下方式做转换:

layer.contents = (__bridge id)image.CGImage

2 contentsGravity

contentsGravity属性类似于UIView的contentMode,不过他是一个NSString,有以下值:

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill

3 contentsScale

这个属性不是用来设置缩放的(缩放有transformaffineTransform),而是和retina屏幕有关,如果设置为1.0则1个点显示一个像素,设置为2.0则1个点显示两个像素。但是它并不总是有效,如果你设置了contentsGravitykCAGravityResizeAspect对图片做了拉伸,那再设置contentsScale就没用了,但是如果你设置为kCAGravityCenter,那就有用了,一般会这样来设置scale:

layer.contentsScale = [UIScreen mainScreen].scale;

4 maskToBounds

UIView有一个clipsToBounds属性,用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds,把它设置为YES,图像就不会超出边界绘制了。

1.5 contentsRect

用来控制显示图像的一部分,CGRect类型,但是使用单位坐标,范围是0~1:

layer.contentsRect = CGRectMake(0,0,0.5,0.5);// 显示图像左上角

5 contentsCenter

和名字不同,这个属性用来控制拉伸(改变contentsGravity的时候)的范围,依旧使用单位坐标,默认值为(0,0,1,1),均匀拉伸,设置其他值的效果参考下图:


contentsCenter效果
    UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH / 2, SCREEN_WIDTH / 2)];
    whiteView.centerX = SCREEN_WIDTH / 2;
    whiteView.centerY = SCREEN_HEIGHT / 2;
    whiteView.backgroundColor = [UIColor whiteColor];
    
    UIImage *image = [UIImage imageNamed:@"facecertificate_done"];
    whiteView.layer.contents = (__bridge id)image.CGImage;
    whiteView.layer.contentsGravity = kCAGravityCenter;
    whiteView.layer.contentsScale = [UIScreen mainScreen].scale;
    whiteView.layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);
    whiteView.layer.contentsCenter = CGRectMake(0.25, 0.25, 0.75, 0.75);
    whiteView.layer.contentsGravity = kCAGravityResize;

6 UIView的drawRect方法

利用这个方法也可以绘制寄宿图。它没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到-drawRect: 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值。如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法。

7 布局

frame代表了图层的外部坐标(也就是在父图层上占据的空间),bounds是内部坐标({0, 0}通常是图层的左上角),center和position都代表了相对于父图层anchorPoint所在的位置。
frame并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。

8 position和anchorPoint

position和anchorPoint没法分开说,position单位是点,anchorPoint则是一个单位坐标,position是一个相对于父layer的坐标,anchorPoint则是相对于自身layer的一个点,当这个点和position重合的时候(anchorPoint向position移动),自身layer的位置就确定了。更改layer的其中一个值不会影响另一个值,但是会影响layer所在的位置。
举个栗子:

UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH / 2, SCREEN_WIDTH / 2)];
whiteView.centerX = SCREEN_WIDTH / 2;
whiteView.centerY = SCREEN_HEIGHT / 2;
whiteView.backgroundColor = [UIColor whiteColor];

CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(SCREEN_WIDTH / 8, SCREEN_WIDTH / 8, SCREEN_WIDTH / 4, SCREEN_WIDTH / 4);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
[whiteView.layer addSublayer:blueLayer];
NSLog(@"sublayer position:(%.2f,%.2f) anchorPoint:(%.2f,%.2f)", blueLayer.position.x, blueLayer.position.y, blueLayer.anchorPoint.x, blueLayer.anchorPoint.y);

如下:


未设置.jpg

输出为:

sublayer position:(93.75,93.75) anchorPoint:(0.50,0.50)

此时的position为view的中央,锚点为中心点

改变下position

blueLayer.position = CGPointMake(70, 70);

如下:


position.PNG

输出为:

sublayer position:(70.00,70.00) anchorPoint:(0.50,0.50)

只改变anchorPoint:

blueLayer.anchorPoint = CGPointMake(0, 0);

如下:


anchorPoint.PNG

输出为:

sublayer position:(93.75,93.75) anchorPoint:(0.00,0.00)

9 zPosition

增加zPosition可以让图层盖在其他图层上面,这个值还和3D动画有关系。

10 设置阴影

相关属性:shadowOpacity, shadowColorshadowOffsetshadowRadius

  • shadowOpacity:是一个必须在0.0(不可见)和1.0(完全不透明)之间的浮点数。
  • shadowColor:控制着阴影的颜色,和borderColorbackgroundColor一样,它的类型也是CGColorRef。阴影默认是黑色,大多数时候你需要的阴影也是黑色的。
  • shadowOffset:控制着阴影的方向和距离。它是一个CGSize的值,宽度控制这阴影横向的位移,高度控制着纵向的位移。shadowOffset的默认值是{0, -3},意即阴影相对于Y轴有3个点的向上位移(也就是说阴影在上面)。
  • shadowRadius:控制着阴影的模糊度,当它的值是0的时候,阴影就和视图一样有一个非常确定的边界线。当值越来越大的时候,边界线看上去就会越来越模糊和自然。苹果自家的应用设计更偏向于自然的阴影,所以一个非零值再合适不过了。

当阴影形状已知的时候,可以通过设置shadowPath属性来直接设置阴影的形状,因为阴影的计算尤其是当寄宿图所在layer透明的时候会消耗资源。

11 设置蒙版

CALayer有个mask属性,是一个CALayer类型,不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域,这个图层的实际内容不重要,重要的是他的轮廓,如果他比父图层小,则会将父图层切割成自己的轮廓形状。


mask图层.png

12 拉伸过滤算法

layer的minificationFiltermagnificationFilter分别代表图层的缩小和放大的拉伸过滤算法,均有三种值:

  • kCAFilterLinear:线性过滤
  • kCAFilterNearest:最近过滤
  • kCAFilterTriline:三线性过滤
    其中线性过滤和三线性过滤效果几乎差不多,都是取附近的几个像素平均来计算新像素,能够得到较为平滑的图像。最近过滤则顾名思义是取最接近他的像素来计算新像素,在图像斜线少、差异特别明显的时候会得到很好的效果,否则会充满马赛克。

图像变换

1 仿射变换

UIView的transform属性是一个CGAffineTransform类型,用于在二维空间做旋转,缩放和平移。CGAffineTransform是一个可以和二维空间向量(例如CGPoint)做乘法的3X2的矩阵。

仿射变换矩阵.jpeg

CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行。
如果你对矩阵完全不熟悉,可以使用以下函数创建CAAffineTransform对象:

  • CGAffineTransformMakeRotation(CGFloat angle)
  • CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
  • CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

UIView可以通过设置transform属性做变换,但实际上它只是封装了内部图层的变换。
CALayer同样也有一个transform属性,但它的类型是CATransform3D,而不是CGAffineTransform。CALayer对应于UIView的transform属性叫做affineTransform
使用图层的affineTransform属性对图形做顺时针45度旋转:

CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;

CGAffineTransformMakeRotation函数接受一个弧度作为参数,而不是角度值。

如果既要对图像做旋转,又要对图像做平移怎么办?Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换:

  • CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
  • CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
  • CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
    这种变换称为混合变换,其中参数t就是第一步要做的变换。
    当需要一个什么都不做的初始变换的时候,可以使用常量:CGAffineTransformIdentity
    如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换的基础上创建一个新的变换:
    CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

变换的顺序会影响最终的结果,如果你对一个图像先缩小到50%,再旋转30度,最后平移200像素,则最终的平移结果会是斜向移动100像素。

2 3D变换

CGAffineTransform类似,CATransform3D也是一个矩阵,但是和2x3的矩阵不同,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵:

三维变换.jpeg

CGAffineTransform矩阵类似,Core Animation提供了一系列的方法用来创建和组合CATransform3D类型的矩阵,和Core Graphics的函数类似,但是3D的平移和旋转多处了一个z参数,并且旋转函数除了angle之外多出了x,y,z三个参数,分别决定了每个坐标轴方向上的旋转:

  • CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
  • CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
  • CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
    z轴垂直于屏幕,往眼睛方向为正方向,绕z轴旋转其实就是2D仿射变换里面的旋转。
    对图层沿y轴旋转45度:
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView.layer.transform = transform;

y.jpeg

看起来图层并没有被旋转,而是仅仅在水平方向上的一个压缩,是哪里出了问题呢?
其实完全没错,视图看起来更窄实际上是因为我们在用一个斜向的视角看它,而不是透视

3 透视投影

在真实世界中,当物体远离我们的时候,由于视角的原因看起来会变小,理论上说远离我们的视图的边要比靠近视角的边跟短,但实际上并没有发生,而我们当前的视角是等距离的,也就是在3D变换中任然保持平行,和之前提到的仿射变换类似。
在等距投影中,远处的物体和近处的物体保持同样的缩放比例,这种投影也有它自己的用处(例如建筑绘图,颠倒,和伪3D视频),但当前我们并不需要。
为了做一些修正,我们需要引入投影变换(又称作z变换)来对除了旋转之外的变换矩阵做一些修改,Core Animation并没有给我们提供设置透视变换的函数,因此我们需要手动修改矩阵值,幸运的是,很简单:
CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制:m34m34用于按比例缩放X和Y的值来计算到底要离视角多远。

m34.jpeg

m34的默认值是0,我们可以通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,通常500-1000就已经很好了。

4 灭点

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。
在现实中,这个点通常是视图的中心,于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点,或者至少是包含所有3D对象的视图中点。Core Animation定义了这个点位于变换图层的anchorPoint(通常位于图层中心,但也有例外)。这就是说,当图层发生变换时,这个点永远位于图层变换之前anchorPoint的位置。

5 sublayerTransform属性

如果有多个视图或者图层,每个都做3D变换,那就需要分别设置相同的m34值,并且确保在变换之前都在屏幕中央共享同一个position,比较麻烦。
CALayer有一个属性叫做sublayerTransform。它也是CATransform3D类型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。

CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;// 保证子图层拥有相同的透视投影
self.containerView.layer.sublayerTransform = perspective;
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform1;
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView2.layer.transform = transform2;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容

  • 我爱上的人,都先于我的爱而离去了。诸如曹雪芹、苏轼、朱生豪、巴金、张国荣、陈晓旭……每每都感叹:君生我未生,我生君...
    莫怨东风阅读 767评论 34 17
  • 每个人都是执行力之王,因为每一个人都能把一件没必要的事儿做到底,比如刷手机。常常听到“你要是能把打游戏的这股劲用到...
    Ciel天阅读 162评论 0 0
  • Java语言有一个优点就是它相对其他编程语言(例如C语言)摒弃了指针的数据类型,但是否真的摒弃了还得仔细研究一下j...
    超级小江阅读 1,097评论 0 0
  • 二十岁,一个涉世不深却自谓看透一切的年龄。 有关二十,有关我··· 村上春树,那个沉默的日本作家曾说过如下一段话,...
    云小婵阅读 243评论 0 1