美文网首页
iOS: JHChainableAnimations 源码阅读

iOS: JHChainableAnimations 源码阅读

作者: 杨柳小易 | 来源:发表于2017-03-08 11:30 被阅读123次

<code>JHChainableAnimations</code>代码阅读

<strong><code>JHChainableAnimations</code>2.0版本代码还是有点变化的,本篇章阅读的是2.0之前的版本,原理上基本一致(别问我为什么,因为项目中直接拉的源码是2.0之前的),以后有空分析一下2.0的变化部分</strong>

读懂代码之前,最好能提前看一下 <code>CAKeyframeAnimation</code>,因为动画最后的操作,最终都是通过这个完成的。

<code> JHKeyframeAnimation </code>声明如下:

@interface JHKeyframeAnimation : CAKeyframeAnimation
// From https://github.com/NachoSoto/NSBKeyframeAnimation
typedef double(^NSBKeyframeAnimationFunctionBlock)(double t, double b, double c, double d);
@property (nonatomic, copy) NSBKeyframeAnimationFunctionBlock functionBlock;
@property(strong, nonatomic) id fromValue;
@property(strong, nonatomic) id toValue;
-(void) calculate;
@end

本文假设已经了解过<code>CAKeyframeAnimation</code>,所以对此不做深入讨论

<code>JHChainableAnimations</code> 是一个方便进行动画操作的三方库,是基于<code>CAAnimation</code>进行封装的

<code>JHChainableAnimations</code>支持链式调用。
链式调用的实现如下:

-(JHChainableFloat)moveY {
    JHChainableFloat chainable = JHChainableFloat(f) {
        
        ······
        ······
        return self;
    };
    return chainable;
}

函数返回一个<code>block block</code>中返回<code>self</code>所以在执行完bock之后可以继续调用self的方法。

下面通过一个栗子来看看具体是如何实现的。

upperView.bounce.moveY(100).animateWithCompletion(0.2f, JHAnimationCompletion(){
            ///TODO THINGS
        });

栗子的作用是<code>Y</code>方向移动100 0.2s完成,并且指定了完成这个动画后做的block

要读懂代码,首先要知道几个数组,所有动画开始,执行,结束的block都放在这些数组中

// Arrays of animations to be grouped
@property (strong, nonatomic) NSMutableArray *JHAnimations;

// Grouped animations
@property (strong, nonatomic) NSMutableArray *JHAnimationGroups;

// Code run at the beginning of an animation link to calculate values
@property (strong, nonatomic) NSMutableArray *JHAnimationCalculationActions;

// Code run after animation is completed
@property (strong, nonatomic) NSMutableArray *JHAnimationCompletionActions;

就不做翻译了,就是动画执行前,执行的动画,执行动画的group 以及动画执行后的block数组.

-(JHChainableFloat)moveY {
    JHChainableFloat chainable = JHChainableFloat(f) {
        /// 把block加入到动画执行前的数组中
        [self addAnimationCalculationAction:^(UIView *weakSelf) {
            JHKeyframeAnimation *positionAnimation = [weakSelf basicAnimationForKeyPath:@"position.y"];
            positionAnimation.fromValue = @(weakSelf.layer.position.y);
            positionAnimation.toValue = @(weakSelf.layer.position.y+f);
            /// 要在CAAnimationGroup 中执行的动画,统一放在JHAnimations 数组中,
            [weakSelf addAnimationFromCalculationBlock:positionAnimation];
        }];
        
        /// 把 block 加入到动画执行完成后的数组中
        [self addAnimationCompletionAction:^(UIView *weakSelf) {
            CGPoint position = weakSelf.layer.position;
            position.y += f;
            weakSelf.layer.position = position;
        }];
        
        return self;
    };
    return chainable;
}

<code> moveY </code>做的事情比较简单

  • 生成动画开始执行前的block 加入到开始执行前的的链式调用数组中,该block的主要作用是 生成 <code> JHKeyframeAnimation </code>对象并且放入到数组中,这个数组的动画是要放入<code> CAAnimationGroup </code>中的。<strong>A</strong>,我们标记一下,后面会用到这个block
  • 生成动画完成后的block ,并且加入完成后的数组<code>JHAnimationCompletionActions</code>中。
-(void) addAnimationCalculationAction:(JHAnimationCalculationAction)action {
    NSMutableArray *actions = [self.JHAnimationCalculationActions lastObject];
    [actions addObject:action];
}

<code> addAnimationCalculationAction </code>加单的把block加入到 <code>JHAnimationCalculationActions</code> 数组中,取出last 数组,每一组动画都是单独放在一个数组中,链式调用有可能有好几组动画过程。每一个动画过程放在一个单独的数组。其他的数组作用也是这样子。

-(JHChainableAnimationWithCompletion)animateWithCompletion {
    JHChainableAnimationWithCompletion chainable = JHChainableAnimationWithCompletion(duration, completion) {
        /// 取出动画group 
        CAAnimationGroup *group = [self.JHAnimationGroups lastObject];
        /// 设置时间
        group.duration = duration;
        /// 设置动画完成后的block
        self.animationCompletion = completion;
        /// 开始动画过程
        [self animateChain];
        /// 返回self 支持链式调用
        return self;
    };
    return chainable;
}

