CAShapeLayer 是 CALayer 的子類吵冒,但是比 CALayer 更靈活搏屑,可以畫出各種圖形,當然免胃,你也可以使用其他方式來畫音五,隨你。
雜談
在 CAShapeLayer 中羔沙,也可以像 CALayer 一樣指定它的 frame 來畫躺涝,就像這樣:
let layer = CAShapeLayer()
layer.frame = CGRectMake(110, 100, 150, 100)
layer.backgroundColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)
然后你就會得到如圖這樣的黑色矩形
但是,CAShapeLayer 有一個神奇的屬性 path
用這個屬性配合上 UIBezierPath 這個類就可以達到超神的效果撬碟。
UIBezierPath 顧名思義诞挨,這是用貝塞爾曲線的方式來構建一段弧線,你可以用任意條弧線來組成你想要的形狀呢蛤,比如,你想用它來和上面一樣畫一個矩形棍郎,那就可以這樣子來做:
let path = UIBezierPath(rect: CGRectMake(110, 100, 150, 100))
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)
要注意的是其障,這里就不要用backgroundColor
這個屬性了,而要使用 fillColor
和 strokeColor
涂佃,前者代表設置這個 Layer 的填充色励翼,后者代表設置它的邊框色
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor
可以試一下把上面代碼設置顏色的部分改成這個樣子,那么運行程序的時候就會是這種樣子
玩一下UIBezierPath
在說回 UIBezierPath 辜荠,在 UIBezierPath 的 init 方法中汽抚,就有很多方便你畫各種圖形的方法,比如你可以畫一個帶圓角的圖形
let path = UIBezierPath(roundedRect: CGRectMake(110, 100, 150, 100), cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor
還可以指定起始角和半徑畫圓
let radius: CGFloat = 60.0
let startAngle: CGFloat = 0.0
let endAngle: CGFloat = CGFloat(M_PI * 2)
let path = UIBezierPath(arcCenter: view.center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor
在這里涉及到角度的問題伯病,起始角和結束角造烁,這里的角度使使用弧度制來表示,這里我收藏了一張圖片午笛,以方便參考
怎么畫曲線
貝塞爾曲線的畫法是由起點惭蟋、終點、控制點三個參數(shù)來畫的药磺,為了解釋清楚這個點告组,我寫了幾行代碼來解釋它
let startPoint = CGPointMake(50, 300)
let endPoint = CGPointMake(300, 300)
let controlPoint = CGPointMake(170, 200)
let layer1 = CALayer()
layer1.frame = CGRectMake(startPoint.x, startPoint.y, 5, 5)
layer1.backgroundColor = UIColor.redColor().CGColor
let layer2 = CALayer()
layer2.frame = CGRectMake(endPoint.x, endPoint.y, 5, 5)
layer2.backgroundColor = UIColor.redColor().CGColor
let layer3 = CALayer()
layer3.frame = CGRectMake(controlPoint.x, controlPoint.y, 5, 5)
layer3.backgroundColor = UIColor.redColor().CGColor
let path = UIBezierPath()
let layer = CAShapeLayer()
path.moveToPoint(startPoint)
path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint)
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)
view.layer.addSublayer(layer1)
view.layer.addSublayer(layer2)
view.layer.addSublayer(layer3)
我很隨意的定義了三個點,為了清楚顯示它們的位置癌佩,我放了三個矩形在上面以便觀察木缝,然后調用 path.moveToPoint(startPoint)
讓它移動到起始點便锨,然后調用path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint)
這個方法告訴它結束點和控制點,這樣它就能畫出一條有弧度的線條了我碟,如果把fillColor
設置一個顏色放案,那么它就會變成一個很丑的形狀了,示例圖如下
控制點決定了它的曲率怎囚,曲線的頂點不等于控制點的位置卿叽,具體可以看一下貝塞爾曲線的定義,你還可以使用兩個控制點來畫恳守,兩個控制點可以使用方法 path.addCurveToPoint(endPoint, controlPoint1: controlPoint, controlPoint2: controlPoint2)
來搞定
這樣它會是這個樣子
再來說說 CAShapeLayer
CAShapeLayer 是個神奇的東西考婴,給它一個path
它就能變成你想要的形狀,它還有很多可以玩的地方催烘。綜合使用可以組合成不同的動畫沥阱,比如下面這樣
這三個動畫就是使用了 strokeEnd
strokeStart
lineWidth
三個屬性,第一個動畫用了strokeEnd
這個屬性的值范圍是0-1伊群,動畫顯示了從0到1之間每一個值對這條曲線的影響考杉,strokeStart
的方法則是相反的,如果把這兩個值首先都設置成0.5然后慢慢改變成0和1的時候就會變成第二個動畫舰始,配合lineWidth
則曲線會慢慢變粗崇棠,這里的很多屬性都是支持動畫的。
示例代碼
private func animation1() {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 2
layer.addAnimation(animation, forKey: "")
}
private func animation2() {
layer.strokeStart = 0.5
layer.strokeEnd = 0.5
let animation = CABasicAnimation(keyPath: "strokeStart")
animation.fromValue = 0.5
animation.toValue = 0
animation.duration = 2
let animation2 = CABasicAnimation(keyPath: "strokeEnd")
animation2.fromValue = 0.5
animation2.toValue = 1
animation2.duration = 2
layer.addAnimation(animation, forKey: "")
layer.addAnimation(animation2, forKey: "")
}
private func animation3() {
let animation = CABasicAnimation(keyPath: "lineWidth")
animation.fromValue = 1
animation.toValue = 10
animation.duration = 2
layer.addAnimation(animation, forKey: "")
}
應用一下
前一陣子在仿時光網(wǎng)這個APP丸卷,其中有一個Layer的形狀很怪異枕稀,是這樣的
很明顯它可以用 CAShapeLayer + UIBezierPath 來做,思路大概是這樣谜嫉,先移動到左上方的位置萎坷,然后向下劃線,然后往右劃線沐兰,然后往上劃線哆档,還剩一個蓋子,這個蓋子就用一個控制點控制曲率住闯,非常簡單瓜浸,代碼如下
let finalSize = CGSizeMake(CGRectGetWidth(view.frame), 400)
let layerHeight = finalSize.height * 0.2
let layer = CAShapeLayer()
let bezier = UIBezierPath()
bezier.moveToPoint(CGPointMake(0, finalSize.height - layerHeight))
bezier.addLineToPoint(CGPointMake(0, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - layerHeight))
bezier.addQuadCurveToPoint(CGPointMake(0,finalSize.height - layerHeight),
controlPoint: CGPointMake(finalSize.width / 2, (finalSize.height - layerHeight) - 40))
layer.path = bezier.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)
就能畫出這樣的形狀來
再來一個復雜點的,微信下拉拍小視頻的那只眼睛很有趣寞秃,來模仿一下那個效果吧斟叼,它是這樣的
首先你得畫出這只眼睛,這是眼睛包括5個部分組成(這個是用OC寫的)
@property (strong, nonatomic) CAShapeLayer *eyeFirstLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeSecondLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeballLayer;
@property (strong, nonatomic) CAShapeLayer *topEyesocketLayer;
@property (strong, nonatomic) CAShapeLayer *bottomEyesocketLayer;
然后春寿,還是通過 UIBezierPath 和 CAShapeLayer 這樣的老套路來畫朗涩,代碼較多
- (CAShapeLayer *)eyeFirstLightLayer {
if (!_eyeFirstLightLayer) {
_eyeFirstLightLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.2
startAngle:(230.f / 180.f) * M_PI
endAngle:(265.f / 180.f) * M_PI
clockwise:YES];
_eyeFirstLightLayer.borderColor = [UIColor blackColor].CGColor;
_eyeFirstLightLayer.lineWidth = 5.f;
_eyeFirstLightLayer.path = path.CGPath;
_eyeFirstLightLayer.fillColor = [UIColor clearColor].CGColor;
_eyeFirstLightLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _eyeFirstLightLayer;
}
- (CAShapeLayer *)eyeSecondLightLayer {
if (!_eyeSecondLightLayer) {
_eyeSecondLightLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.2
startAngle:(211.f / 180.f) * M_PI
endAngle:(220.f / 180.f) * M_PI
clockwise:YES];
_eyeSecondLightLayer.borderColor = [UIColor blackColor].CGColor;
_eyeSecondLightLayer.lineWidth = 5.f;
_eyeSecondLightLayer.path = path.CGPath;
_eyeSecondLightLayer.fillColor = [UIColor clearColor].CGColor;
_eyeSecondLightLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _eyeSecondLightLayer;
}
- (CAShapeLayer *)eyeballLayer {
if (!_eyeballLayer) {
_eyeballLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.3
startAngle:(0.f / 180.f) * M_PI
endAngle:(360.f / 180.f) * M_PI
clockwise:YES];
_eyeballLayer.borderColor = [UIColor blackColor].CGColor;
_eyeballLayer.lineWidth = 1.f;
_eyeballLayer.path = path.CGPath;
_eyeballLayer.fillColor = [UIColor clearColor].CGColor;
_eyeballLayer.strokeColor = [UIColor whiteColor].CGColor;
_eyeballLayer.anchorPoint = CGPointMake(0.5, 0.5);
}
return _eyeballLayer;
}
- (CAShapeLayer *)topEyesocketLayer {
if (!_topEyesocketLayer) {
_topEyesocketLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
[path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y - center.y - 20)];
_topEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
_topEyesocketLayer.lineWidth = 1.f;
_topEyesocketLayer.path = path.CGPath;
_topEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
_topEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _topEyesocketLayer;
}
- (CAShapeLayer *)bottomEyesocketLayer {
if (!_bottomEyesocketLayer) {
_bottomEyesocketLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
[path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y + center.y + 20)];
_bottomEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
_bottomEyesocketLayer.lineWidth = 1.f;
_bottomEyesocketLayer.path = path.CGPath;
_bottomEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
_bottomEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _bottomEyesocketLayer;
}
然后更改一下某些屬性的值,方便稍后的動畫
- (void)setupAnimation {
self.eyeFirstLightLayer.lineWidth = 0.f;
self.eyeSecondLightLayer.lineWidth = 0.f;
self.eyeballLayer.opacity = 0.f;
_bottomEyesocketLayer.strokeStart = 0.5f;
_bottomEyesocketLayer.strokeEnd = 0.5f;
_topEyesocketLayer.strokeStart = 0.5f;
_topEyesocketLayer.strokeEnd = 0.5f;
}
最后根據(jù) UIScrollView 的 contentOffset
來控制各種屬性绑改,辦法較笨谢床,但管用兄一。
- (void)animationWith:(CGFloat)y {
CGFloat flag = self.frame.origin.y * 2.f - 20.f;
if (y < flag) {
if (self.eyeFirstLightLayer.lineWidth < 5.f) {
self.eyeFirstLightLayer.lineWidth += 1.f;
self.eyeSecondLightLayer.lineWidth += 1.f;
}
}
if(y < flag - 20) {
if (self.eyeballLayer.opacity <= 1.0f) {
self.eyeballLayer.opacity += 0.1f;
}
}
if (y < flag - 40) {
if (self.topEyesocketLayer.strokeEnd < 1.f && self.topEyesocketLayer.strokeStart > 0.f) {
self.topEyesocketLayer.strokeEnd += 0.1f;
self.topEyesocketLayer.strokeStart -= 0.1f;
self.bottomEyesocketLayer.strokeEnd += 0.1f;
self.bottomEyesocketLayer.strokeStart -= 0.1f;
}
}
if (y > flag - 40) {
if (self.topEyesocketLayer.strokeEnd > 0.5f && self.topEyesocketLayer.strokeStart < 0.5f) {
self.topEyesocketLayer.strokeEnd -= 0.1f;
self.topEyesocketLayer.strokeStart += 0.1f;
self.bottomEyesocketLayer.strokeEnd -= 0.1f;
self.bottomEyesocketLayer.strokeStart += 0.1f;
}
}
if (y > flag - 20) {
if (self.eyeballLayer.opacity >= 0.0f) {
self.eyeballLayer.opacity -= 0.1f;
}
}
if (y > flag) {
if (self.eyeFirstLightLayer.lineWidth > 0.f) {
self.eyeFirstLightLayer.lineWidth -= 1.f;
self.eyeSecondLightLayer.lineWidth -= 1.f;
}
}
}
最后
總之使用 UIbezierPath 和 CAShapeLayer 可以畫出你想要的任何形狀,沒有它做不到识腿,只有你想不到出革,搞定了它們你就可以輕松定制你想要的任何控件了。
微信Demo
代碼