想必大家常会遇到这样的需求吧,做一个view, 既有圆角又有直角。这种view主要见于某些从下往上弹出的view,上面是圆角,下面是直角。
相信叫你做出一个四周都有圆角的view简直信手拈来吧
self.layer.maskToBounds = YES;
self.layer.cornerRadius = 6.f;
就如上面两句代码所示,简单又快捷。如果是简单的显示内容的view,这样子就完全可以了,但是呢,这种方式性能不怎么好,因为这种方法会造成出现离屏渲染(offscreen-rendering 指的是GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作),离屏渲染不仅会消耗大量性能,而且还需要开辟大量额外的空间用于 当前屏幕渲染(on-screen rendering)和离屏渲染 之间的相互切换,而且一旦出现离屏渲染的情况,从开始渲染到最终完成渲染,期间还需要多次的 当前屏幕渲染和离屏渲染 之间的切换,一次的切换都已经耗性能了,多次就更不用说了。有次是当有图像的view出现离屏渲染的情况时,那种性能和额外空间更是加倍的增加。因此就可能出现大家很多时候见到的卡屏现象。
并且,上面两句代码实现的圆角,并不能实现我们标题提到的那种形式:既有圆角又有直角。
这里开始就要说到我们这篇文章的重点了,那就是用到图层(CALayer),说到layer,可能你会想到这样一个方法
- (void)drawRect:(CGRect)rect {
}
这个方法主要用于在当前view上进行绘制,连什么NSSring和UIImage都能直接绘制到上面的(就是调用其相应的draw方法)。并且draw方法一旦实现,就会生成一个寄宿图(我等会儿会稍微讲讲这个寄宿图),寄宿图的话, 是既耗性能又浪费空间的方法,因此你会发现,你平时自己新建一个view的时候,这个方法都是默认被注释了的,这也说明,其实苹果官方也是不推荐大家实现这个方法。
(给大家解释下寄宿图吧,每个view都会在生成的时候对应生成绑定一个layer,这个layer就叫做这个view的寄宿图层,layer有一个属性,叫做contentsImage,这个就是view的寄宿图了,就是CGImage的一张图,通常情况下,如果这个属性没进行设置的话,那么这个寄宿图就是空的,如果一旦实现了view的-drawRect方法,系统就会默认用Core Graphics绘制一张空的CGImage图片赋值给这个contentsImage属性,并且由于-drawRect方法在view的Frame以及相关的属性发生变化的时候都会调用,也就是调用比较频繁, 所以,系统就会默认将那个绘制出来的CGImage图缓存起来,这样就会占用内存并且在每次调用-drawRect方法时都会消耗一定的性能来显示这个contentsImage,这也是为什么自定义view的时候,苹果官方是默认将这个-drawRect方法注释掉的,所以苹果官方的建议也是,如果没有在view上绘制内容的话,尽量别实现这个-drawRect方法,因为这样是比较消耗性能和内存的。一旦你实现了CALayerDelegate协议中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其实就是前者的包装方法),图层就创建了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:(图层宽 乘 图层高 乘 4字节),宽高的单位均为像素。对于一个在Retina iPad上的全屏图层来说,这个内存量就是 (2048乘1526乘4字节),相当于12MB内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配)。
那么我们改怎么做能既不耗性能跟浪费空间也能优雅的实现这种效果呢,这里就要用到一个东西,叫做CAShapeLayer。聪明的你,想必从名字就能看出来这个类的主要作用吧,没错,就是一个能绘制出各式各样图层的类。
注意:大家使用CAShapeLayer尽量别放在-drawRect:方法中,原因上面已经数清楚了
我们先来看看官方对CAShapeLayer的解释
@interface CAShapeLayer : CALayer
The shape is composited between the layer's contents and its first sublayer.
The shape will be drawn antialiased, and whenever possible it will be
mapped into screen space before being rasterized to preserve
resolution independence. However, certain kinds of image
processing operations, such as CoreImage filters, applied to the layer
or its ancestors may force rasterization in a local coordinate space.
Shape rasterization may favor speed over accuracy. For example,
pixels with multiple intersecting path segments may not give exact
results.
shape在图层内容与其第一子图层之间合成。
shape将对图形进行抗锯齿绘制,并在可能的情况下将其映射到屏幕
空间,然后再进行栅格化以保持分辨率的独立性。 但是,应用于图层
或其祖父类的某些类型的图像处理操作(例如CoreImage滤镜)可能会
在局部坐标空间中强制进行栅格化。
shapre栅格化可能会使速度胜于精度。 例如,
具有多个相交路径段的像素可能无法给出精确的
结果。
如果我们要绘制一个上面两个角是圆角,下面两个角是直角的view怎么做呢,具体请看下面代码
//这是我写在UIView分类中的一个方法,当然你可以写在任何地
/// 设置顶部两角为圆角的view
/// @param cornerRadius radius
/// @param bgColor 背景颜色
- (CAShapeLayer *)zl_setTopCornerWithRadius:(CGFloat)cornerRadius bgColor:(UIColor *)bgColor {
//初始化一个shapeLayer对象
CAShapeLayer *layer = CAShapeLayer.layer;
//这是显示上两个圆角,下两个直角的主要方法,
//这个方法主要是通过生成一个贝塞尔曲线(这里就是上面圆角,下面直角的贝塞尔曲线),之后将这个UIBezierPath对象赋值给CAShapeLayer的对象
//之后CAShapeLayer会按照赋值来的path属性来进行显示,当然前提还是你需要将它加在某个layer上面
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
//上面绘制出了一个上面两个角是圆角,下面两个角是直角的path
layer.path = path.CGPath;
//fillColor填充色,相当于view的backgroundColor
//温馨提示:这直接设置layper. backgroundColor是不行的
layer.fillColor = bgColor.CGColor;
//将shapeLayer加到self.layer的第一层子图层中
[self.layer insertSublayer:layer atIndex:0];
//设置当前view的背景色为透明色,以便加上的图层能很好的显示出来并被看到
self.backgoundColor = UIColor.clearColor;
return layer;
}
/// 这个方法主要用于 创建指定位置圆角的矩形路径
/// @param rect path的rect,相当于view或layer的frame
/// @param corners 这是一个枚举值,看了下面这个枚举的具体值,想必聪明的你就能一目了然
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners
typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
UIRectCornerTopLeft = 1 << 0, //左上角
UIRectCornerTopRight = 1 << 1,//右上角
UIRectCornerBottomLeft = 1 << 2,//左下角
UIRectCornerBottomRight = 1 << 3,//右下角
UIRectCornerAllCorners = ~0UL//所有角
};
只要一看见左移,就能明白,这个枚举值是可以通过' | '来进行多值合并赋值的(例如这样:UIRectCornerBottomLeft | UIRectCornerBottomRight),当然也可以直接用加号进行直接的多
值合并赋值(例如这样: UIRectCornerTopRight + UIRectCornerTopLeft )
下面看看我们项目中做出来的效果吧!
希望能对您有所帮助!
源码地址