Core Animation学习总结
Posted luozhiwei_iOS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Core Animation学习总结相关的知识,希望对你有一定的参考价值。
目录:
- The Layer Beneath
-
- The Layer Tree(图层树)
- The Backing Image(寄宿层)
- Layer Geometry(图层几何学)
- Visual Effects(视觉效果)
- Transforms(变换)
- Specialized Layers(专有图层)
- Setting Things in Motion
-
- Implicit Animations(隐式动画)
- Explicit Animations(显式动画)
- Layer Time(图层时间)
- Easing(缓冲)
- Timer-Based Animation(基于定时器的动画)
- The Performance of a Lifetime
-
- Tuning for Speed(性能调优)
- Efficient Drawing(高效绘图)
- Image IO(图像IO)
- Layer Performance(图层性能)
Core Animation 之 CALayer
The Layer Tree(图层树)
1、图层与视图的区别:
2、关于视图层级、图层树、呈现树、渲染树
所以以上四层,每一层都负责不同的任务,其数据流是视图-》图层-》呈现树-》渲染树。并且绘制周期60FPS也是会随着系统的状态而变化的,若系统资源超载,而会通过减少绘制次数来确保系统能稳定运行。
3、视图动画
视图动画是显式,因为UIView默认把CALayer的隐式动画禁止掉了,所以要通过动画Block的形式才能取消动画的限制。(动画block实际上就是以前的动画栈)。
并且视图能实现的动画只是图层所开放的一部分,都是2D动画,所以视图动画的效果会稍稍简单。
4、图层动画
图层动画是隐性动画,只要修改图层的属性,其变化都会以默认的渐变形式呈现出来(具体原理见《隐式动画》)。并且图层动画支持更多视图动画无法实现的效果。如:
5、更适宜使用CAlayer呈现内容的场景
因为UIView与CALayer都可以呈现内容,虽然CALayer不能直接实现事件响应,但开发者也可以通过hit-test机制的原理来自己实现事件响应,那什么场景更加适合用CALayer而不是UIView呢?如下所示:
|
The Backing Image(寄宿层)
1、寄宿层&Contents属性
CALayer有一个名曰Contents的属性,这个属性是与寄宿层相对应了,而contents属性指向的对象必须为CGImageRef类型(一个指向CGImage结构的指针),所以寄宿层是用来展示图片所用的,如下情况会调用寄宿层:
core Graphics可以实现自定义绘制,但通常不建议那么做,因为core Graphics绘制会默认生成一个绘制专用的存储空间,而这空间有十多M那么多,会占用大量内存,所以Apple不建议实现空的core Graphics更不建议在core Graphics实现不属于该方法的代码。
2、contentGravity属性
与UIView的contentMode属性相对应,用于调整图片的布局,支持一下常量值:
3、contentsScale
contentsScale属性定义了寄宿层的像素尺寸和视图大小比例,默认是1.0(一个点一个像素),在retina屏幕得设置在2.0(一个点两个像素),在plus设备上得设备为3.0(一个点3个像素)。
但若contentGravity设置了如kCAGravityResizeAspectFill自动适配大小的属性后,contentsScale会不起作用。所以不能依靠contentsScale来做缩放的操作,缩放还是得交给transform或者affineTransform。
3、maskToBounds
该属性与UIView的clipsToBounds属性相对应,都是应用于将超出图层边界的子图层的内容进行裁剪。
这里需要一点需要注意的,但我们实现radiusCorner时,实际上就是通过设置背景颜色来实现的,而与maskToBounds结合才是真正把边角内容裁剪掉,但radiusCorner+maskToBounds结合使用会引发离屏渲染,所以在radiusCorner满足要求,就不要再调用maskToBounds。
4、contentsRect
CALayer的contentsRect允许我们在图层边框内显示寄宿图的一个子域,而contentsRect是一个相对值属性,如{0,0,1,1}表示整个寄宿图,这种坐标系统也叫单位。如下简述iOS下的三种坐标系统单位:
contentsRect有一种经典的用法——image sprites(图片拼接,常用于游戏),这种用法就是用来实现一次性载入多张图片的,因为每一张图片的使用前都要经过,加载——》压缩——》呈现,的一个过程其中加载和压缩是十分耗时的,加载消耗IO,压缩算法的复杂会增加CPU的消耗,所以想游戏这样的App需要加载解压大量的图片时,可以把所有图片整合成一张图片来加载解压,然后通过contentsRect裁剪出子图,并显示,可以优化加载解压的过程。
5、contentsCenter
contentsCenter的名字其实有一点的误导性,其实际上与UIView的Stretching属性相对应,用来实现寄宿层的缩放时的呈现效果。也是一个相对值单位。
6、customs drwaing
除了通过CGImage设置到contents的方式来实现寄宿层,还可以通过Core Graphics来直接绘制寄宿层,但这种方式十分消耗内存不建议使用。
CALayer有一个可选的Delegate属性,实现了CALayerDelegate,但CALayer需要重新绘制,则调用它的displayLayer方法(与UIView的setNeedDispaly相对应),就会调用其代理方法drawLayer:inContext(与UIView的drawRect相对应)。若在视图层中,只要用户实现了drawRect那么只要屏幕需要重新绘制那么该方法都会被默认调用。
|
Layer Geometry(图层几何学)
1、布局
UIView的三个重要布局属性:frame、bounds、center
CALayer的三个重要的布局属性:frame、bounds、position
可以看出UIView与CALayer的布局属性是一一对应的,而唯一有啥差别的就是锚点的命名,UIView为center,而CALayer为position,但锚点最开始都是默认为图形的中心。
其中frame实际上就是一个虚拟属性,其由bounds与center/position计算所得,所以修改bounds与center/position也会影响到frame的数值。
还有无论是视图还是图层,在屏幕上无论怎么变形,本质上其还是一个矩形,但在旋转的情况下,frame的数值是会事实变动的,如下图:
2、锚点
锚点属性是用来指定图层相对于父图层的位置,也可以理解为利用图层的句柄。默认为图层的中心但也可以修改,在图层中修改了锚点会产生移动(隐性动画),同样的锚点也是一个相对值。
如下,为锚点的实验效果图:
图一:时分秒针直接以图层的中点为锚点,因为旋转时也以其为中心,效果如下:
图二:将锚点修改成指针的尾端
图三:修改锚点后的旋转效果。
3、坐标系
因为每个图层都有自己的坐标系,所以CALayer也提供了一系列的方法用于一个点在不同的坐标系之间转换,这些方法特别适合于子图层与父图层之间的坐标转换。
其实UIView与CALayer都有zPosition,但由于UIView只支持2D变换,所以UIView的zPosition仅仅适合于用来作图层的调整,但更加建议用UIView提供的视图数组管理方法来调整视图的绘制顺序来调整。而CALayer的zPosition是一个重要的三维坐标系。
4、Hit Testing
Hit Testing是iOS中一个十分重要的机制,用于检索点击了的目标图标,与响应链条相互搭配的话,就会将最终目标图标设置为第一响应者。Hit Testing提供了两个核心的方法:
5、自动布局
对于UIView,通过UIView暴露出来的UIViewAutoresizingMask和NSLayoutConstraint来实现自动布局。
但对于CALayer,则需要手动来操作实现,其中最为简单的方法就是实现CALayerDelegate的如下函数:
- (void)layoutSublayersOfLayer:(CALayer *)layer;
在该方法内根据当前layer的属性来计算调整来实现自动布局。
因为CALayer实现自动布局不方便,所以这也是为什么更加建议使用UIView来实现界面的构建。
|
Visual Effects(视觉效果)——本节探讨能够通过CALayer实现的附加视觉效果
1、圆角
CALayer有一个叫做cornerRadius的属性控制图层角的曲率,这个曲率只影响背景颜色而不影响图片或者子图片。圆角的计算原理就是一个以CALayer的中点,曲率所设的半径的原与矩形的交集就是目标图形。
还有一个名曰maskToBounds属性,就是实现将图层里面边界外的图形全部切割丢弃。
所以通过cornerRadius+maskToBounds组合可以实现圆角,但同时会引发离屏渲染,若是小量的离屏渲染还好,但如滚动条的场景,开会有大量的离屏渲染的任务产生,就会严重影响性能,这一点也是注意优化的。具体效果如下图所示:
2、图层边框
通过设置CALayer的borderWidth与borderColor两个属性可以修改边框的效果。效果如下:
3、阴影
阴影是一种能达到图层深度暗示效果的装饰。CALayer提供以下参数来定制阴影的效果:
shadowRadius的设置效果图如下:
有一点需要注意的,就是阴影的绘制是属于离屏绘制,是比较效果资源的,所以一定要减少同一屏幕进行大量阴影绘制的情况。
4、阴影裁剪
图层的阴影更多是继承于内容的外形,而不是根据边界和角半径来确定,为了计算出阴影的形状,core animation会将寄宿层也考虑在内。但若直接maskToBounds来裁剪,会把阴影效果也裁减掉。
为了解决以上问题,可以通过专门引入一个阴影图层来解决,具体效果如下:
图一:直接maskToBounds裁剪会把阴影效果一并裁剪掉。
图二:通过添加一个另外的阴影图层在下面,然它来实现阴影,而具体的内容图层就直接maskToBounds裁剪。
图三:最终既能实现裁剪,又能实现阴影。
PS:无论是阴影还是圆角裁剪都会引发离屏渲染,大量的该类型的图形需要渲染的话,会减低帧率。
5、shadowPath 属性
阴影并一定是方形的,我们也可以通过shadowPath来定制自己想要的阴影形状,具体效果如下所示:
6、图层蒙版
通过masksToBounds属性,我们可以实现边界裁剪,但我们还需要更加灵活的裁剪需求的时候就可以通过图层蒙版来实现。CALayer提供一个mask的属性来解决一个问题,而mask本来就是指向另一个图层的指针,其具体原理如下图所示:
从上图效果可以知道mask舒心只关心形状的交集,而颜色还是由原来的图形的颜色决定。
还有一点需要注意的是图层蒙版也会引发离屏渲染,会带来性能问题,所以使用的时候也得多加注意。
7、拉伸过滤
当我们视图显示一个图片的时候,都应该以正确的比例,正确的1:1像素显示在屏幕上,原因如下:
但有时候我们就需要缩略图,所专门另外存储缩略图这对内存来说。这时就可以通过CALayer的minificationFilter(缩小滤波器)和magnificationFilter(方法滤波器)属性实现图形的缩放算法,其中提供以下三种默认的缩放算法:
对于大图的缩放,采用kCAFilterLinear、kCAFilterTrilinear,效果会比较好。
对于小图、较少斜线的图的缩放,,采用kCAFilterNearest效果比较好。
图一:采用kCAFilterLinear的方法滤波器算法的效果。
图二:采用kCAFilterNearest的放大滤波器算法的效果:
8、透明组
UIView有一个alpha属性类确定视图的透明度,而CALayer有一个与之对应的属性——opacity。这两个属性都会影响到子图层。如下图所示:
当我们将view2的alpha数值设置为0.5,这时候,对于label所处的点的颜色计算公式为:50%label + 25%view + 25%background,所以就是灰色偏白的颜色。
若想整个图层的色调保持一致,可以通过将shouldRasterize属性设置为YES,那么图层及其子图层将被整合成一个整体的图片。效果如下:
图一:shouldRasterize属性设置为默认值NO的效果。
图二:shouldRasterize属性设置为默认值YES的效果。
|
Transforms(变换)
1、仿射变换
UIView对应的transfrom属性是一个CGAffineTransfrom类型,用于实现二维空间旋转、平移、缩放、斜切。
而CALayer的transform属性是一个CATransforms3D,能应用3维空间进行旋转、平移、缩放、斜切等效果。
其中,仿射变化的数学原理如下:
不过iOS为了运算的方便,已经提供了三个标准方法分别用于实现旋转、缩放,平移,而斜切得靠原生运算来实现。
其中有一点需要注意的,就是旋转的angle是以弧度制为单位的。
iOS系统提供以下方法来实现混合变换,能同一以下的组合函数实现复杂的变形模式:
图一:创建一个空的变换结构体。
图二:不断叠加变换结构体
图三:直接将两个变换结构体合并
剪切变换的效果与其实现代码:
2、3D变化
3D变化与2D变化是十分相似的,不同的只是3D变换是3个维度的,添加了Z轴。其数学原理如下:
这种矩阵运算还是挺麻烦的,所以iOS系统提供给我们便捷的方法,:
同样也会有变换叠加的方法,其中旋转的时候一定要注意当前的旋转的参考轴,顺时针旋转为正值,逆时针旋转为负值。
3、透视投影
在真实的世界中,当物体远离我们,由于视角的原因,其会变小,所以为了让3D效果更佳真实,需要引入投影变换,虽然core animation并没有提供给我们,但十分简单,其数学实现如下:
只要将m34 = -1/d, d= 500~1000,(d越小越失真,d越大透视效果越弱)。
图3.1:没实现透视投影的旋转效果。
图3.2:实现了透视投影的旋转效果。
4、灭点
在人的视觉中,当物体离人越远则会变得越小,当接近无穷远的时候就会汇聚成一个不可见的点,这个点就叫灭点。现实生活中,这个点通常就是中心点,所以在程序中我们也要让图形的灭点集中在屏幕的中点,如下所示:
而实现方式则是,首先添加图形的时候先以屏幕中点为锚点添加,然后通过transfrom来讲图层移动到恰当的位置。
5、sublayerTransform
sublayerTransform是一种将所有相应的配置统一设置到该图层的所有子图层的便捷属性。
6、3D坐标下的图层背面
在3D坐标下,图层的背面也是会绘制的,而且是正面的镜像。
我们可以通过CALayer的doubleSided属性来决定是否执行双面绘制。
7、扁平化图层
如果父图层的坐标发生变换,那么子图层也会随着父图层而进行同样的变换,如7.1图所示。但有时我们只想变换父图层并不想变换子图层,这时就得做相对变换来抵消掉父图层的变换,让视觉效果看起来子图层是静止的。如下图所示:
图7.1:2D相对旋转理论图
图7.2:2D相对旋转效果图
图7.3:3D相对旋转理论图
图7.4:3D相对旋转实际效果图
我们可以看出,在2D坐标系下,相对变换是合理的,但在3D坐标系下,相对变换的结果并不如预想的一样,为什么呢?
这是因为core Animation图层虽然存在于3D空间之内,但并不是每一个图层都存在于同一个3D空间之间。而用户是以当前window所在的3D坐标系为参考的,这时7.4图的3D坐标系与window的已经不重合的,所以在我们的视觉里面,就是看不到预想的效果,这就是图层扁平化。
图层扁平化让core animation创建复杂的3D场景变得十分困难,因为很难使用图层树去创建一个3D结构的层级关系——将所有的场景下的3D都保持相同的3D坐标系。
不过CALayer提供一个专用图层——CATransformLayer来解决这个问题,所有添加到该图层内的3D图形都共享一个标准坐标系。
8、3D场景下的光亮和阴影
略,暂时不懂。
9、3D场景下的点击事件
在处理点击事件,一定要注意点击响应是由UIView中的图层结构来决定的,而不是CALayer在屏幕上的呈现效果,所以得注意事件拦截的顺序。
可以通过设置userInteractionEnabled以及简单的视图覆盖来实现点击事件正确的传递。
|
Specialized Layers(专有图层)
1、CAShapeLayer
2、CATextLayer
3、CATransformLayer
4、CAGradientLayer
5、CAReplicatorLayer
6、CAScrollLayer
7、CATiledLayer
8、CAEmitterLayer
9、CAEAGLlayer
10、AVPlayerLayer
|
Core Animation 之 动画
Implicit Animations(隐式动画)
1、隐式动画&事务
Core Animation基于一个假设,就是屏幕上的所有图形都可以做动画,而且这种动画不需要开发者去设置,会自动实现。这也是为什么直接修改CALayer的属性会以一种渐变的形式来更新到一个新的值。
这种只需要修改图层的数值就会引发动画的形式来更新到新数值的动画称为隐式动画。
而这种定义了动画的具体呈现效果的机制,就称为事务。
由于CALayer是默认开启了隐式动画的,而若我们在CALayer上调用显式动画的话,就考虑到隐式动画对效果的影响,进而决定是否需要取消隐式动画。
2、动画Block
UIView封装了CALayer,但UIView禁止了隐性动画,所以开发者需要在UIView上实现显性动画。在iOS4.0之前,UIView采用动画栈来管理显示,后来提供了动画Block这个更加便捷的方式,但原理都是一样的。
3、图层行为
我们把改变CALayer属性时自动应用的动态称为行为,一组行为的执行过程如下所示:
经过以上的整个流程,要么actionForKey返回nil则无动画效果,要么就是返回CAAction,然后CALayer利用它实现动画。
因此可以推动出UIView是如何禁止隐式动画的,就是讲CALayer的delegate设置为自身,然后在actionForLayer:forKey方法中返回nil。
4、呈现与模型
在iOS中,屏幕每秒钟重新刷新屏幕60次。所以Core Animaiton就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。
正是以为上面的机制的存在,所以才会有iOS才会有图层以及呈现层之间的关系,图层更像是model,而呈现层就是view,Core Animation就是Controller,所以在一个绘制周期内,图层负责收集与保存用户对属性的修改,到绘制时刻时,就将图层的数据覆盖到呈现树。
我们可以通过CALayer的 -presentationLayer方法来获取正在屏幕上显示的呈现树的数据,虽然一般不需要,但在以下情况下会比较有作用
|
Explicit Animations(显式动画)
1、Core Animation的类图架构
2、基础属性动画
基础动画主要由CABasicAnimation实现,由上图可知道,而CABasicAnimation继承于CAPropertyAnimation属性动画,通过设置以下三个数值以及其他的选项,即可实现自动动画:
其中toValue与byValue不能同时使用,toVaule是绝对值,byValue是相对值。属性动画都是针对关键帧的动画,也就是说只关心出发点与结束点,中间的过渡可以自己生成也可以默认。
3、关键帧动画
CAKeyFrameAnimation用于实现关键帧动画,能通过设置其animations数组来定义每个关键帧的位置,也可以直接通过path属性来定义运动的路径。
4、affineTransform属性
若我们让一个图形沿着曲线运动,可以通过设置affineTransform,让其能沿着曲线的切线运动从而让运动更加真实。前后效果图如下图所示:
5、虚拟属性
属性动画实际上是针对关键路径而不是一个键的,这就意味着可以对子属性甚至虚拟属性做动画。
如若我们想实现旋转的效果,本来我们需要在keyPath上设置“transform”,
若使用虚拟属性,可以直接在keyPath上直接设置“transfrom.rotation”,这样在设置formValue与toValue时就可以直接设置弧度叫的值。
其实transfrom.rotation这个属性是并不存在的,而是core aniamiton自动根据CAValueFunction来重新计算transform的数值所得,正因为如此才称之为虚拟属性。
6、动画组
CAAnimationGroup是一种组合动画的解决方案。通过CAAnimationGroup添加沿曲线运动与颜色变化的动画。
7、过渡
略。
8、在动画的过程中取消动画
Core Animation提供了一部分的方法来实现动画添加以及移除,如下图所示:
- (CAAnimation *)animationForKey:(NSString *)key; //实现动画的添加,其中key不仅可以用来访问动画,还可以用来移除动画 - (void)removeAnimationForKey:(NSString *)key;
//移除Key指定的动画 - (void)removeAllAnimations;
//移动CALayer已经添加的所有动画 |
Layer Time(图层时间)
1、CAMediaTiming协议
CAMediaTiming协议定义了在一段动画内控制逝去时间的属性集合。CALayer和CAAnimaition都实现了该协议,所以时间可以被任意图层或者一段动画的类所控制。
CAMediaTiming协议下定义的一些核心属性:
可以通过将repeatCount或者repeatDuration设置为INFINITY来实现动画无限循环播放,但不能同时使用这两个属性。
2、相对时间
在Core Animation中,时间是相对的,每个动画都有自己的描述时间,可以独立的加速、延迟或者偏移,其中有以下关键属性:
3、fillMode
当一个图层产生动画,实际上就是呈现层在执行动画,那么,当呈现层执行完动画是否要讲当前属性的值覆盖会图层,这种行为就称为fill mode,这个由开发者决定:
4、全局时间与本地时间
对
CALayer 或者CAGroupAnimation 调整duration 和repeatCount /repeatDuration 属性并不会影响到子动画。但是beginTime ,timeOffset 和speed 属性将会影响到子动画。每个
CALayer 和CAAnimation 实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime ,timeOffset 和speed 属性计算。CoreAnimation有一个全局时间的概念,也就是所谓的马赫时间(“马赫”实际上是iOS和Mac
OS系统内核的命名)。
用
CACurrentMediaTime 函数来访问马赫时间:CFTimeInterval time = CACurrentMediaTime(); 该值返回的是一个相对值,与现实的时间无关,但可以通过它来比对不同动画之间的时间差,iOS提供以下方法来进行不同图层之间本地时间的转化:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
通过以上知识可以实现同步不同图层的speed,timeOffset、beginTime的动画。
5、暂停、倒回和快进的实现
设置动画的
speed 属性为0可以暂停动画,但不能再添加后再修改,否则会报错。但我们可以通过以下的设置来实现一些调试:
self.window.layer.speed = 100; 6、手动动画
将speed设置为0让动画停止播放,然后修改timeOffset来切换不时间的动画序列,从而实现手动切花动画的效果。
|
Easing(缓冲)——让动画更加平滑自然
1、CALayer、Animation的缓冲动画与CAMediaTimingFucntion
首先需要设置
CAAnimation 的timingFunction 属性,是CAMediaTimingFunction 类的一个对象。如果想改变隐式动画的计时函数,同样也可以使用CATransaction 的+setAnimationTimingFunction: 方法。来实现缓冲函数,其中有以下默认的缓冲函数:kCAMediaTimingFunctionLinear //线性
kCAMediaTimingFunctionEaseIn //缓进
kCAMediaTimingFunctionEaseOut //缓出
kCAMediaTimingFunctionEaseInEaseOut //缓进缓出
kCAMediaTimingFunctionDefault
//默认的效果与 kCAMediaTimingFunctionEaseInEaseOut类似,但效果更佳不明显2、UIView的缓冲动画
通过设置UIView的options参数添加如下常量也可以实现缓冲动画的效果:
UIViewAnimationOptionCurveEaseInOut 3、缓冲和关键帧动画
CAKeyframeAnimation 有一个NSArray 类型的timingFunctions 属性,我们可以用它来对每次动画的步骤指定不同的计时函数。但是指定函数的个数一定要等于keyframes 数组的元素个数减一,因为它是描述每一帧之间动画速度的函数。4、自定义缓冲函数
暂时不讨论。
|
Timer-Based Animation(基于定时器的动画)——我们可以通过事务来实现动画,设置关键帧,让中间的过渡自动计算得出,也可以基于定时器来设置每一个绘制周期的变换来实现动画。
iOS提供一下两种形式来实现基于定时器的动画:
但无论是NSTimer还是CADispaly本质上都是基于runloop,NSTImer把每个时间触发的事件注册到runloop里面,以待制定。而CADisplay则把任务直接嵌套进绘制操作之前。
|
Core Animation 之 性能优化
Tuning for Speed(性能调优)
1、CPU & GPU
CPU(中央处理器)和GPU(图形处理器)都是能用来处理。
总的来说,我们可以用软件(使用CPU)做任何事情,但是对于图像处理,通常用硬件会更快,因为GPU使用图像对高度并行浮点运算做了优化。由于某些原因,我们想尽可能把屏幕渲染的工作交给硬件去处理。问题在于GPU并没有无限制处理性能,而且一旦资源用完的话,性能就会开始下降了(即使CPU并没有完全占用)。
性能优化的本质就会合理地利用CPU与GPU,使他们不会超出负荷。
Core Animation处于iOS的核心地位,无论是应用内还是应用外都会用到它。所以iOS特别设计了一个进程来执行渲染相关的任务,也叫渲染服务。渲染服务管理动画和屏幕上组合的图层。
当运行一段动画的时候,这个过程会被切分为六个阶段,包括应用内的四个阶段,与应用外的2个阶段,其中应用内的阶段如下:
当数据被打包到渲染服务进程,会将其反序列化形成另一个渲染树。使用这个渲染树对动画的每一帧做出如下工作:
降低GPU图层绘制的部分场景:
降低CPU图层绘制的部分场景:
2、Instruments实现App性能优化,操作顺序通常如下:
|
Efficient Drawing(高效绘图)
1、软件绘制
软件绘图意为不借助GPU的图形绘制,在iOS中通常就是由Core Graphics来实现,但其对比Core Animation和OpenGL,Core Graphics要慢不少,而且也十分消耗内存。
软件绘图不仅效率低,还会消耗可观的内存。
CALayer 只需要一些与自己相关的内存:只有它的寄宿图会消耗一定的内存空间。即使直接赋给contents 属性一张图片,也不需要增加额外的照片存储大小。如果相同的一张图片被多个图层作为contents 属性,那么他们将会共用同一块内存,而不是复制内存块。但是一旦你实现了
CALayerDelegate 协议中的-drawLayer:inContext: 方法或者UIView 中的-drawRect: 方法(其实就是前者的包装方法),图层就创建了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:图层宽*图层高*4字节,宽高的单位均为像素。对于一个在Retina
iPad上的全屏图层来说,这个内存量就是 2048*1526*4字节,相当于12MB内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配。软件绘图的代价昂贵,除非绝对必要,你应该避免重绘你的视图。提高绘制性能的秘诀就在于尽量避免去绘制。
2、矢量图形
我们用Core Graphics来绘图的一个通常原因就是只是用图片或是图层效果不能轻易地绘制出矢量图形。矢量绘图包含一下这些:
文章中实现了如下效果的demo:
其实现思路就是每当有touch event发生,则向UIBezierPath中添加一条直线。但在对于屏幕中每当有一个touch event事件发生,意味着整个屏幕都要重新绘制一遍,当需要重新绘制的内容越来越多,则会引起帧率的下降。这时候我们可以通过脏矩阵来对这个问题进行优化。
3、脏矩形
脏矩形是一个能制定重新绘制区域的机制。在程序中则是通过用setNeedDispalyInRect来替代setNeedDispaly方法来实现绘制具体的区域,具体实现见文章。
4、异步绘制
UIKit的单线程天性意味着寄宿图通常要在主线程上更新,这意味着绘制会打断用户交互,甚至让整个app看起来处于无响应状态。我们对此无能为力,但是如果能避免用户等待绘制完成就好多了。
针对这个问题,core Animation提供了一下两种方案来实现异步绘制,提高界面的响应效率:
|
Image IO(图像IO)——研究如何优化从闪存或者网络中加载和显示图片
1、加载与潜伏
绘图实际消耗的时间通常并不是影响性能的因素,而且若把图片直接存储在内存,会损耗大量的资源,所以需要在应用运行的时候周期性地加载和卸载图片。
图片文件的加载速度同时受到CPU及IO(输入/输出)延迟的影响。iOS设备中的闪存已经比传统硬盘快很多了,但仍然比RAM慢将近200倍左右,这就需要谨慎地管理加载,以避免延迟。
有时候图片也需要从远程网络连接中下载,这将会比从磁盘加载要消耗更多的时间,甚至可能由于连接问题而加载失败(在几秒钟尝试之后)。你不能在主线程中加载网络,并在屏幕冻结期间期望用户去等待它,所以需要后台线程。
2、异步线程加载
由于图片的加载是十分耗时间的,若把该操作放置在主线程中执行会降低CPU的利用率甚至拖慢帧率。可以通过GCD或者NSOperationQueue来实现异步加载最后再在主线程中同步发起渲染即可。
3、延迟解压
一旦图片文件被加载使用,就必须要经过解码(解压)的过程,解码过程是一个相当复杂的任务,耗时长也占大量的内存。
对于PNG:加载相对长,文件相对更大,但解码比较快。
对于JPEG:加载快,图片小,但解码算法复杂耗时长。
由于iOS系统会让加载完成的图片不会立即解压,而是到需要用的时刻才正式解压,所以会影响性能。
4、使用CATiledLayer实现异步加载和显示大型图片
略。
5、分辨率交换
略。
6、使用imageNamed实现缓存
imageName方法是能避免延迟加载,而且该方法在加载后会立即解压,但只对应用资源束有效。所以网络图片无效。
之前我们提到使用
[UIImage imageNamed:] 加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是[UIImage
imageNamed:] 方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。所以也要注意不能用于加载大图片,不然会占用大量的内存资源。
7、自定义缓存
8、NSCache
NSCache和NSDictionary类似,都是直接通过键值进行访问,但不同的是,NSCache所持有的对象在内存不足的时候,会自动将其释放。
9、文件格式与加载性能
略。
|
Layer Performance(图层性能)
1、隐形绘制
2、文本
CATextLayer与UILable都是直接将文本绘制在图层的寄宿层内,所以要避免频繁的改动,若该文本需要频繁改动,可以先将其放在一个子图层上,通过contentMode来等比例缩放寄宿层。
3、光栅化
我们提到了
CALayer 的shouldRasterize 属性(光栅化),它可以解决重叠透明图层的混合失灵问题。启用
shouldRasterize 属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents 和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。当我们使用得当时,光栅化可以提供很大的性能优势(如你在第12章所见),但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。
为了检测你是否正确地使用了光栅化方式,用Instrument查看一下Color Hits Green和Misses Red项目,是否已光栅化图像被频繁地刷新(这样就说明图层并不是光栅化的好选择,或则你无意间触发了不必要的改变导致了重绘行为)。
总结:会占用较多内存,要避免重复绘制,所以一旦应用要尽量减少重新绘制,而多利用快照缓存。
4、离屏渲染
屏幕外渲染和我们启用光栅化时相似,除了它并没有像光栅化图层那么消耗大,子图层并没有被影响到,而且结果也没有被缓存,所以不会有长期的内存占用。但是,如果太多图层在屏幕外渲染依然会影响到性能
对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用
CAShapeLayer ,contentsCenter 或者shadowPath 来获得同样的表现而且较少地影响到性能。5、混合和过度绘制
开发者应该尽量减少重叠图层的重复渲染,因为对于用户看来某些遮挡的图层的内容是无关紧要的,但渲染就会消耗资源,所以无论任何场景都建议如下操作:
当然光栅化是是具体场景二选择使用,不然也会引入别的性能问题,如占用大量内存。
6、减少图层数量
初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销。事实上,一次性能够在屏幕上显示的最大图层数量也是有限的。
确切的限制数量取决于iOS设备,图层类型,图层内容和属性等。但是总得说来可以容纳上百或上千个,下面我们将演示即使图层本身并没有做什么也会遇到的性能问题。
7、对象回收
处理巨大数量的相似视图或图层时还有一个技巧就是回收他们。对象回收在iOS颇为常见;
UITableView 和UICollectionView 都有用到,MKMapView 中的动画pin码也有用到,还有其他很多例子。 |
Q&A:
- Q:图层没有寄宿层,是否等于无法显示内容?(寄宿层)
-
- A:不是,还有通过矢量图绘制的方式来实现图形呈现,如通用的现在应该就是通过算来来绘制矢量图的,只有需要显示图片或者调用core graphics的时候才会生成寄宿层,所以若非必要不要实现draw方法,不然系统会默认添加一个寄宿层。
- Q:组透明的点颜色计算公式?(视觉效果)
-
- A:若某子view是透明的,那么它的颜色公式为:child_color*alpha + super_color*(1-alpha)。
- Q:3D效果与共享灭点的具体操作?(变换)
-
- A:在做3D效果时要尽量将所有图形的灭点设置在屏幕的中心,所以建议先在屏幕的中心创建图像,然后通过transform的形式来移动图层,那么就能保证灭点在屏幕中心。
- Q:3D变化计算?(变换)
-
- A:提供了平移、旋转、缩放,但每次操作都是基于二维的,所以注意当前的参考坐标系,剩下的计算与二维一样。
- Q:立方体的光亮与阴影原理?(变换)
-
- A:通过GLK库的函数计算垂直,并添加阴影层。
- Q:3D点击事件处理?(变换)
-
- A:在3D图层中,因为一个点击的点,实际上可能会穿过两个图层的点,这时事件拦截的顺序由subviews数组的顺序觉得,也就是谁最靠近用户视觉,谁有限响应。
- Q:常规的界面绘制是通过矢量绘制还是寄宿图绘制实现?(专用图层)
-
- A:常规界面是矢量绘制完成的。
- Q:平面化3d层级结构的意思?(专用图层)
-
- A:
- Q:CATiledLayer解决大图加载(专用图层)
-
- A:
参考文章:
《iOS Core Animation Advance Techniques》书籍
以上是关于Core Animation学习总结的主要内容,如果未能解决你的问题,请参考以下文章
Instruments性能优化-Core Animation