iOS核心动画高级技巧--(四)视觉效果

在这一章,我们将会探索 一些能够通过使用CALayer属性实现的视觉效果。

圆角

圆角矩形是iOS的一个标志性审美特性。这在iOS的每一个地方都得到了体现, 不论是主屏幕图标,还是警告弹框,甚至是文本框。按照这流行程度,你可能会认为一定有不借助Photoshop就能轻易创建圆角举行的方法。恭喜你,猜对了。

CALayer有一个叫做 conrnerRadius 的属性控制着图层角的曲率。它是一个浮点数,默认为0(为0的时候就是直角),但是你可以把它设置成任意值。默认情况 下,这个曲率值只影响背景颜色而不影响背景图片或是子图层。不过,如果把masksToBounds设置成YES的话,图层里面的所有东西都会被截取。

图层边框

CALayer另外两个非常有用属性就是 borderWidthborderColor。二者共同定义了图层边的绘制样式。这条线(也被称作stroke)沿着图层的 bounds 绘制,同时也包含图层的角。

borderWidth是以点为单位的定义边框粗细的浮点数,默认为 0. borderColor 定义了边框的颜色,默认为黑色。

borderColorCGColorRef类型,而不是UIColor,所以它不是Cocoa的 内置对象。不过呢,你肯定也清楚图层引用了borderColor,虽然属性声明并不能证明这一点。CGColorRef在引用/释放时候的行为表现得与NSObject极其相似。但是Objective-C语法并不支持这一做法,所以 CGColorRef属性即便是强引用也只能通过assign关键字来声明。

阴影

iOS的另一个常见特性呢,就是阴影。阴影往往可以达到图层深度暗示的效果。 也能够用来强调正在显示的图层和优先级(比如说一个在其他视图之前的弹出框),不过有时候他们只是单纯的装饰目的。

shadowOpacity 属性一个大于默认值(也就是0)的值,阴影就可以显示在任意图层之下。 shadowOpacity是一个必须在0.0(不可见)和1.0(完全不透 明)之间的浮点数。如果设置为1.0,将会显示一个有轻微模糊的黑色阴影稍微在图层之上。若要改动阴影的表现,你可以使用CALayer的另外三个属
性: shadowColorshadowOffsetshadowRadius