<code> animateWithCompletion </code> 中设置了<code>group</code>, 然后调用 <code> animateChain </code>执行动画。

-(void) animateChain {
    /// 检查数据是否合法
    [self sanityCheck];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    /// 设置动画完成后的block
    [CATransaction setCompletionBlock:^{
        [self.layer removeAnimationForKey:@"AnimationChain"];
        /// 会检查动画数组里还有没有要执行的动画,如果没有clean 如果有,继续调用 animateChain 函数。链式调用的精髓这里
        [self chainLinkDidFinishAnimating];
    }];
    /// 执行动画过程
    [self animateChainLink];
    
    [CATransaction commit];
    
    [self executeCompletionActions];
}

-(void) animateChainLink {
    /// 锚点放在中心点
    [self makeAnchorFromX:0.5 Y:0.5];
    NSMutableArray *actionCluster = [self.JHAnimationCalculationActions firstObject];
    /// 调用刚才 标记的 A 处
    for (JHAnimationCalculationAction action in actionCluster) {
        __weak UIView *weakSelf = self;
        /// 执行 动画开始前的block 生成 JHKeyframeAnimation 对象,并且加入到动画的数组中
        action(weakSelf);
    }
    CAAnimationGroup *group = [self.JHAnimationGroups firstObject];
    NSMutableArray *animationCluster = [self.JHAnimations firstObject];
    for (JHKeyframeAnimation *animation in animationCluster) {
        animation.duration = group.duration;
        /// 生成 values 数组,(动画过程)
        [animation calculate];
    }
    group.animations = animationCluster;
    [self.layer addAnimation:group forKey:@"AnimationChain"];
    
    // 更新一下约束。
    NSTimeInterval delay = MAX(group.beginTime - CACurrentMediaTime(), 0.0);
    [self.class animateWithDuration:group.duration
                              delay:delay
                            options:0
                         animations:^{
        [self updateConstraints];
    } completion:nil];
}

animateChainLink 函数首先把锚点放在中心,然后执行 刚才标记的 A 处的block(生成JHKeyframeAnimation 对象,并且放入 动画数组中)

-(void) chainLinkDidFinishAnimating {
    /// 删除已经执行过的动画
    [self.JHAnimationCompletionActions removeObjectAtIndex:0];
    [self.JHAnimationCalculationActions removeObjectAtIndex:0];
    [self.JHAnimations removeObjectAtIndex:0];
    /// 删除已经执行过的group
    [self.JHAnimationGroups removeObjectAtIndex:0];
    [self sanityCheck];
    /// 如果响应连中没有要执行的动画了就清空
    if (self.JHAnimationGroups.count == 0) {
        [self clear];
        if (self.animationCompletion) {
            JHAnimationCompletion completion = self.animationCompletion;
            self.animationCompletion = nil;
            completion();
        }
    }
    else {
        /// 如果响应链中还有要执行的动画,继续调用
        [self animateChain];
    }
}

<code>chainLinkDidFinishAnimating</code>一组动画执行完毕后,结束的调用。

  • 首先会删除已经执行过的动画。
  • 然后检查一下剩余动画group的的数量是否合法。
  • 如果有剩余的动画组没执行,则继续调用<code>animateChain</code>,如果没有,则调用<code>animationCompletion</code>block

<strong>下面代码过程就是根据 JHKeyframeAnimation 的 fromValue 和 toValue 计算出一组值,赋值给 values对象。 </strong>

-(void) calculate {
    [self createValueArray];
}

- (void) createValueArray {
    if (self.fromValue && self.toValue && self.duration) {
        if ([self.fromValue isKindOfClass:[NSNumber class]] && [self.toValue isKindOfClass:[NSNumber class]]) {
            self.values = [self valueArrayForStartValue:[self.fromValue floatValue] endValue:[self.toValue floatValue]];
    else {
    ···
    ···
    }
- (NSArray*) valueArrayForStartValue:(CGFloat)startValue endValue:(CGFloat)endValue {
    NSUInteger steps = (NSUInteger)ceil(kFPS * self.duration) + 2;
    
    NSMutableArray *valueArray = [NSMutableArray arrayWithCapacity:steps];
    
    const double increment = 1.0 / (double)(steps - 1);
    
    double progress = 0.0,
    v = 0.0,
    value = 0.0;
    
    NSUInteger i;
    for (i = 0; i < steps; i++)
    {
        v = self.functionBlock(self.duration * progress * 1000, 0, 1, self.duration * 1000);
        value = startValue + v * (endValue - startValue);
        
        [valueArray addObject:@(value)];
        
        progress += increment;
    }
    
    return [NSArray arrayWithArray:valueArray];
}

<code> valueArrayForStartValue </code> 计算出一组值赋值给<code>values</code>

相关文章

网友评论

      本文标题:iOS: JHChainableAnimations 源码阅读

      本文链接:https://www.haomeiwen.com/subject/ollbgttx.html