103240arr0u880uyeau9y8.jpg
  文 / 杨骑滔

  在最近做个一个自定义PageControl——KYAnimatedPageControl中,我实现了CALayer的形变动画以及CALayer的弹性动画,效果先过目:

103248reyua0xn13oeerr0.jpg
  小球是由弧AB、弧BC、弧CD、弧DA 四段组成,其中每段弧都绑定两个控制点:弧AB 绑定的是 C1 、 C2;弧BC 绑定的是 C3 、 C4 .....

  如何表达各个点?

  首先,A、B、C、D是四个动点,控制他们动的变量是ScrollView的contentOffset.x。我们可以在-(void)scrollViewDidScroll:UIScrollView *)scrollView中实时获取这个变量,并把它转换成一个控制在 0~1 的系数,取名为factor。

 

 

  1. _factor = MIN(1, MAX(0, (ABS(scrollView.contentOffset.x - self.lastContentOffset) / scrollView.frame.size.width)));

10324985dp3mgfooy3g5s8.jpg
  当然这只是一个通式,我们需要让 图像过(0,0),并且最后衰减到1 。我们可以让原图像先绕X轴翻转180度,也就是加一个负号。然后沿y轴向上平移一个单位。所以稍加变形可以得到如下函数:

103249wot272rksvoi1w2p.jpg
  想看函数的图像?没问题,推荐一个在线查看函数图象的网站 —— Desmos ,把这段公式 1-\left(e^{-5x}\cdot \cos (30x)\right) 复制粘帖进去就可以看到图像。

  改进后的函数图像是这样的:

103249d865e0dm9kgy50kx.jpg
  完美满足了我们 图形过(0,0),震荡衰减到1 的要求。其中式子中的 5 相当于阻尼系数,数值越小幅度越大;式子中的 30 相当于震荡频率 ,数值越大震荡次数越多。

  接下来就需要转换成代码。

  总体思路是创建60帧关键帧(因为屏幕的最高刷新频率就是60FPS),然后把这60帧数据赋值给 CAKeyframeAnimation 的 values 属性。

  用以下代码生成60帧后保存到一个数组并返回它,其中//1就是利用刚才的公式创建60个数值:

 

 

  1. +(NSMutableArray *) animationValues:(id)fromValue toValue:(id)toValue usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity duration:(CGFloat)duration{
    • //60个关键帧
      • NSInteger numOfPoints = duration * 60;
        • NSMutableArray *values = [NSMutableArray arrayWithCapacity:numOfPoints];
          • for (NSInteger i = 0; i < numOfPoints; i++) {
            • [values addObject:@(0.0)];
              • }
                • //差值
                  • CGFloat d_value = [toValue floatValue] - [fromValue floatValue];
                    • for (NSInteger point = 0; point CGFloat x = (CGFloat)point / (CGFloat)numOfPoints;
                      • CGFloat value = [toValue floatValue] - d_value * (pow(M_E, -damping * x) * cos(velocity * x)); //1 y = 1-e^{-5x} * cos(30x)
                        • values[point] = @(value);
                          • }
                            • return values;
                              • }
复制代码
  接下来创建一个对外的类方法,并返回一个 CAKeyframeAnimation :

 

 

  1. +(CAKeyframeAnimation *)createSpring:(NSString *)keypath duration:(CFTimeInterval)duration usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity fromValue:(id)fromValue toValue:(id)toValue{
    • CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:keypath];
      • NSMutableArray *values = [KYSpringLayerAnimation animationValues:fromValue toValue:toValue usingSpringWithDamping:damping * dampingFactor initialSpringVelocity:velocity * velocityFactor duration:duration];
        • anim.values = values;
          • anim.duration = duration;
            • return anim;
              • }
复制代码
  另一个关键

  以上,我们创建了 CAKeyframeAnimation 。但是这些values到底是对谁起作用的呢?如果你熟悉CoreAnimation的话,没错,是对传入的keypath起作用。而这些keypath其实就是CALayer中的属性@property。比如,之所以当传入的keypath为transform.rotation.x时CAKeyframeAnimation会让layer发生旋转,就是因为CAKeyframeAnimation发现CALayer中有这么个属性叫transform,于是动画就发生了。现在我们需要改变的是主题一中的那个factor变量,所以,很自然地想到,我们可以给CALayer补充一个属性名为factor就行了,这样CAKeyframeAnimation加到layer上时发现layer有这个factor属性,就会把60帧不同的values赋值给factor。当然我们要把fromValue和toValue控制在0~1:

 

 

  1. CAKeyframeAnimation *anim = [KYSpringLayerAnimation createSpring:@factor duration:0.8 usingSpringWithDamping:0.5 initialSpringVelocity:3 fromValue:@(1) toValue:@(0)];
    • self.factor = 0;
      • [self addAnimation:anim forKey:@restoreAnimation];
复制代码
  最后一步,虽然CAKeyframeAnimation实时地去改变了我们想要的factor,但我们还得通知屏幕刷新,这样才能看到动画。

 

 

  1. +(BOOL)needsDisplayForKey:(NSString *)key{
    • if ([key isEqual:@factor]) {
      • return YES;
        • }
          • return [super needsDisplayForKey:key];
            • }
复制代码
  上面的代码通知屏幕当factor发生变化时,实时刷新屏幕。

  最后的最后,你需要重载CALayer中的-(id)initWithLayer:GooeyCircle *)layer方法,为了保证动画能连贯起来,你需要拷贝前一个状态的layer及其所有属性。

 

 

  1. -(id)initWithLayer:(GooeyCircle *)layer{
    • self = [super initWithLayer:layer];
      • if (self) {
        • self.indicatorSize = layer.indicatorSize;
          • self.indicatorColor = layer.indicatorColor;
            • self.currentRect = layer.currentRect;
              • self.lastContentOffset = layer.lastContentOffset;
                • self.scrollDirection = layer.scrollDirection;
                  • self.factor = layer.factor;
                    • }
                      • return self;
                        • }
复制代码
  总结:

  做自定义的动画最关键的就是要有变量,要有输入。像滑动ScrollView的时候,滑动的距离就是动画的输入,可以作为动画的变量;当没有交互的时候,可以用CAAnimation。其实CAAnimation底层就有个定时器,而定时器的作用就是可以产生变量,时间就是变量,就可以产生变化的输入,就能看到变化的状态,连起来就是动画了。

声明:游资网登载此文出于传递信息之目的,绝不意味着游资网赞同其观点或证实其描述。
锐亚教育

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