iOS CoreAnimation(核心动画二)寄宿图:layer图层中包含的图
contents属性
CALayer 有一个属性叫做
contents
,这个属性的类型被定义为id,意味着它可以是任何类型的对象。可以给contents
属性赋任何值,你的app仍然能够编译通过;但是,在实践中,如果你给
contents
赋的不是CGImage,那么你得到的图层将是空白的
思考:为什么contents 属性的类型被定义为id?
由Mac OS的历史原因造成的。它之所以被定义为id类型,是因为在Mac OS系统上,这个属性对CGImage和NSImage类型的值都起作用。如果你试图在iOS平台上将UIImage的值赋给它,只能得到一个空白的图层。
- contents属性的实际赋值
事实上,你真正要赋值的类型应该是CGImageRef,它是一个指向CGImage结构的指针
UIImage有一个CGImage属性,它返回一个”CGImageRef”,如果你想把这个值直接赋值给CALayer的
contents
,那你将会得到一个编译错误。因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型尽管Core Foundation类型跟Cocoa对象在运行时貌似很像(被称作toll-free bridging),他们并不是类型兼容的,不过你可以通过bridged关键字转换
给图层的寄宿图赋值的写法:
1 | layer.contents = (__bridge id)image.CGImage; //使用ARC(自动引用计数) |
contentGravity:
在使用UIImageView的时候遇到图片拉伸问题,解决方法就是把
contentMode
属性设置成更合适的值CALayer与
contentMode
对应的属性叫做contentsGravity
,用来处理寄宿图的拉伸适配;和
cotentMode
一样,contentsGravity
的目的是为了决定内容在图层的边界中怎么对齐
具体的设置参数包括:
- kCAGravityCenter :中心显示
- kCAGravityTop:上方显示
- kCAGravityBottom:下方显示
- kCAGravityLeft:左边显示
- kCAGravityRight:右边显示
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
contentsScale
contentsScale
属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数;UIView有一个类似功能但是非常少用到的contentScaleFactor
属性- contentsScale并不是总会对屏幕上的寄宿图有影响,若contents
由于设置了
contentsGravity`属性,则它已经被拉伸以适应图层的边界,contentsScale不会起作用(contentsScale与contentsGravity冲突);contentsScale
属性其实属于支持高分辨率(又称Hi-DPI或Retina)屏幕机制的一部分:用来判断在绘制图层的时候应该为寄宿图创建的空间大小,和需要显示的图片的拉伸度(假设并没有设置contentsGravity
属性)- 如果
contentsScale
设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕.- 当用代码的方式来处理寄宿图的时候,一定要记住要手动的设置图层的
contentsScale
属性,否则,你的图片在Retina设备上就显示得不正确
1 | layer.contentsScale = [UIScreen mainScreen].scale; |
maskToBounds
认情况下,UIView仍然会绘制超过边界的内容或是子视图,在CALayer下也是这样的
UIView有一个叫做
clipsToBounds
的属性可以用来决定是否显示超出边界的内容CALayer对应的属性叫做
masksToBounds
(边缘遮挡),把它设置为YES
contentsRect
CALayer的
contentsRect
属性允许我们在图层边框里显示寄宿图的一个子域和
bounds
,frame
不同,contentsRect
不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的
ios 中使用到的坐标系统:
点——在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素;
在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素;
iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果
像素——物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。
单位—— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。
应用:类似于精灵图技术的图片拼合
1 | layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5) |
contentsCenter
contentsCenter
其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域(9切片技术)默认情况下,
contentsCenter
是{0, 0, 1, 1},这意味着如果大小(由conttensGravity
决定)改变了,那么寄宿图将会均匀地拉伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造一个边框这意味着我们可以随意重设尺寸,边框仍然会是连续的。他工作起来的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常类似,只是它可以运用到任何寄宿图,甚至包括在Core Graphics运行时绘制的图形
利用Core Graphics设置寄宿图
- 给
contents
赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图- 能够通过继承UIView并实现
-drawRect:
方法来自定义绘制-drawRect:
方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例- 如果UIView检测到
-drawRect:
方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以contentsScale
的值- 如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法
- 当视图在屏幕上出现的时候
-drawRect:
方法就会被自动调用-drawRect:
方法里面的代码 利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新(通常是因为开发者调用了-setNeedsDisplay
方法-drawRect:
方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片
CALayer的delegate
属性
delegate
属性:实现了CALayerDelegate
协议,当CALayer需要一个内容特定的信息时,就会从协议中请求CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的
delegate
属性被声明为id类型,所有的代理方法都是可选的
当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示
1
2(void)displayLayer:(CALayerCALayer *)layer;
// 趁着这个机会,如果代理想直接设置contents属性的话,它就可以这么做,不然没有别的方法可以调用了如果代理不实现
-displayLayer:
方法,CALayer就会转而尝试调用下面这个方法:
1 | - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; |
- 使用样例:实现CALayerDelegate
1 | @implementation ViewController |
- 我们在blueLayer上显式地调用了
-display
。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。- 尽管我们没有用
masksToBounds
属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。- 理解了CALayerDelegate,并知道怎么使用它。但是除非你创建了一个单独的图层,你几乎没有机会用到CALayerDelegate协议
当使用寄宿了视图的图层的时候,你也不必实现-displayLayer:
和-drawLayer:inContext:
方法来绘制你的寄宿图。通常做法是实现UIView的-drawRect:
方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用-display
方法