CAShapeLayer与UIBezierPath实现注水动画与正余弦水波纹动画

JanJVLN 7年前
   <p>初步学习了CoreAnimation框架,总结了几个动画效果,主要是通过CAShapeLayer与贝塞尔曲线实现。先看下效果</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d512e070635dcbdb1de29aeebd12a7da.gif"></p>    <p style="text-align:center">1.gif</p>    <p><strong>扇形下载进度</strong></p>    <p>要实现扇形的下载进度,有两种方法, 这里先使用第一种:</p>    <p>1.使用设置UIBezierPath的角度</p>    <p>2.使用 CAShapeLayer的stokeEnd属性</p>    <pre>  <code class="language-objectivec">//设置中心点      CGPoint point = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);        //起点位置      CGFloat startAngle = - M_PI /2;        //结束位置      CGFloat endAngle = self.progress *M_PI *2 + startAngle;          UIBezierPath *path =[UIBezierPath bezierPathWithArcCenter:point radius:self.bounds.size.width/2 startAngle:startAngle endAngle:endAngle clockwise:1];         //画一根到 圆心的线      [path addLineToPoint:point];          //通过layer绘制      CAShapeLayer *layer =[CAShapeLayer layer];          layer.path = path.CGPath;        layer.fillColor =[UIColor colorWithRed:0.47 green:0.83 blue:0.98 alpha:1].CGColor;        [self.layer addSublayer:layer];</code></pre>    <p><strong>圆形进度</strong></p>    <pre>  <code class="language-objectivec">首先 我们需要一个背景层 一个前景层,一个路径供给两个layer使用。这里我们使用改变stokeEnd 来改变圆弧的进度,代码里增加了一点渐变     self.backLayer =[CAShapeLayer layer];      self.backLayer.fillColor  =[UIColor clearColor].CGColor;      self.backLayer.frame = self.bounds;      self.backLayer.lineWidth = 4;      self.backLayer.strokeColor =[UIColor lightGrayColor].CGColor;      [self.layer addSublayer:self.backLayer];        self.foreLayer =[CAShapeLayer layer];      self.foreLayer.fillColor  =[UIColor clearColor].CGColor;      self.foreLayer.frame = self.bounds;      self.foreLayer.strokeColor =[UIColor redColor].CGColor;      self.foreLayer.lineWidth = 4;      [self.layer addSublayer:self.foreLayer];          UIBezierPath *path=  [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.bounds.size.width/2-2 startAngle:-M_PI_2 endAngle:M_PI *1.5 clockwise:YES];      self.backPath = path;        self.backLayer.path = self.backPath.CGPath;      self.foreLayer.path = self.backPath.CGPath;      self.foreLayer.strokeEnd = 0;          self.gradientLayerLeft =[CAGradientLayer layer];      self.gradientLayerLeft.frame = self.bounds;        self.gradientLayerLeft.colors =@[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor blueColor].CGColor];      self.gradientLayerLeft.locations = @[@0,@0.5,@1];      self.gradientLayerLeft.startPoint = CGPointMake(0, 0);      self.gradientLayerLeft.endPoint = CGPointMake(0, 1);      [self.layer addSublayer:self.gradientLayerLeft];          //设置mask      [self.gradientLayerLeft setMask:self.foreLayer];  -(void)setProgressValue:(CGFloat)progressValue  {      _progressValue = progressValue;      self.foreLayer.strokeEnd = progressValue;      self.label.text = [NSString stringWithFormat:@"%.f%%",progressValue *100];  }</code></pre>    <p><strong>一个加载动画</strong></p>    <p>引自:旋转加载动画</p>    <p>可以重点学习下做动画的思路,</p>    <pre>  <code class="language-objectivec">//核心代码 在动画开始的时候做一些平移旋转  - (void)animationDidStart:(CAAnimation *)anim{      [UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState animations:^{            self.ball_1.transform = CGAffineTransformMakeTranslation(-BALL_RADIUS, 0);          self.ball_1.transform = CGAffineTransformScale(self.ball_1.transform, 0.7, 0.7);            self.ball_3.transform = CGAffineTransformMakeTranslation(BALL_RADIUS, 0);          self.ball_3.transform = CGAffineTransformScale(self.ball_3.transform, 0.7, 0.7);              self.ball_2.transform = CGAffineTransformScale(self.ball_2.transform, 0.7, 0.7);      } completion:^(BOOL finished) {          [UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseIn  | UIViewAnimationOptionBeginFromCurrentState animations:^{              self.ball_1.transform = CGAffineTransformIdentity;              self.ball_3.transform = CGAffineTransformIdentity;              self.ball_2.transform = CGAffineTransformIdentity;          } completion:NULL];        }];  }</code></pre>    <p><strong>使用正余弦做的注水动画</strong></p>    <p>在使用正余弦做注水动画时,先了解下正余弦</p>    <pre>  <code class="language-objectivec">/**   正弦曲线公式可表示为y=Asin(ωx+φ)+k:   A,振幅,最高和最低的距离   W,角速度,用于控制周期大小,单位x中的起伏个数   K,偏距,曲线整体上下偏移量   φ,初相,左右移动的值     这个效果主要的思路是添加两条曲线 一条正玄曲线、一条余弦曲线 然后在曲线下添加深浅不同的背景颜色,从而达到波浪显示的效果   */</code></pre>    <p>我们要做的 就是使用两条正余弦,但是这两条正余弦, 波峰需要对应波谷,有两种方法:</p>    <ol>     <li>使用for循环分别拼接正余弦的路径</li>    </ol>    <pre>  <code class="language-objectivec">-(void)updateWave  {      //波浪宽度      CGFloat waterWaveWidth = self.bounds.size.width;      //初始化运动路径      CGMutablePathRef path = CGPathCreateMutable();        CGMutablePathRef maskPath = CGPathCreateMutable();      //设置起始位置      CGPathMoveToPoint(path, nil, 0, _waveY);        //设置起始位置      CGPathMoveToPoint(maskPath, nil, 0, _waveY);      //初始化波浪其实Y为偏距      CGFloat y = _waveY;        //正弦曲线公式为: y=Asin(ωx+φ)+k;      for (float x = 0.0f; x <= waterWaveWidth ; x++) {          y = _waveAmplitude * sin(_wavePalstance * x + _waveX) + _waveY;            CGPathAddLineToPoint(path, nil, x, y);        }      for (float x = 0.0f; x <= waterWaveWidth ; x++) {          y = _waveAmplitude * cos(_wavePalstance * x + _waveX) + _waveY;            CGPathAddLineToPoint(maskPath, nil, x, y);      }      [self updateLayer:_waveLayer1 path:path];      [self updateLayer:_waveLayer2 path:maskPath];    }  -(void)updateLayer:(CAShapeLayer *)layer path:(CGMutablePathRef )path  {      //填充底部颜色      CGFloat waterWaveWidth = self.bounds.size.width;      CGPathAddLineToPoint(path, nil, waterWaveWidth, self.bounds.size.height);      CGPathAddLineToPoint(path, nil, 0, self.bounds.size.height);      CGPathCloseSubpath(path);      layer.path = path;       CGPathRelease(path);  }</code></pre>    <p>2.使用单个for循环只是 设置 另一条曲线的y值相反即可实现两条正余弦的效果 ,最后一个动画中会有说明</p>    <pre>  <code class="language-objectivec">for (int x = 0; x<WIDTH; x++) {          y = waveHeight*sinf(0.01*waveCurvature*x+offSetValue*0.045);          CGPathAddLineToPoint(path, nil, x, y);          //遮罩层的路径与之相反          masky = -y;          CGPathAddLineToPoint(maskPath, nil, x, masky);      }</code></pre>    <p>但是我们有个需求就是改变 波浪的高度, 实现注水的百分比,就需要设置波浪的偏距</p>    <pre>  <code class="language-objectivec">-(void)updateWaveY  {      CGFloat targetY = self.bounds.size.height - _progress * self.bounds.size.height;      if (_waveY < targetY) {          _waveY += 2;      }      if (_waveY > targetY ) {          _waveY -= 2;      }  }</code></pre>    <p><strong>正余弦动画2</strong></p>    <p>如果有个需求 ,比如一个小船 随着波浪的波动而起伏</p>    <p>那我们就需要计算 波浪的位置,然后设置小船的frame</p>    <pre>  <code class="language-objectivec">//这里使用创建正余弦的第二种方法      for (int x = 0; x<WIDTH; x++) {          y = waveHeight*sinf(0.01*waveCurvature*x+offSetValue*0.045);          CGPathAddLineToPoint(path, nil, x, y);          //遮罩层的路径与之相反          masky = -y;          CGPathAddLineToPoint(maskPath, nil, x, masky);      }  计算出实浪波动时,最中间的位置,设置小船的frame    CGFloat CentY = waveHeight*sinf(0.01*waveCurvature*WIDTH/2+offSetValue*0.045);      CGRect boardViewFrame = [boardView frame];      boardViewFrame.origin.y = 100-waveHeight+CentY;      boardView.frame = boardViewFrame;</code></pre>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/16fd028ee636</p>    <p> </p>