iOS: JHChainableAnimations 源码阅读

<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>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,089评论 4 62
  • B1组婷婷#周检视# 0708-0714 百日目标检视 1. 目标1每天平甩功,30分钟 2. 目标2熊猫读书每天...
    金金Amy阅读 135评论 2 2
  • 他爱你,你也刚好爱他,这是最好的爱情。 2017年9月8日凌晨4点,薛之谦和高磊鑫同时发微博,宣布两人复合!微博...
    半世大叔阅读 710评论 0 3
  • 昨昔灯火通明,今日孤枕难眠。 见一孩与父母游走嬉戏,余心生羡慕。想昨朝,余亦随父母游走四方,心未生孤寂,今日感人世...
    若鱼_吻风阅读 388评论 8 2
  • 郑璐 宜昌 焦点网络初级七期 原创持续分享第18天 今天朋友圈刷爆了“请给我一顶圣诞帽@微信官网”,这是...
    迷你旅客阅读 329评论 1 4