周老师的QuartzCore教程 - 神奇的CALayer

神奇的CALayer

CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互

Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在。所以,CALayerUIView还有一个更大的区别在于,当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作,这就是隐式动画。

目录:

contents属性

contents

contentsScale

contentsRect

contentsCenter

视觉效果

蒙板

圆角

maskedCorners属性

UIRectCorner与贝塞尔曲线

高性能渲染圆型图标

阴影

shadowPath属性

拉伸过滤

contents属性

contents

contents属性是一个id属性,因为在MacOS上UIImageCGImage同样对对其生效

但是在iOS上,只有CGImage生效,赋值其他对象,只会得到一个空白的图层

layer.contents = (__bridge id)image.CGImage;

然后你就会的到下图所示:

1.1.png

可见这个雪人是有点胖了,你此时脑海中会立马浮现出一个似曾相识的属性

view.contentMode = UIViewContentModeScaleAspectFit;

是的没错,很接近了。不过在CALayer上是这样

layer.contentsGravity = kCAGravityResizeAspect;

好的雪人的身材恢复了:

1.2.png

layer.contentsGravityview.contentMode对应,但是是一个NSString类型,可选的常量有:

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill

contentsScale

contentsScale定义了寄宿图的像素尺寸和视图大小比例,默认为1.0的浮点数

contentsScale是用于支持高分屏机制的一部分[当然现在几乎所有设备都是高分屏了]

如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片

这个属性可以用来做CALayer的拉伸,但是通常不这么做[而是使用transformaffineTransform],如果如果我们把contentsGravity设置为kCAGravityCenter,这个属性不会拉伸图片:

1.3.png

如图所示,雪人很大,像素点也很粗。这是因为使用UIImage类去读取的图片是Retina屏的图片,而转换CGImage赋值的时候,这个因素就丢失了。

所以我们可能需要手动修改这个问题:

UIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layer
self.layerView.layer.contents = (__bridge id)image.CGImage; //center the image
self.layerView.layer.contentsGravity = kCAGravityCenter;

self.layerView.layer.contentsScale = image.scale;//此处也可以选择[UIScreen mainScreen].scale
1.4.png

好了我们的雪人变的正常了

划重点:

请记住contentsScale,否则在你发现你的CALayer相关的视图像素点变得很粗的时候你不一定想得到回来翻这一截节

contentsRect

CALayer的contentsRect属性允许我们在图层边框里显示寄宿图的一个子域。

contentsRect使用了单位坐标,单位坐标是指定在0-1之间的浮点数,表示相对于寄宿图的尺寸比例

周老师科普课堂

iOS有以下三种坐标系:

    • 最常见的坐标系,点代表了逻辑像素(一个点在retina屏幕上是2*2个像素),点坐标使高分屏和低分屏有统一视觉效果
  1. 像素
    • 物理像素坐标通常不会用在屏幕布局,UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素。
  2. 单位
    • 以比例作为基本单位,对于图片大小或是图层边界相关的显示非常方便的度量方式,当大小改变时不需要再次调整。单位坐标多见于OpenGL这种纹理坐标系统,Core Animation中也用到了单位坐标

contentsRect默认为{0, 0, 1, 1},表示以左上角{0,0}为基准点,显示宽高为寄宿图宽高的1倍。如果我们指定为小一点的矩形,图片会被裁剪:

1.5.png
通过contentsRect实现图片的拼接

contentsRect在APP中最有趣的地方在于一个叫做image sprites(图片拼合)的用法

图片拼合后可以打包整合到一张大图上一次性载入。相比多次载入不同的图片,这样做有时能够带来很多性能上的优化(单张大图比多张小图载入地更快)

好的我们这次放过可爱的雪人,把我们敬爱的杨总抬上来!

1.6.png

经过一番操作我们要实现这张杨总的图片切割

- (void)SpriteDemo {
    
    UIImage *hq = [UIImage imageNamed:@"hq.jpg"];
    [self addSpriteImage:hq contentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.view1.layer];
    [self addSpriteImage:hq contentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.view2.layer];
    [self addSpriteImage:hq contentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.view3.layer];
    [self addSpriteImage:hq contentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.view4.layer];
}

- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer {

    layer.contents = (__bridge id)image.CGImage;
    layer.contentsGravity = kCAGravityResizeAspect;
    layer.contentsRect = rect;
}

之后我们完成了对这张图片的切割工作

