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
这个属性不是用来设置缩放的(缩放有transform
和affineTransform
),而是和retina屏幕有关,如果设置为1.0则1个点显示一个像素,设置为2.0则1个点显示两个像素。但是它并不总是有效,如果你设置了contentsGravity
为kCAGravityResizeAspect
对图片做了拉伸,那再设置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),均匀拉伸,设置其他值的效果参考下图:
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);
如下:
输出为:
sublayer position:(93.75,93.75) anchorPoint:(0.50,0.50)
此时的position为view的中央,锚点为中心点
改变下position
blueLayer.position = CGPointMake(70, 70);
如下:
输出为:
sublayer position:(70.00,70.00) anchorPoint:(0.50,0.50)
只改变anchorPoint:
blueLayer.anchorPoint = CGPointMake(0, 0);
如下:
输出为:
sublayer position:(93.75,93.75) anchorPoint:(0.00,0.00)
9 zPosition
增加zPosition可以让图层盖在其他图层上面,这个值还和3D动画有关系。
10 设置阴影
相关属性:shadowOpacity
, shadowColor
,shadowOffset
和shadowRadius
。
-
shadowOpacity
:是一个必须在0.0(不可见)和1.0(完全不透明)之间的浮点数。 -
shadowColor
:控制着阴影的颜色,和borderColor
和backgroundColor
一样,它的类型也是CGColorRef
。阴影默认是黑色,大多数时候你需要的阴影也是黑色的。 -
shadowOffset
:控制着阴影的方向和距离。它是一个CGSize
的值,宽度控制这阴影横向的位移,高度控制着纵向的位移。shadowOffset
的默认值是{0, -3}
,意即阴影相对于Y轴有3个点的向上位移(也就是说阴影在上面)。 -
shadowRadius
:控制着阴影的模糊度,当它的值是0的时候,阴影就和视图一样有一个非常确定的边界线。当值越来越大的时候,边界线看上去就会越来越模糊和自然。苹果自家的应用设计更偏向于自然的阴影,所以一个非零值再合适不过了。
当阴影形状已知的时候,可以通过设置shadowPath
属性来直接设置阴影的形状,因为阴影的计算尤其是当寄宿图所在layer透明的时候会消耗资源。
11 设置蒙版
CALayer有个mask属性,是一个CALayer类型,不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域,这个图层的实际内容不重要,重要的是他的轮廓,如果他比父图层小,则会将父图层切割成自己的轮廓形状。
12 拉伸过滤算法
layer的minificationFilter
和magnificationFilter
分别代表图层的缩小和放大的拉伸过滤算法,均有三种值:
-
kCAFilterLinear
:线性过滤 -
kCAFilterNearest
:最近过滤 -
kCAFilterTriline
:三线性过滤
其中线性过滤和三线性过滤效果几乎差不多,都是取附近的几个像素平均来计算新像素,能够得到较为平滑的图像。最近过滤则顾名思义是取最接近他的像素来计算新像素,在图像斜线少、差异特别明显的时候会得到很好的效果,否则会充满马赛克。
图像变换
1 仿射变换
UIView的transform
属性是一个CGAffineTransform
类型,用于在二维空间做旋转,缩放和平移。CGAffineTransform
是一个可以和二维空间向量(例如CGPoint)做乘法的3X2的矩阵。
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的矩阵:
和
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;
看起来图层并没有被旋转,而是仅仅在水平方向上的一个压缩,是哪里出了问题呢?
其实完全没错,视图看起来更窄实际上是因为我们在用一个斜向的视角看它,而不是
透视
。
3 透视投影
在真实世界中,当物体远离我们的时候,由于视角的原因看起来会变小,理论上说远离我们的视图的边要比靠近视角的边跟短,但实际上并没有发生,而我们当前的视角是等距离的,也就是在3D变换中任然保持平行,和之前提到的仿射变换类似。
在等距投影中,远处的物体和近处的物体保持同样的缩放比例,这种投影也有它自己的用处(例如建筑绘图,颠倒,和伪3D视频),但当前我们并不需要。
为了做一些修正,我们需要引入投影变换(又称作z变换)来对除了旋转之外的变换矩阵做一些修改,Core Animation并没有给我们提供设置透视变换的函数,因此我们需要手动修改矩阵值,幸运的是,很简单:
CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制:m34
。m34
用于按比例缩放X和Y的值来计算到底要离视角多远。
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;