本文是要点型笔记,建议先阅读以下参考内容后再阅读,完成知识体系的构建:
[1] View Programming Guide for iOS
[2] Core Animation Programming Guide
[3] ios核心动画高级技巧
[4] 通过实现一个TableView来理解IOS UI编程
[5] 详解 CALayer 和 UIView 的区别和联系
[6] iOS绘图教程
[7] Quartz 2D编程指南之一~十三 - 南峰子
LayerKit => Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,可视内容(图片、文本、背景色、…)被分解成独立的图层,存储在一个叫做【图层树】的体系之中。
图层可以通过【寄宿图】来提供可视内容,通过【图层几何学/布局特性】锁定摆放的位置,可以加一些基础【视觉效果】(圆角、边框、阴影、蒙版、拉伸、透明…)或高级视觉效果-【变换】(仿射、3D);除了图片和背景纯色之外,CA还提供了一系列【专有图层】对绘制能力进行了扩展,针对特殊场景进行了封装和性能优化。
注:优先级-displayLayer: > -drawInContext: > -drawLayer:inContext: > -drawRect:
附 UIKit & Quarts 2D:
第一章:图层的树状结构
1. 四个平行的层级关系
视图树 —— UIView,着重于交互-响应事件(触摸/手势…),作为CALayer的管理者
模型树/逻辑树(目标帧) —— CALayer - modelLayer(),不涉及响应链(通过视图层级关系传递触摸事件的机制)
呈现树/动画树(当前帧) —— presentationLayer(), 动画过程中的当前值,详见第七章
渲染树/显示树(下一帧) —— 图层和动画打包提交到渲染服务后反序列化所得树,被用于生成gl三角形s,详见第十二章
2. CALayer和UIView的区别和联系
- 两者通过UIView的
layerClass类方法
和layer属性
联系在一起 - 属性封装 - Layer 的 frame 是由 anchorPoint,position,bounds和 transform 共同决定的,而View 只是简单的返回 Layer的 frame(bounds和center/position类似)
- 分工 - UIView侧重对显示内容的管理,而CALayer侧重显示内容的绘制(drawXxx())和显示(display())
- 事件响应 - UIResponder决定了UIView能接受并响应事件,而CALayer不可以(即使有Hit Testing方法)
-
隐式动画 - 非根CALayer 支持隐式动画,而UIView 作为 Layer 的默认代理,通过在actionForLayer:forKey:返回nil默认禁止了隐式动画(在animation block中会自动重新启用,本质:
[CATransaction setDisableActions:NO];
) -
视图未暴露的CALayer的功能:
- 锚点、阴影,圆角,边框、透明遮罩
- 非矩形范围(mask or shadowPath???)
- 3D变换(
UIView的transform;)CALayer的transform和affineTransform - 多级非线性动画(UIView的默认显式动画都是线性动画)
3. CALayer和UIView的选择
UIView的适用场景
- 优先使用,原因:自动排版/布局和事件处理
CALayer的适用场景:
可以在Mac OS上运行的跨平台应用- 使用多种CALayer的子类(见第六章),并且不想创建额外的UIView去包封装它们
- 对性能要求特别高 => 考虑用CALayer或OpenGL绘图
第二章:设置寄宿图 - 给图层提供内容
第三章:图层几何学
- 布局属性 - 在图层级别无法根据兄弟图层控制位置和尺寸
- frame(外 - 父图层坐标系)
- bounds(内 - 自身坐标系)
- anchorPoint - 锚点在自身坐标系中的(x,y) - 最佳实例:时钟应用中的指针转动
- center(UIView)/position(CALayer) - ️锚点在父图层坐标系中的(x,y)
第四章:视觉效果
- 圆角 - conrnerRadius
- 只影响背景颜色
而不影响背景图片或是子图层 - 通常结合masksToBounds使用,来裁剪子图层和背景图片
- 只影响背景颜色
- 图层边框 - borderWidth和borderColor
- 边框是绘制在图层边界里面的,而且在所有子内容之前,也在子图层之前。
- 实现边框不遮挡内容的两种做法
- 使用带边框效果的寄宿图
- 让边框和内容解除父子关系,并让内容在图层树中靠前
-
阴影 -
shadowOpacity、shadowColor、shadowOffset(方向和距离)和shadowRadius(模糊度)- 与边框不同
- 图层的阴影继承自内容(包含寄宿图和子视图)的外形,而不是根据边界和角半径来确定。
- 阴影可能在图层边界之外;会被masksToBounds裁剪掉
- 解决方案:使用两个图层(一个只画阴影的空的外图层和一个用masksToBounds裁剪内容的内图层)
- shadowPath - 提高性能,CGPathRef类型
- CGPathCreateMutable+CGPathMoveToPoint+CGPathAddXxx
- UIBezierPath类 + addXxx方法
- 与边框不同
-
图层蒙板
- mask - 类似于一个子图层,但它定义的是父图层的部分可见区域
- 比conrnerRadius+masksToBounds更灵活:蒙板图不局限于静态图,以通过编码甚至动画的方式实时生成,而且让子图层或子视图裁剪成同样的形状
拉伸过滤
- 较小的图或者是差异特别明显、极少斜线的大图,Nearest会保留这种差异明显的特质以呈现更好的结果
- 对于大多数的图尤其是有很多斜线或曲线轮廓的图片,选用kCAFilterLinear或kCAFilterTrilinear
- 组透明
- alpha(UIView)、opacity(CALayer)都会影响子层级的
- 组透明整体实现方案(解决上述透明度混合造成显示不一致的问题)
- UIViewGroupOpacity -> YES(Info.plist)
- allowsGroupOpacity(IOS7+可用,相当于UIViewGroupOpacity)
- shouldRasterize+rasterizationScale(配置屏幕),如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片
- alpha(UIView)、opacity(CALayer)都会影响子层级的
第五章:变换
- CGAffineTransform
- “仿射”:图层中平行的两条线在变换之后仍然保持平行
- 混合变换 - 变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋转结果可能不同
- CATransform3D
- 透视投影 - m34 = -1.0/d,d代表了假想中视角相机和屏幕之间的距离,通常设置为500~1000
- 灭点 - anchorPointZ+anchorPoint,旋转和缩放的不变点 => sublayerTransform
- 背面 - doubleSided属性,区别:doubleSided不影响事件响应,设置hidden或alpha为0不会响应事件
- 固体对象
- 光亮和阴影 - GLKMatrix3Xxx和GLKVector3Xxx方法
- 如何让图层顺序靠前(响应链中靠后)的图层得到事件响应
- bringSubviewToFront、sendSubviewToBack、exchangeSubviewAtIndex(_:withSubviewAtIndex:)等方法可以同时改变显示顺序和图层顺序(响应链优先级)
- 通过将优先级更高的UIView的userInteractionEnabled设置为NO
第六章:专用图层 +(Class)layerClass
-
CAShapeLayer - 通过矢量图形而不是bitmap来绘制寄宿图的图层子类
- 相比在CALayer中绘制路径而言优势如下:
- 渲染快速(硬件加速)
- 高效使用内存(不需要寄宿图)
- 不会被图层边界剪裁掉(可以在边界之外绘制)
- 不会出现像素化(因为是矢量图形)
- 属性
- fillColor、strokeColor、lineWidth(单位:点)、lineCap、lineJoin
- path - UIBezierPath.CGPath
- 圆角 - 相比使用cornerRadius更灵活
- 相比在CALayer中绘制路径而言优势如下:
-
CATextLayer - 使用了Core text,渲染非常快
- 属性
- string - (富文本使用NSAttributedString)
- font 和 fontSize、
wrapped(自动换行)、truncationMode(截断模式)、alignmentMode
- 文本像素化
- 属性
textLayer.contentsScale = UIScreen.mainScreen().scale
- UILabel替代品 - 用CATextLayer作为宿主图层的UILabel子类,可以随着视图自动调整大小而且也没有冗余的寄宿图
-
- 不能显示自己的内容,只有当存在了一个能作用于子图层的变换它才真正存在,而且变换将对所有子图层的3D场景生效而不是扁平化后的子图层。
- CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构
-
CAGradientLayer
- 用来生成两种或更多颜色平滑渐变,绘制使用了硬件加速
- 基础渐变
- startPoint和endPoint
- colors(CGColorRef数组)
- 多重渐变
- locations属性
- 以NSNumber包装的浮点数值数组,定义了colors属性中每个不同颜色的位置(以单位坐标系标定)
- 确保数组大小和colors数组大小一定要相同
-
- 高效生成许多相似的图层,并在每个复制体上应用不同的变换。
- 属性
- instanceCount
- instanceTransform
- instanceRed/Green/BlueOffset、instanceAlphaOffset
- ……
- 反射 - ReflectionView
-
- -scrollToPoint:方法,自动适应bounds的原点以便图层内容出现在滑动的地方
并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。- UIScrollView并没有用CAScrollLayer,事实上,就是简单的通过直接操作图层边界来实现滑动。
var visibleRect: CGRect { get }
func scrollPoint(_ p: CGPoint) // 从图层树中查找并找到第一个可用的CAScrollLayer,然后滑动它使得指定点成为可视的
func scrollToRect(_ r: CGRect) // 类似,不过是使指定矩形可视
-
CATiledLayer
- OpenGL有一个最大的纹理尺寸(通常是20482048或40964096,这个取决于设备型号),如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,你仍然会遇到很大的性能问题。所以需要使用类似CATiledLayer的技术
-(void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx //在此根据需要加载不同的tile
class func fadeDuration() -> CFTimeInterval
tileLayer.contentsScale = [UIScreen mainScreen].scale;
CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。
- 一个高性能的粒子引擎,被用来创建实时粒子动画如:烟雾,火,雨等等这些效果。
- 自身属性
+ emitterCells
+ preservesDepth
+ renderMode
- kCAEmitterLayerAdditive是叠加效果,其他是覆盖效果
- [CAEmitterCell](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CAEmitterCell_class/)的属性
+ contents、color
+ birthRate
+ lifetime 、lifetimeRange - 类似velocity
+ alphaSpeed
+ velocity、velocityRange - [velocity~(velocity+velocityRange)]定义了速度变化范围
+ emissionLatitude(z轴)、emissionLongitude(xy平面)、emissionRange(环绕发射角度的一个圆锥范围)
-
CAEAGLLayer
- OpenGL没有对象或是图层的继承概念,它的全部工作就是处理3D空间中有颜色和纹理的三角形
- GLKView(GLKit框架) + CAEAGLLayer(QuartzCore)
- 重要元素
class func setCurrentContext(_ context: EAGLContext!) -> Bool
func renderbufferStorage(_ target: Int, fromDrawable drawable: EAGLDrawable!) -> Bool
func presentRenderbuffer(_ target: Int) -> Bool
+ CAEAGLLayer的[drawableProperties属性](https://developer.apple.com/library/ios/documentation/iPhone/Reference/EAGLDrawable_Ref/index.html#//apple_ref/occ/intfp/EAGLDrawable/drawableProperties)(EAGLDrawable协议)
+ [GLKBaseEffect](https://developer.apple.com/library/ios/documentation/GLkit/Reference/GLKBaseEffect_ClassRef/) - 着色逻辑:func prepareToDraw()
+ glXxx系列方法(setupBuffers & drawFrame)
-
AVPlayerLayer
- 由AVFoundation框架提供 (不同于其他CALayer由Core Animation框架提供)
- 播放控制和展现分离:AVPlayer和AVPlayerLayer
- AVPlayer - play()、pause()、rate等
-
AVPlayerLayer - player属性和init(player player: AVPlayer)
- 继承了父类CALayer的所有特性,不会受限于要在一个矩形中播放视频;
- 可实现3D、圆角、有色边框、蒙板、阴影等效果