1.7.png

这大图切小图的神技今天就讲到这里,欲知多张小图如何拼接成大图?请看下期专业图层之CATiledLayer

contentsCenter

这个属性,看名字好像和位置有关系,然并不,这货是一个CGRect。

contentsCenter定义了一个固定边框和一个在图层上可拉伸的区域。

这个属性用文字实在是太难说清楚了。。。好在下图可以很好的表达这个东西是干什么的:
1.8.png

contentsCenter默认为{0,0,1,1},表示Layer大小改变,寄宿图均匀伸展。如果我们增加原点并减小尺寸,则会在图片的创造一个边框。

非常常见的一个应用场景就是IM的消息气泡。

气泡资源是这样的:

1.9.png

设置contentsCenterww{0.5,0.5,0,0},一套操作之后变成了这样:

1.10.png

[我觉得你们应该都知道但是我还是觉得这个好神奇啊非要拉出来讲一讲不可]

视觉效果

蒙板

众所周知UIView有一个clipsToBounds属性,设置后会隐藏超出自身范围的subView。

对应的,CALayer有一个相同作用的属性masksToBounds,效果与clipsToBounds相同,可以由父视图约束子视图的显示范围。

这个时候就有人会问了,周老师我想让子图层作为蒙板控制父视图的显示范围要怎么操作呢:

mask属性

mask:蒙板属性,[用过PhotoShop的同学们此处高呼:似里!]。

属性本身是一个CALayer类型对象,类似于一个子视图,但是不做绘制工作,而是定义了父图层的可见区域。

mask图层的颜色是无关紧要的,真正重要的是图层的轮廓。带有mask的图层会保留mask有颜色的部分,没有透明的部分会被抛弃:

2.1.png

CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.imageView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
maskLayer.contents = (__bridge id)maskImage.CGImage;

self.imageView.layer.mask = maskLayer;

CALayer蒙板图层真正厉害的地方在于蒙板图不局限于静态图。任何有图层构成的都可以作为mask属性,这意味着你的蒙板可以通过代码甚至是动画实时生成。

圆角

啊圆角还要讲?不就是conerRadius吗?周老师你怕不是在这里骗流量噢?

2.2.jpg

但是你知不知道回字有四。。。。

今天就来讲讲圆角的字的几种特殊写法

maskedCorners属性

小明同学今天开开心心吃着火锅唱着歌,突然产品打电话过来:”歪?小明?是我!那天那个图标,能不能做的飞扬一点”

2.3.png

肉眼可见,这个图标只有两个圆角。

小明哭着对我说:我真傻真的,我单知道一个图标可以做四个圆角,没想到策划居然会要我做两个

周通禅师指着屏幕:你看这是什么?

maskedCorners属性,可控制圆角效果出现在矩形的指定角上

typedef NS_OPTIONS (NSUInteger, CACornerMask)
{
  kCALayerMinXMinYCorner = 1U << 0,
  kCALayerMaxXMinYCorner = 1U << 1,
  kCALayerMinXMaxYCorner = 1U << 2,
  kCALayerMaxXMaxYCorner = 1U << 3,
};

周通禅师一番变化

view.layer.cornerRadius = 50;
view.layer.maskedCorners = kCALayerMaxXMinYCorner | kCALayerMinXMaxYCorner;

登时立刻出现了小明同学想要的效果,小明欢欢喜喜的回家去了

UIRectCorner与贝塞尔曲线

第二天小明又来了:

“周通禅师,你这个属性只适配iOS11以上,用不了咋整啊”

周通禅师微微一笑:没事,你忘了我们刚刚学过的蒙板大法了吗?

//CAShapeLayer是一个通过矢量图形绘制的图层子类,此后专用图层会详细解说
CAShapeLayer *layer = [CAShapeLayer layer];
CGRect rect = view.bounds;
//设置半径为50 ————————  个人实验发现这个radii属性仅.x生效表示圆角半径,.y无效,若有偏差还望指正
CGSize radii = CGSizeMake(50, 50);
//圆角对右上角和左上角生效
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomLeft;
//创建圆角矩形曲线
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
//以曲线绘制CAShapeLayer
layer.path = path.CGPath;
//将CAShapeLayer设置为蒙板
self.view2.layer.mask = layer;

代码量变得很多,但是适配问题解决了,小明又欢欢喜喜的回家去了

