CAShapeLayer 是 CALayer 的子類,但是比 CALayer 更靈活,可以畫出各種圖形,當然,你也可以使用其他方式來畫,隨你。
雜談
在 CAShapeLayer 中,也可以像 CALayer 一樣指定它的 frame 來畫,就像這樣:
1 2 3 4 5 |
let layer = CAShapeLayer() layer.frame = CGRectMake(110, 100, 150, 100) layer.backgroundColor = UIColor.blackColor().CGColor view.layer.addSublayer(layer) |
然後你就會得到如圖這樣的黑色矩形
但是,CAShapeLayer 有一個神奇的屬性 path
用這個屬性配合上 UIBezierPath 這個類就可以達到超神的效果。
UIBezierPath 顧名思義,這是用貝塞爾曲線的方式來構建一段弧線,你可以用任意條弧線來組成你想要的形狀,比如,你想用它來和上面一樣畫一個矩形,那就可以這樣子來做:
1 2 3 4 5 |
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 的填充色,後者代表設定它的邊框色
1 2 |
layer.fillColor = UIColor.clearColor().CGColor layer.strokeColor = UIColor.blackColor().CGColor |
可以試一下把上面程式碼設定顏色的部分改成這個樣子,那麼執行程式的時候就會是這種樣子
玩一下UIBezierPath
在說回 UIBezierPath ,在 UIBezierPath 的 init 方法中,就有很多方便你畫各種圖形的方法,比如你可以畫一個帶圓角的圖形
1 2 3 4 5 |
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 |
還可以指定起始角和半徑畫圓
1 2 3 4 5 6 7 8 |
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 |
在這裡涉及到角度的問題,起始角和結束角,這裡的角度使使用弧度制來表示,這裡我收藏了一張圖片,以方便參考
怎麼畫曲線
貝塞爾曲線的畫法是由起點、終點、控制點三個引數來畫的,為了解釋清楚這個點,我寫了幾行程式碼來解釋它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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
則曲線會慢慢變粗,這裡的很多屬性都是支援動畫的。
示例程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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: "") } |
應用一下
前一陣子在仿時光網這個APP,其中有一個Layer的形狀很怪異,是這樣的
很明顯它可以用 CAShapeLayer + UIBezierPath 來做,思路大概是這樣,先移動到左上方的位置,然後向下劃線,然後往右劃線,然後往上劃線,還剩一個蓋子,這個蓋子就用一個控制點控制曲率,非常簡單,程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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寫的)
1 2 3 4 5 |
@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 這樣的老套路來畫,程式碼較多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
- (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; } |
然後更改一下某些屬性的值,方便稍後的動畫
1 2 3 4 5 6 7 8 9 |
- (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; } |
最後根據 UIScrollView 的 contentOffset
來控制各種屬性,辦法較笨,但管用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- (void)animationWith:(CGFloat)y { CGFloat flag = self.frame.origin.y * 2.f - 20.f; if (y 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 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
程式碼