151445288x12pwmpeaw4o2.png
151445oyo554qijed55e4e.png
  蓝色部分是我们的弧,因为弧太短,我们用灰色的线来表示弧的运动轨迹,这样看的更清楚些;

  具体的节点数值我没有标出,能看出来,第2阶段可以用重绘弧的方式实现,看上去比第1阶段还要简单一些。

  如果是在项目中,第2阶段就会选用重绘弧方案了,减少方案数可以降低项目的维护成本,毕竟多一种方案就多一些Bug的可能性,项目中新人的学习成本也大一些。

  所以,第二篇就这么完事了吗?

  当然不是,我们现在是学习,是在玩,方案当然是越多越好。

  接下来,我们要换一种方式来实现第2阶段动画,至于重绘弧的方式,有兴趣的同学可以自己实现一下,加深一下印象。

  换一种思路

  大家请再次观察一下这张图:

151446gvjmn7ngmm93gz7j.png
  我们可不可以这样理解,灰色部分是一条弧,弧OD只是它的一部分,这部分涂上了蓝色。

  再进一步说,我们将灰色改成透明,那是不是可以这样认为,弧本身是透明的,我们将弧上O和D之间的部分涂上了蓝色。

  如果我们不停的改变O和D的位置,弧涂上蓝色部分的位置就在不断变化,看上去就像一段蓝色弧在移动。

  由这个思路,我们可以有一种新方案,stroke方案。

  stroke方案

  看到stroke,有过绘制经验的同学可能想起了strokeColor、lineWidth等词,没错,就是这个stroke,没用过的同学也不用着急,后文会慢慢讲。

  我们的弧可以认为是一条路径(后文我们用path这个词,例如第1阶段我们用的UIBezierPath),stroke就是沿着path涂色。

  那么重绘弧的方案可以理解为,我们不停的创建新的蓝色path,每条path的起点和终点不一样,来形成动画。

  而stroke方案则是,我们只创建一条透明的path,我们不断改变其涂蓝色的起点和终点,来形成动画。

  假设我们给涂蓝色的起点终点分别命名为SS(strokeStart)和SE(strokeEnd),

  再假设SS、SE不用具体数值表示,而用0~1之间的值表示,代表其在path的哪个位置(比如0.1就是距path起点的10%处),

  我们用一条直线path来示例,依然用灰色代表透明部分,用蓝色代表stroke部分,大家请看下图,看看SS、SE取值怎么影响path的样子:

151446jnsrvmynar31msrf.png
  图中示意了4种情况下,SS、SE取值形成的path的样子,大家应该已经有感觉了,接下来我们示范一下SS、SE动态变化时path的样子。

  假设SS=0不变,SE从0变化到1,由于SS与SE之间的部分就是要涂色的部分,看上去就是涂色的部分从path的起点开始,越来越接近path终点,直到到达终点,效果就是path从无到有,如下图:

151452c6i1qzzmc5u7q36m.png
  图中左下方有一个d,代表x轴上弧圆心到圆左侧的距离。

  由此我们可以得出变量的表达式,手写太长,我偷个懒,直接上一段代码,代码中的kRadius是小圆的半径:

 

 

  1. // 小圆圆心
  2. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  3. // d(x轴上弧圆心与小圆左边缘的距离)
  4. CGFloat d = ?; // 此时还不知道d的表达式
  5. // 弧圆心
  6. CGPoint arcCenter = CGPointMake(center.x - kRadius - d, center.y);
  7. // 弧半径
  8. CGFloat arcRadius = kRadius * 2 + d;
  9. // O(origin)
  10. CGFloat origin = M_PI * 2;
  11. // D(dest)
  12. CGFloat dest = M_PI * 2 - asin(kRadius * 2 / arcRadius); // 2π - 图中的θ角弧度

  思路OK了,弧也找到了,现在可以写代码了。

  写代码

  这次我们要用到CAShapeLayer了,CAShapeLayer是CALayer的子类,请看它的三个属性,如图:

1514531naz8vdhdrndt0d6.png
  Perfect!

  我们要的它全都有,上代码:

 

 

  1. self.moveArcLayer = [CAShapeLayer layer];
    • [self.layer addSublayer:self.moveArcLayer];
      • self.moveArcLayer.frame = self.layer.bounds;
        • // 弧的path
          • UIBezierPath *moveArcPath = [UIBezierPath bezierPath];
            • // 小圆圆心
              • CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
                • // d(x轴上弧圆心与小圆左边缘的距离)
                  • CGFloat d = kRadius / 2;
                    • // 弧圆心
                      • CGPoint arcCenter = CGPointMake(center.x - kRadius - d, center.y);
                        • // 弧半径
                          • CGFloat arcRadius = kRadius * 2 + d;
                            • // O(origin)
                              • CGFloat origin = M_PI * 2;
                                • // D(dest)
                                  • CGFloat dest = M_PI * 2 - asin(kRadius * 2 / arcRadius);
                                    • [moveArcPath addArcWithCenter:arcCenter radius:arcRadius startAngle:origin endAngle:dest clockwise:NO];
                                      • self.moveArcLayer.path = moveArcPath.CGPath;
                                        • self.moveArcLayer.lineWidth = 3;
                                          • self.moveArcLayer.strokeColor = [UIColor blueColor].CGColor;
                                            • self.moveArcLayer.fillColor = nil;
                                              •  
                                              • // SS(strokeStart)
                                                • CGFloat SSFrom = 0;
                                                  • CGFloat SSTo = 0.9;
                                                    •  
                                                    • // SE(strokeEnd)
                                                      • CGFloat SEFrom = 0.1;
                                                        • CGFloat SETo = 1;
                                                          •  
                                                          • // end status
                                                            • self.moveArcLayer.strokeStart = SSTo;
                                                              • self.moveArcLayer.strokeEnd = SETo;
                                                                •  
                                                                • // animation
                                                                  • CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:@strokeStart];
                                                                    • startAnimation.fromValue = @(SSFrom);
                                                                      • startAnimation.toValue = @(SSTo);
                                                                        •  
                                                                        • CABasicAnimation *endAnimation = [CABasicAnimation animationWithKeyPath:@strokeEnd];
                                                                          • endAnimation.fromValue = @(SEFrom);
                                                                            • endAnimation.toValue = @(SETo);
                                                                              •  
                                                                              • CAAnimationGroup *step2 = [CAAnimationGroup animation];
                                                                                • step2.animations = @[startAnimation, endAnimation];
                                                                                  • step2.duration = kStep2Duration;
                                                                                    • step2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
                                                                                      •  
                                                                                      • [self.moveArcLayer addAnimation:step2 forKey:nil];
复制代码
  很清晰,而且代码也不多,所以说现在的系统库和第三方库已经很强大了,思路有了,实现起来一般不会太难。

  发散

  stroke方案很适合处理沿path变化的动效,比如前文中的这张图,

  其实就是自定义进度条的雏形:

none.gif
  这里的path是直线,那就是直线的进度条,如果是圆,那就是圆形的进度条,如果是很个性的形状,那就是很个性的进度条。

  除此之外,还有很多CAShapeLayer的path和stroke实现的动效,因为path的无限可能性,也造就了很多stroke方案的炫动效,比如这个动画写字的,炫到飞起!

none.gif
  相关阅读iOS开发:停止不必要的UI动效设计

锐亚教育

锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