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),均匀拉伸,设置其他值的效果参考下图:


iOS动画相关笔记_第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);

如下:


iOS动画相关笔记_第2张图片
未设置.jpg

输出为:

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

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

改变下position

blueLayer.position = CGPointMake(70, 70);

如下:


iOS动画相关笔记_第3张图片
position.PNG

输出为:

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

只改变anchorPoint:

blueLayer.anchorPoint = CGPointMake(0, 0);

如下:


iOS动画相关笔记_第4张图片
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图层定义了父图层的部分可见区域,这个图层的实际内容不重要,重要的是他的轮廓,如果他比父图层小,则会将父图层切割成自己的轮廓形状。


iOS动画相关笔记_第5张图片
mask图层.png

12 拉伸过滤算法

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

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

图像变换

1 仿射变换

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

iOS动画相关笔记_第6张图片
仿射变换矩阵.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的矩阵:

iOS动画相关笔记_第7张图片
三维变换.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;

iOS动画相关笔记_第8张图片
y.jpeg

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

3 透视投影

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

iOS动画相关笔记_第9张图片
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;

你可能感兴趣的:(iOS动画相关笔记)