嗯,圆和椭圆还不错,但如果是带圆角的矩形呢?我们现在能做到那样了么? ------Steve Jobs
本文介绍的属性相对于前几篇文章来说比较简单,开发也比较常用;核心内容是对阴影的理解。
1、masksToBounds 裁剪边界
下面代码,黄色视图超出了父视图蓝色视图的区域,可以复制代码运行查看效果。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let blueView = UIView(frame:CGRect(x:50, y: 200, width:350, height:300))
blueView.backgroundColor = UIColor.lightGray
let yellowView = UIView(frame:CGRect(x:-50, y:-50, width:200, height:200))
yellowView.backgroundColor = UIColor.yellow
blueView.addSubview(yellowView)
view.addSubview(blueView)
}
}
在以上代码加入 blueView.layer.masksToBounds = true,可以将超出边界部分裁剪掉。
// 相对应的 UIView 的属性:blueView.clipsToBounds = true
2、cornerRadius 圆角
加入以下代码:
yellowView.layer.cornerRadius = 15
blueView.layer.cornerRadius = 15
// cornerRadius:角落半径,圆角的半径是多少 pt
更改5个字母,再运行一遍: 把 yellowView 从 UIVew 换成 UILabel,其他都不变。
发现 label 右下角的圆角没了!咋回事?
深入解释 cornerRadius :By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.
大致意思是,圆角对于 layer 的 contents 属性(之前介绍过,一般是 CGImage)不起作用,如果想让 contents 也圆角,就设置 masksToBounds 为 true。
因此我们可以猜测,UILabel 内部可能是绘制文字作为寄宿图显示的,这些并不重要,重要的是知道:
yellowView.layer.masksToBounds = true 就可以让 label 有圆角了!
那如何知道一个视图是否自己绘制了寄宿图呢?目前应该只能靠经验... UILabel、UIImageView 是最常见的使用寄宿图的系统控件...
对了,如果想让四个角中的某几个是圆角,其他是直角呢?这就需要用到图层蒙板(下一篇提到) 或者 CAShapeLayer(后面“专用图层”提到)。
3、borderColor、borderWidth 边框
再次运行,加上:
blueView.layer.borderColor = UIColor.red.cgColor
blueView.layer.borderWidth = 10
图层绘制了边线。这条边线(也被称作stroke)沿着图层的 bounds 绘制,同时也包含圆角。
注意:边框是绘制在图层边界内侧的,而且在所有子内容之前,也在子图层之前 !
为了验证在子图层之前,我们把裁剪关掉,可以尝试用一下这个属性,同样是裁剪开关:
blueView.clipsToBounds = false
运行后就可以看到效果了!
4、阴影
介绍 CALayer 的几个属性。
shadowOpacity: 可以当作阴影的初始透明度,在 0.0 (默认,不可见的阴影) 和1.0 (完全不透明的阴影) 之间的浮点数。只要设置一个大于 0 的值,就会显示一个有轻微模糊的黑(默认)色阴影在图层上。若要改动阴影的表现,可以使用 CALayer 的另外几个属性: shadowColor,shadowOffset,shadowRadius,shadowPath。
第一个是阴影颜色不需要多解释;
第二个是阴影偏移量:一个 CGSize 值,宽度控制阴影横向偏移,高度控制纵向偏移。 默认值(0, -3),即阴影相对于 Y 轴有 3 个点的向上位移。大家注意到 macOS 里的阴影都是在窗口左右下方,上方没有;在iOS里,因为之前说过的 两个系统排版的坐标翻转问题,使 iOS 把阴影搞到反向上方了。如果需要显示在下方的阴影,要手动设置正数。
第三个是阴影模糊度:值是 0 的时候,阴影就像一条边界线(只是像一条线,实际是实心的一大块)。当值越来越大的时候,边界线看上去就会越来越模糊(自然)。苹果自家的应用设计更偏向于自然的阴影,所以一般使用非零值(默认 3 pt)。单词 radius 是半径,也可以视为边界线的宽度,在这个距离内,背景色由 shadowColor(opacity 是初始透明度)渐变到透明,就成了阴影。
第四个是完全自定义的阴影绘制形状路径,CGPath,下篇文章会具体介绍它。
前面提到图层边框是沿着图层边界绘制的,但是阴影是沿着 “内容” 的外形绘制的(如果有的话)(如果没有指定 shadowPath)!!!那么阴影的最终形状是怎么计算的呢?
系统会考虑:1 寄宿图的轮廓;2 背景颜色;3 递归计算子视图的阴影形状,结合三者形成最终 “内容” 的形状,并绘制阴影;4 特殊情况如 CAShapeLayer 的 path 等
这也是为什么直接给 UILabel 设置阴影,阴影会出现在文字上,而设置背景色之后,阴影出现在边框上。
此外,业务上经常同时出现阴影和圆角裁剪,但开启了 masksToBounds 属性,阴影都被剪掉了。常用的办法是 用两个视图,一个画阴影的父视图,一个显示圆角的子视图。
需要注意刚才提到的阴影计算方式,如果希望阴影出现在视图边界,恰好视图没有背景色或寄宿图,那么必须设置 shadowPath,或简单一点,直接设置子视图的背景色,作为系统计算出的阴影路径。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 真正的内容
let v: UIView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100)))
v.layer.cornerRadius = 10
v.layer.masksToBounds = true
v.backgroundColor = .red
// 呈现阴影
let out = UIView(frame: CGRect(x: 100, y: 200, width: 100, height: 100))
out.layer.shadowOpacity = 1
out.layer.shadowRadius = 5
out.layer.shadowOffset = CGSize(width: 0, height: 5)
view.addSubview(out)
out.addSubview(v)
}
}
运行可以看到目标实现了