UIView-阴影(shadow)

在本文中,你将了解到如下内容:

  1. iOS中对shadow的简单使用
  2. shadow相关属性的解析
  3. shadow与图层混合和离屏渲染的关系

前言

在我们日常的开发过程中,为视图添加阴影是一个很常见的场景。本篇文章我们将详细了解layershadow相关的属性,以及添加阴影导致性能下降的一些简单解析。在了解了这些东西以后,我们将在使用阴影时得心应手(小激动)。

离屏渲染和图层混合

在开始了解shadow相关的属性之前,我们先对shadow对性能造成影响的原因进行一些了解,这样也方便我们对将要看到的一些奇怪现象出现进行理解。

离屏渲染我们不在这里费篇幅进行解释,不过我们需要对图层混合进行一些简单的理解。

  • 当一个图层layer1完全遮盖了图层layer2时,GPU就不会渲染被遮盖的layer2。
  • 而如果layer1的opaque为false或者backgroundColor.alpha<1,这个时候就需要去渲染被layer1遮盖的图层的颜色。
  • 所以,当我们有多个半透明的图层叠在一起,那么GPU就需要为每个像素点根据各个图层的alpha值来计算像素点最终需要显示的颜色值。

图层混合的一个最直观的解释就是,我们为视图设置阴影,但是视图的背景色设置为半透明。示例代码如下:

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 200, 60)];
view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:0.5];
view.layer.shadowColor = UIColor.redColor.CGColor;
view.layer.shadowOffset = CGSizeMake(0, 0);
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = 5;

生成的效果图如下:

我们设置背景色为蓝色,阴影色为红色,但是最终生成的图片的背景色却是紫色。这就是因为背景色的alpha值为0.5,系统进行了图层混合的原因。

我们都知道,如果为视图设置阴影,就可能会造成离屏渲染图层混合,从而增大渲染的消耗,严重时会造成页面的卡顿。那么是什么只要设置了阴影在渲染时就需要离屏渲染图层混合,还是只是在特定情况下才需要呢?我们接下来对这个问题做一个小小的实验。

上面的图片是我们对4个视图的背景色的alpha值和shadowPath这两个属性分别设置不同的值所展示的效果(其中backgroundColor的RGB为0xFFFFFF,阴影颜色为UIColor.darkGrayColor)。shadowPath这个属性我们暂时不做详细介绍,这里只是把它作为一个输入值。
我们打开Color Blended layersColor Off-screen Rendered得到了下面这张图:

图中被黄色覆盖的区域就是会进行离屏渲染渲染的区域,被红色覆盖的区域就是会进行图层混合的区域。

我们总结一下:

  • 如果设置视图的背景色为半透明(alpha != 1)则会进行图层混合
  • 如果设置了shadowPath,则不会进行离屏渲染,如果不设置,则需要。
    所以我们在使用时,建议不要设置视图的背景色为半透明,并且尽量为阴影设置shadowPath

shadow相关属性

1. shadowColor

shadowColor很好理解,就是阴影的颜色,默认值是黑色。我们写一个如下示例代码:

UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(60, 200, 100, 60)];
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(210, 200, 100, 60)];
UIView *view3 = [[UIView alloc] initWithFrame:CGRectMake(60, 300, 100, 60)];
UIView *view4 = [[UIView alloc] initWithFrame:CGRectMake(210, 300, 100, 60)];

[self addShadowForView:view1 shadowColor:UIColor.redColor];
[self addShadowForView:view2 shadowColor:UIColor.greenColor];
[self addShadowForView:view3 shadowColor:UIColor.blackColor];
[self addShadowForView:view4 shadowColor:UIColor.darkGrayColor];

- (void)addShadowForView:(UIView *)view shadowColor:(UIColor *)shadowColor {
    view.backgroundColor = UIColor.whiteColor;
    
    view.layer.shadowColor = shadowColor.CGColor;
    view.layer.shadowOffset = CGSizeMake(0, 0);
    view.layer.shadowOpacity = 1;
    view.layer.shadowRadius = 5;
    
    view.layer.borderColor = shadowColor.CGColor;
    view.layer.borderWidth = 1 / UIScreen.mainScreen.scale;
    view.layer.cornerRadius = 10;
}

效果图如下:

2. shadowOpacity

shadowOpacity是阴影的不透明度,该值范围是[0, 1],0表示完全透明,1表示完全不透明。

[self addShadowForView:view1 shadowOpacity:1.0];
[self addShadowForView:view2 shadowOpacity:0.6];
[self addShadowForView:view3 shadowOpacity:0.3];
[self addShadowForView:view4 shadowOpacity:0];

- (void)addShadowForView:(UIView *)view shadowRadius:(CGFloat)shadowRadius {
    view.backgroundColor = UIColor.whiteColor;
    
    view.layer.shadowColor = UIColor.redColor.CGColor;
    view.layer.shadowOffset = CGSizeMake(0, 0);
    view.layer.shadowOpacity = 1;
    view.layer.shadowRadius = shadowRadius;
    
    view.layer.borderColor = UIColor.redColor.CGColor;
    view.layer.borderWidth = 1 / UIScreen.mainScreen.scale;
    view.layer.cornerRadius = 10;
}

