放肆的使用 UIBezierPath 和 CAShapeLayer 画各种图形

thwi1315 8年前
   <p>CAShapeLayer 是 CALayer 的子类,但是比 CALayer 更灵活,可以画出各种图形,当然,你也可以使用其他方式来画,随你。</p>    <h2>杂谈</h2>    <p>在 CAShapeLayer 中,也可以像 CALayer 一样指定它的 frame 来画,就像这样:</p>    <pre>  <code class="language-objectivec">letlayer = CAShapeLayer()  layer.frame = CGRectMake(110, 100, 150, 100)  layer.backgroundColor = UIColor.blackColor().CGColor     view.layer.addSublayer(layer)  </code></pre>    <p>然后你就会得到如图这样的黑色矩形</p>    <p><img src="https://simg.open-open.com/show/f29132c30b1969f1dc78d0fa748e4ed9.png"></p>    <p>但是,CAShapeLayer 有一个神奇的属性 path 用这个属性配合上 UIBezierPath 这个类就可以达到超神的效果。</p>    <p>UIBezierPath 顾名思义,这是用贝塞尔曲线的方式来构建一段弧线,你可以用任意条弧线来组成你想要的形状,比如,你想用它来和上面一样画一个矩形,那就可以这样子来做:</p>    <pre>  <code class="language-objectivec">letpath = UIBezierPath(rect: CGRectMake(110, 100, 150, 100))  letlayer = CAShapeLayer()  layer.path = path.CGPath  layer.fillColor = UIColor.blackColor().CGColor  view.layer.addSublayer(layer)  </code></pre>    <p>要注意的是,这里就不要用 backgroundColor 这个属性了,而要使用 fillColor 和 strokeColor ,前者代表设置这个 Layer 的填充色,后者代表设置它的边框色</p>    <pre>  <code class="language-objectivec">layer.fillColor = UIColor.clearColor().CGColor  layer.strokeColor = UIColor.blackColor().CGColor  </code></pre>    <p>可以试一下把上面代码设置颜色的部分改成这个样子,那么运行程序的时候就会是这种样子</p>    <p><img src="https://simg.open-open.com/show/ccb01c27173ae5a520618ab5564d83d3.png"></p>    <h2>玩一下UIBezierPath</h2>    <p>在说回 UIBezierPath ,在 UIBezierPath 的 init 方法中,就有很多方便你画各种图形的方法,比如你可以画一个带圆角的图形</p>    <pre>  <code class="language-objectivec">letpath = UIBezierPath(roundedRect: CGRectMake(110, 100, 150, 100), cornerRadius: 50)  letlayer = CAShapeLayer()  layer.path = path.CGPath  layer.fillColor = UIColor.clearColor().CGColor  layer.strokeColor = UIColor.blackColor().CGColor  </code></pre>    <p><img src="https://simg.open-open.com/show/cec8faaf91a85ea6db13c8d5d0146389.png"></p>    <p>还可以指定起始角和半径画圆</p>    <pre>  <code class="language-objectivec">letradius: CGFloat = 60.0  letstartAngle: CGFloat = 0.0  letendAngle: CGFloat = CGFloat(M_PI * 2)  letpath = UIBezierPath(arcCenter: view.center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)  letlayer = CAShapeLayer()  layer.path = path.CGPath  layer.fillColor = UIColor.clearColor().CGColor  layer.strokeColor = UIColor.blackColor().CGColor  </code></pre>    <p><img src="https://simg.open-open.com/show/06e3823f174125e54226666f56c31a68.png"></p>    <p>在这里涉及到角度的问题,起始角和结束角,这里的角度使使用弧度制来表示,这里我收藏了一张图片,以方便参考</p>    <p><img src="https://simg.open-open.com/show/811043df5d873adf88859fe831e5ee90.png"></p>    <h2>怎么画曲线</h2>    <p>贝塞尔曲线的画法是由起点、终点、控制点三个参数来画的,为了解释清楚这个点,我写了几行代码来解释它</p>    <pre>  <code class="language-objectivec">letstartPoint = CGPointMake(50, 300)  letendPoint = CGPointMake(300, 300)  letcontrolPoint = CGPointMake(170, 200)     letlayer1 = CALayer()  layer1.frame = CGRectMake(startPoint.x, startPoint.y, 5, 5)  layer1.backgroundColor = UIColor.redColor().CGColor     letlayer2 = CALayer()  layer2.frame = CGRectMake(endPoint.x, endPoint.y, 5, 5)  layer2.backgroundColor = UIColor.redColor().CGColor     letlayer3 = CALayer()  layer3.frame = CGRectMake(controlPoint.x, controlPoint.y, 5, 5)  layer3.backgroundColor = UIColor.redColor().CGColor     letpath = UIBezierPath()  letlayer = 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)  </code></pre>    <p>我很随意的定义了三个点,为了清楚显示它们的位置,我放了三个矩形在上面以便观察,然后调用 path.moveToPoint(startPoint) 让它移动到起始点,然后调用 path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint) 这个方法告诉它结束点和控制点,这样它就能画出一条有弧度的线条了,如果把 fillColor 设置一个颜色,那么它就会变成一个很丑的形状了,示例图如下</p>    <p><img src="https://simg.open-open.com/show/380aa9f57543b181b5af0508ac59ceb6.png"></p>    <p>控制点决定了它的曲率,曲线的顶点不等于控制点的位置,具体可以看一下贝塞尔曲线的定义,你还可以使用两个控制点来画,两个控制点可以使用方法 path.addCurveToPoint(endPoint, controlPoint1: controlPoint, controlPoint2: controlPoint2) 来搞定</p>    <p>这样它会是这个样子</p>    <p><img src="https://simg.open-open.com/show/9359c924ecc4a2a3251c7d4a3aafe1a9.png"></p>    <h2>再来说说 CAShapeLayer</h2>    <p>CAShapeLayer 是个神奇的东西,给它一个 path 它就能变成你想要的形状,它还有很多可以玩的地方。综合使用可以组合成不同的动画,比如下面这样</p>    <p><img src="https://simg.open-open.com/show/21036f1af18fe3f76599f94cfc3f7ae1.gif"></p>    <p>demo1.gif</p>    <p><img src="https://simg.open-open.com/show/4a250105e7a0e08ecc5f4f474414a7ef.gif"></p>    <p>demo2.gif</p>    <p><img src="https://simg.open-open.com/show/0b86ee906bd38ccf1ecac522cabb578a.gif"></p>    <p>demo3.gif</p>    <p>这三个动画就是使用了 strokeEnd strokeStart lineWidth 三个属性,第一个动画用了 strokeEnd 这个属性的值范围是0-1,动画显示了从0到1之间每一个值对这条曲线的影响, strokeStart 的方法则是相反的,如果把这两个值首先都设置成0.5然后慢慢改变成0和1的时候就会变成第二个动画,配合 lineWidth 则曲线会慢慢变粗,这里的很多属性都是支持动画的。</p>    <p>示例代码</p>    <pre>  <code class="language-objectivec">    private funcanimation1() {          letanimation = CABasicAnimation(keyPath: "strokeEnd")          animation.fromValue = 0          animation.toValue = 1          animation.duration = 2          layer.addAnimation(animation, forKey: "")      }         private funcanimation2() {          layer.strokeStart = 0.5          layer.strokeEnd = 0.5             letanimation = CABasicAnimation(keyPath: "strokeStart")          animation.fromValue = 0.5          animation.toValue = 0          animation.duration = 2             letanimation2 = CABasicAnimation(keyPath: "strokeEnd")          animation2.fromValue = 0.5          animation2.toValue = 1          animation2.duration = 2             layer.addAnimation(animation, forKey: "")          layer.addAnimation(animation2, forKey: "")      }         private funcanimation3() {          letanimation = CABasicAnimation(keyPath: "lineWidth")          animation.fromValue = 1          animation.toValue = 10          animation.duration = 2          layer.addAnimation(animation, forKey: "")      }  </code></pre>    <h2>应用一下</h2>    <p>前一阵子在仿时光网这个APP,其中有一个Layer的形状很怪异,是这样的</p>    <p><img src="https://simg.open-open.com/show/6afd5c845f024ac1c9381addf4d40638.png"></p>    <p>很明显它可以用 CAShapeLayer + UIBezierPath 来做,思路大概是这样,先移动到左上方的位置,然后向下划线,然后往右划线,然后往上划线,还剩一个盖子,这个盖子就用一个控制点控制曲率,非常简单,代码如下</p>    <pre>  <code class="language-objectivec">letfinalSize = CGSizeMake(CGRectGetWidth(view.frame), 400)  letlayerHeight = finalSize.height * 0.2  letlayer = CAShapeLayer()  letbezier = 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)  </code></pre>    <p>就能画出这样的形状来</p>    <p><img src="https://simg.open-open.com/show/3297d1d2ac6c2d9fa918371882446009.png"></p>    <p>再来一个复杂点的,微信下拉拍小视频的那只眼睛很有趣,来模仿一下那个效果吧,它是这样的</p>    <p><img src="https://simg.open-open.com/show/8e1cbe1e9cd657abafa0318437b4bb7d.gif"></p>    <p>首先你得画出这只眼睛,这是眼睛包括5个部分组成(这个是用OC写的)</p>    <pre>  <code class="language-objectivec">@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;  </code></pre>    <p>然后,还是通过 UIBezierPath 和 CAShapeLayer 这样的老套路来画,代码较多</p>    <pre>  <code class="language-objectivec">- (CAShapeLayer *)eyeFirstLightLayer {      if (!_eyeFirstLightLayer) {          _eyeFirstLightLayer = [CAShapeLayerlayer];          CGPointcenter = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);          UIBezierPath *path = [UIBezierPathbezierPathWithArcCenter: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 = [UIColorblackColor].CGColor;          _eyeFirstLightLayer.lineWidth = 5.f;          _eyeFirstLightLayer.path = path.CGPath;          _eyeFirstLightLayer.fillColor = [UIColorclearColor].CGColor;          _eyeFirstLightLayer.strokeColor = [UIColorwhiteColor].CGColor;      }      return _eyeFirstLightLayer;  }     - (CAShapeLayer *)eyeSecondLightLayer {      if (!_eyeSecondLightLayer) {          _eyeSecondLightLayer = [CAShapeLayerlayer];          CGPointcenter = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);          UIBezierPath *path = [UIBezierPathbezierPathWithArcCenter: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 = [UIColorblackColor].CGColor;          _eyeSecondLightLayer.lineWidth = 5.f;          _eyeSecondLightLayer.path = path.CGPath;          _eyeSecondLightLayer.fillColor = [UIColorclearColor].CGColor;          _eyeSecondLightLayer.strokeColor = [UIColorwhiteColor].CGColor;         }      return _eyeSecondLightLayer;  }     - (CAShapeLayer *)eyeballLayer {      if (!_eyeballLayer) {          _eyeballLayer = [CAShapeLayerlayer];          CGPointcenter = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);          UIBezierPath *path = [UIBezierPathbezierPathWithArcCenter: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 = [UIColorblackColor].CGColor;          _eyeballLayer.lineWidth = 1.f;          _eyeballLayer.path = path.CGPath;          _eyeballLayer.fillColor = [UIColorclearColor].CGColor;          _eyeballLayer.strokeColor = [UIColorwhiteColor].CGColor;          _eyeballLayer.anchorPoint = CGPointMake(0.5, 0.5);         }      return _eyeballLayer;  }     - (CAShapeLayer *)topEyesocketLayer {      if (!_topEyesocketLayer) {          _topEyesocketLayer = [CAShapeLayerlayer];          CGPointcenter = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);          UIBezierPath *path = [UIBezierPathbezierPath];          [pathmoveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];          [pathaddQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)                      controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y - center.y - 20)];          _topEyesocketLayer.borderColor = [UIColorblackColor].CGColor;          _topEyesocketLayer.lineWidth = 1.f;          _topEyesocketLayer.path = path.CGPath;          _topEyesocketLayer.fillColor = [UIColorclearColor].CGColor;          _topEyesocketLayer.strokeColor = [UIColorwhiteColor].CGColor;      }      return _topEyesocketLayer;  }     - (CAShapeLayer *)bottomEyesocketLayer {      if (!_bottomEyesocketLayer) {          _bottomEyesocketLayer = [CAShapeLayerlayer];          CGPointcenter = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);          UIBezierPath *path = [UIBezierPathbezierPath];          [pathmoveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];          [pathaddQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)                      controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y + center.y + 20)];          _bottomEyesocketLayer.borderColor = [UIColorblackColor].CGColor;          _bottomEyesocketLayer.lineWidth = 1.f;          _bottomEyesocketLayer.path = path.CGPath;          _bottomEyesocketLayer.fillColor = [UIColorclearColor].CGColor;          _bottomEyesocketLayer.strokeColor = [UIColorwhiteColor].CGColor;         }      return _bottomEyesocketLayer;  }  </code></pre>    <p>然后更改一下某些属性的值,方便稍后的动画</p>    <pre>  <code class="language-objectivec">- (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;  }  </code></pre>    <p>最后根据 UIScrollView 的 contentOffset 来控制各种属性,办法较笨,但管用。</p>    <pre>  <code class="language-objectivec">- (void)animationWith:(CGFloat)y {      CGFloatflag = 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;          }      }  }  </code></pre>    <h2>最后</h2>    <p>总之使用 UIbezierPath 和 CAShapeLayer 可以画出你想要的任何形状,没有它做不到,只有你想不到,搞定了它们你就可以轻松定制你想要的任何控件了。</p>    <h2>微信Demo</h2>    <p>代码</p>    <p><a href="/misc/goto?guid=4959732765977224716" rel="nofollow,noindex">https://github.com/J0HDev/WXVideo</a></p>    <p> </p>    <p>来自:http://ios.jobbole.com/92213/</p>    <p> </p>