显而易见, shadowColor 属性控制着阴影的颜色,和borderColor一样,它的类型也是CGColorRef` 。阴影默认是黑色,大多数时候你需要的阴影也是黑色的(其他颜色的阴影看起来是不是
有一点点奇怪。。。)

shadowOffset属性控制着阴影的方向和距离。它是一个 CGSize的值,宽度控制这阴影横向的位移,高度控制着纵向的位移。shadowOffset 的默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移。

shadowRadius 属性控制着阴影的模糊度,当它的值是0的时候,阴影就和视图 一样有一个非常确定的边界线。当值越来越大的时候,边界线看上去就会越来越模 糊和自然。苹果自家的应用设计更偏向于自然的阴影,所以一个非零值再合适不过了。

通常来讲,如果你想让视图或控件非常醒目独立于背景之外(比如弹出框遮罩层),你就应该给 shadowRadius设置一个稍大的值。阴影越模糊,图层的深度 看上去就会更明显.

阴影裁剪

和图层边框不同,图层的阴影继承自内容的外形,而不是根据边界和角半径来确定。为了计算出阴影的形状,Core Animation会将寄宿图(包括子视图,如果有的话)考虑在内,然后通过这些来完美搭配图层形状从而创建一个阴影。

当阴影和裁剪扯上关系的时候就有一个头疼的限制:阴影通常就是在 Layer的边界之外,如果你开启了 masksToBounds属性,所有从图层中突出来的 内容都会被才剪掉。如果在我们之前的边框示例项目中增加图层的阴影属性时,你就会发现问题所在。

从技术角度来说,这个结果是可以是可以理解的,但确实又不是我们想要的效果。如果你想沿着内容裁切,你需要用到两个图层:一个只画阴影的空的外图层,和一个用masksToBounds 裁剪内容的内图层。我们只把阴影用在最外层的视图上,内层视图进行裁剪。

shadowPath属性

我们已经知道图层阴影并不总是方的,而是从图层内容的形状继承而来。这看上
去不错,但是实时计算阴影也是一个非常消耗资源的,尤其是图层有多个子图层,
每个图层还有一个有透明效果的寄宿图的时候。

如果你事先知道你的阴影形状会是什么样子的,你可以通过指定一个shadowPath 来提高性能。shadowPath 是一个 CGPathRef 类型(一个指向CGPath 的指针)。CGPath 是一个Core Graphics对象,用来指定任意的一个 矢量图形。我们可以通过这个属性单独于图层形状之外指定阴影的形状。

用 shadowPath 指定任意阴影形状.png
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad{
  [super viewDidLoad];
  //enable layer shadows
  self.layerView1.layer.shadowOpacity = 0.5f;
  self.layerView2.layer.shadowOpacity = 0.5f;
  //create a square shadow
  CGMutablePathRef squarePath = CGPathCreateMutable();
  CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
  self.layerView1.layer.shadowPath = squarePath;
  CGPathRelease(squarePath);
   //create a circular shadow
  CGMutablePathRef circlePath = CGPathCreateMutable();
  CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds)
  self.layerView2.layer.shadowPath = circlePath; 
  CGPathRelease(circlePath);
@end;

如果是一个矩形或者是圆,用 CGPath会相当简单明了。但是如果是更加复杂一 点的图形, UIBezierPath类会更合适,它是一个由UIKit提供的在CGPath基础上 的Objective-C包装类。

图层蒙板

通过masksToBounds属性,我们可以沿边界裁剪图形;通过 cornerRadius属性,我们还可以设定一个圆角。但是有时候你希望展现的内容不是在一个矩形或圆角矩形。比如,你想展示一个有星形框架的图片,又或者想让一些古卷文字慢慢渐变成背景色,而不是一个突兀的边界。

使用一个32位有alpha通道的png图片通常是创建一个无矩形视图最方便的方法, 你可以给它指定一个透明蒙板来实现。但是这个方法不能让你以编码的方式动态地 生成蒙板,也不能让子图层或子视图裁剪成同样的形状。

CALayer有一个属性叫做 mask可以解决这个问题。这个属性本身就是个 CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,mask 图层定义了父图层的部分可见区域。

mask图层的 Color属性是无关紧要的,真正重要的是图层的轮廓。 mask 属 性就像是一个饼干切割机, mask 图层实心的部分会被保留下来,其他的则会被抛 弃。

如果 mask 图层比父图层要小,只有在 mask 图层里面的内容才是它关心的, 除此以外的一切都会被隐藏起来。

把图片和蒙板图层作用在一起的效果.png
- (void)viewDidLoad
{
  [super viewDidLoad];
  //create mask layer
  CALayer *maskLayer = [CALayer layer];
  maskLayer.frame = self.layerView.bounds;
  UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
  maskLayer.contents = (__bridge id)maskImage.CGImage;
//apply mask to image layer 
  self.imageView.layer.mask = maskLayer;
}

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

拉伸过滤

最后我们再来谈谈 minificationFiltermagnificationFilter属性。总得来讲,当我们视图显示一个图片的时候,都应该正确地显示这个图片(意即:以正确的比例和正确的1:1像素显示在屏幕上)。原因如下:

  • 能够显示最好的画质,像素既没有被压缩也没有被拉伸。
  • 能更好的使用内存,因为这就是所有你要存储的东西。
  • 最好的性能表现,CPU不需要为此额外的计算。

不过有时候,显示一个非真实大小的图片确实是我们需要的效果。比如说一个头像或是图片的缩略图,再比如说一个可以被拖拽和伸缩的大图。这些情况下,为同一图片的不同大小存储不同的图片显得又不切实际。

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

事实上,重绘图片大小也没有一个统一的通用算法。这取决于需要拉伸的内容, 放大或是缩小的需求等这些因素。 CALayer 为此提供了三种拉伸过滤方法,他们 是:

  • kCAFilterLinear (双线性滤波算法)
  • kCAFilterNearest (最近过滤)
  • kCAFilterTrilinear(三线性滤波算法)

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

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

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

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

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

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

总的来说,对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算
法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很
多斜线或是曲线轮廓的图片来说,最近过滤算法会导致更差的结果。换句话说,线
性过滤保留了形状,最近过滤则保留了像素的差异。

组透明

UIView有一个叫做alpha 的属性来确定视图的透明度。CALayer有一个等同的属性叫做opacity ,这两个属性都是影响子层级的。也就是说,如果你给一个图 层设置了opacity属性,那它的子图层都会受此影响。

iOS常见的做法是把一个控件的alpha值设置为0.5(50%)以使其看上去呈现为不可用状态。对于独立的视图来说还不错,但是当一个控件有子视图的时候就有点奇怪了,图展示了一个内嵌了UILabel的自定义UIButton;左边是一个不透明的 按钮,右边是50%透明度的相同按钮。我们可以注意到,里面的标签的轮廓跟按钮 的背景很不搭调。

右边的渐隐按钮中,里面的标签清晰可见.png

这是由透明度的混合叠加造成的,当你显示一个50%透明度的图层时,图层的每 个像素都会一半显示自己的颜色,另一半显示图层下面的颜色。这是正常的透明度的表现。但是如果图层包含一个同样显示50%透明的子图层时,你所看到的视图, 50%来自子视图,25%来了图层本身的颜色,另外的25%则来自背景色。

在我们的示例中,按钮和表情都是白色背景。虽然他们都是50%的可见度,但是 合起来的可见度是75%,所以标签所在的区域看上去就没有周围的部分那么透明。 所以看上去子视图就高亮了,使得这个显示效果都糟透了。

理想状况下,当你设置了一个图层的透明度,你希望它包含的整个图层树像一个 整体一样的透明效果。你可以通过设置Info.plist文件中的 UIViewGroupOpacityYES来达到这个效果,但是这个设置会影响到这个应用,整个app可能会受到不良 影响。如果 UIViewGroupOpacity 并未设置,iOS 6和以前的版本会默认为NO(也许以后的版本会有一些改变)。

另一个方法就是,你可以设置CALayer的一个叫做shouldRasterize属性来实现组透明的效果,如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了
为了启用shouldRasterize属性,我们设置了图层的resterizationScale属性。默认情况下,所有图层拉伸都是1.0, 所以如果你 使用了shouldRasterize属性,你就要确保你设置了rasterizationScale属 性去匹配屏幕,以防止出现Retina屏幕像素化的问题。

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (UIButton *)customButton
{
  //create button
  CGRect frame = CGRectMake(0, 0, 150, 50);
  UIButton *button = [[UIButton alloc] initWithFrame:frame];
  button.backgroundColor = [UIColor whiteColor];
  button.layer.cornerRadius = 10;
  //add label
  frame = CGRectMake(20, 10, 110, 30);
  UILabel *label = [[UILabel alloc] initWithFrame:frame];
  label.text = @"Hello World";
 label.textAlignment = NSTextAlignmentCenter;
  [button addSubview:label];
  return button;
}

- (void)viewDidLoad
{
  [super viewDidLoad];
  //create opaque button
  UIButton *button1 = [self customButton];
  button1.center = CGPointMake(50, 150);
  [self.containerView addSubview:button1];
//create translucent button
UIButton *button2 = [self customButton];  
button2.center = CGPointMake(250, 150); button2.alpha = 0.5; [self.containerView addSubview:button2];
  //enable rasterization for the translucent button
  button2.layer.shouldRasterize = YES; //重点
  button2.layer.rasterizationScale = [UIScreen mainScreen].scale; // 重点
} 
@end
修正后的图.png
总结

这一章介绍了一些可以通过代码应用到图层上的视觉效果,比如圆角,阴影和蒙板。我们也了解了拉伸过滤器和组透明。

iOS核心动画高级技巧--目录

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

推荐阅读更多精彩内容