得到效果图如下:

3. shadowOffset

shadowOffset是阴影的偏移,需要传入一个CGSize,默认值是(0, -3)。
CGSize的两个值为xy来作说明,x为正数时向右偏移,x为负数时向左偏移;y为正数时向下偏移,y为负数时向上偏移。我们如下设置不同的shadowOffset

[self addShadowForView:view1 shadowOffset:CGSizeMake(-5, 0)];
[self addShadowForView:view2 shadowOffset:CGSizeMake(5, 0)];
[self addShadowForView:view3 shadowOffset:CGSizeMake(0, -5)];
[self addShadowForView:view4 shadowOffset:CGSizeMake(0, 5)];

- (void)addShadowForView:(UIView *)view shadowOffset:(CGSize)shadowOffset {
    view.backgroundColor = UIColor.whiteColor;
    
    view.layer.shadowColor = UIColor.redColor.CGColor;
    view.layer.shadowOffset = shadowOffset;
    view.layer.shadowOpacity = 1;
    view.layer.shadowRadius = 5;
    
    view.layer.borderColor = UIColor.redColor.CGColor;
    view.layer.borderWidth = 1 / UIScreen.mainScreen.scale;
    view.layer.cornerRadius = 10;
}

得到如下结果:

4. shadowRadius

shadowRadius可以理解成阴影的宽度,默认值是3。阴影分为内侧阴影和外侧阴影,shadowRadius表示一侧阴影的宽度,也就是说完整阴影的宽度是shadowRadius * 2
如果想看到内侧阴影,我们可以在一个特殊的情况下呈现:

    view5.backgroundColor = UIColor.clearColor;
    
    view5.layer.shadowColor = UIColor.redColor.CGColor;
    view5.layer.shadowOffset = CGSizeMake(0, 0);
    view5.layer.shadowOpacity = 1;
    view5.layer.shadowRadius = 5;
    
    view5.layer.borderColor = UIColor.grayColor.CGColor;
    view5.layer.borderWidth = 1 / UIScreen.mainScreen.scale;

效果图如下:

我们可以看到边框的内外都有阴影,但是阴影很浅。笔者猜测这是在做混合时,边框的alpha对最终颜色值产生了影响导致。

我们分别给4个视图的设置不同的shadowRadius,效果图如下:

5. shadowPath

shadowPath可以理解成阴影的轮廓。轮廓该怎么解释呢?我们可以这样理解,我们要绘制阴影,其实就是要拿着一支中间颜色深,两边颜色慢慢变浅的笔,沿着轮廓画一遍得到的结果。
在不设置shadowPath的时候,如上所述,会进行离屏渲染,而此时的轮廓就是self.bounds
我们分别设置不同的shadowPath给4个视图,代码如下:

UIBezierPath *path1 = [UIBezierPath bezierPathWithRect:view1.bounds];

UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:CGRectMake(-10, -10, 120, 80)];

UIBezierPath *path3 = [UIBezierPath bezierPath];
[path3 moveToPoint:CGPointMake(-5, -5)];
[path3 addLineToPoint:CGPointMake(105, -5)];
[path3 addLineToPoint:CGPointMake(105, 65)];
[path3 addLineToPoint:CGPointMake(-5, 65)];
[path3 closePath];

UIBezierPath *path4 = [UIBezierPath bezierPath];
[path4 moveToPoint:CGPointMake(-5, -5)];
[path4 addLineToPoint:CGPointMake(105, -5)];
[path4 addLineToPoint:CGPointMake(105, 65)];
[path4 closePath];

[self addShadowForView:view1 shadowPath:path1.CGPath];
[self addShadowForView:view2 shadowPath:path2.CGPath];
[self addShadowForView:view3 shadowPath:path3.CGPath];
[self addShadowForView:view4 shadowPath:path4.CGPath];

- (void)addShadowForView:(UIView *)view shadowPath:(CGPathRef)shadowPath {
    view.backgroundColor = UIColor.whiteColor;
    
    view.layer.shadowColor = UIColor.redColor.CGColor;
    view.layer.shadowOffset = CGSizeMake(0, 0);
    view.layer.shadowOpacity = 1;
    view.layer.shadowRadius = 5;
    view.layer.shadowPath = shadowPath;
    
    view.layer.borderColor = UIColor.redColor.CGColor;
    view.layer.borderWidth = 1 / UIScreen.mainScreen.scale;
    view.layer.cornerRadius = 10;
}

效果图如下:

我们可以看到shadowPathself.bounds时,其效果就和不设置shadowPath一样。
如果shadowPath范围超出了视图的范围,则超出部分会被shadowColor进行填充,之后再进行混合操作,计算最终的颜色值。
shadowPath完全可以自定义,但是必须一个封闭区间,shadowPath是一条线时不会显示阴影。

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

推荐阅读更多精彩内容