iOS: JHChainableAnimations 源码阅读

JHChainableAnimations代码阅读

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

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

JHKeyframeAnimation 声明如下:

@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

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

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

JHChainableAnimations支持链式调用。
链式调用的实现如下:

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

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

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

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

栗子的作用是Y方向移动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;
}

moveY 做的事情比较简单

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

addAnimationCalculationAction 加单的把block加入到 JHAnimationCalculationActions 数组中,取出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;
}

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

-(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];
    }
}

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

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

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

-(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];
}

valueArrayForStartValue 计算出一组值赋值给values

你可能感兴趣的:(iOS: JHChainableAnimations 源码阅读)