高性能渲染圆型图标

众所周知,cornerRadius操作和mask操作都是会涉及比较消耗性能的离屏渲染的操作(大家都这么说),偶尔来一发还好,如果有数量较大的离屏渲染操作,可能会造成程序的卡顿。例如我们的好兄弟最近聊天列表,带有大量圆角图标(头像,用户信息配置化图标),如果全都采用离屏渲染的操作,势必带来体验上的落差。

此处解决方案是:通过赛贝尔曲线绘制一个圆角矩形(或者直接是一个圆),通过UIImagedrawInRect:方法将图标的内容直接绘制到指定区域中形成一个自带圆角的UIImage对象

//此处直接参考MUIAvatar中相应代码
@implementation UIImage (CycleImage)
- (UIImage*)imageWithCornerRadius:(CGFloat)radius{
    CGFloat w = self.size.width;
    CGFloat h = self.size.height;
    CGFloat scale = [UIScreen mainScreen].scale;
    
    UIImage *image = nil;
    CGRect imageFrame = CGRectMake(0, 0, w, h);
    UIGraphicsBeginImageContextWithOptions(self.size, NO, scale);
    //生成对应贝塞尔曲线
    [[UIBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:radius] addClip];
    //将Image内容写入上下文
    [self drawInRect:imageFrame];
    //从上下文中取出结果
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

阴影

CALayer的阴影可以说是相当的简单,只要将shadowOpacity设置为一个大于0的值,阴影就会出现在图层之下。

shadowOpacity:阴影透明度,必须设置在0(不可见)和1.0(完全不透明)之间的浮点数

2.4.png

若要改变阴影的表现,你可以使用另外三个属性

shadowOffset:设置一个阴影的偏移量

2.5.png

shadowColor:设置阴影的颜色

2.6.png

shadowRadius:设置阴影的圆角,(但是同时会增加阴影的模糊程度)

2.7.png
//上图对应代码为
layer.shadowOpacity = 0.5;
layer.shadowOffset = CGSizeMake(100, 100);
layer.shadowColor = [UIColor blueColor].CGColor;
layer.shadowRadius = 50;

阴影与图层边框不同,图层的阴影继承自内容的外形而不是边界和角半径

2.8.png
shadowPath属性

shadowPath是独立于图层外用来指定阴影形状的属性,是一个CGPathRef类型(一个指向CGPath的指针)

当寄宿图内容过于复杂、图层有多个字图层、寄宿图甚至带有透明通道时,性能的消耗是非常巨大的。所以你事先知道阴影的形状或者希望指定阴影的形状,可以使用shadowPath来提高性能。

//创建一个矩形阴影
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
view1.layer.shadowPath = squarePath; 
CGPathRelease(squarePath);
//创建一个圆形阴影
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
view2.layer.shadowPath = circlePath; 
CGPathRelease(circlePath);
//其实不一定要用CGPath这么骚的东西,用贝塞尔曲线的path.CGPath也是可以的
2.9.png

拉伸过滤

通常情况下,视图显示一个图片的时候,我们希望它能够以1:1的比例显示图片。如此能够显示最好的画质,并且最大化利用性能。

但是总有天不如人愿的时候,比如你要显示一个头像或者缩略图。这种情况下再使用原图恐怕是要被人当成是[和谐词]了。

当图片需要显示不同的大小的时候,有一种叫做拉伸过滤的算法就起到作用了。它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。

minificationFiltermagnificationFilter属性:缩小图片和放大图片过滤器

CALayer为过滤器提供了三种方式:

kCAFilterLinear(默认过滤器)

kCAFilterNearest

kCAFilterTrilinear

kCAFilterLinear:过滤器采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。

kCAFilterTrilinear:与kCAFilterLinear非常相似,大部分情况下二者都看不出来有什么差别。但是,较双线性滤波算法而言,三线性滤波算法存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而得到最后的结果。

这个方法的好处在于算法能够从一系列已经接近于最终大小的图片中得到想要的结果,也就是说不要对很多像素同步取样。这不仅提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题。

2.10.png

对于大图来说,双线性滤波和三线性滤波表现得更出色

kCAFilterNearest:一种比较武断的方法。这个算法(也叫最近过滤)就是取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。

2.11.png

对于没有斜线的小图来说,最近过滤算法要好很多

总而言之,线性过滤保留了形状,最近过滤则保留了像素的差异

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容