iOS: JHChainableAnimations 源碼閱讀

<code>JHChainableAnimations</code>代碼閱讀

<strong><code>JHChainableAnimations</code>2.0版本代碼還是有點(diǎn)變化的,本篇章閱讀的是2.0之前的版本,原理上基本一致(別問我為什么,因?yàn)轫?xiàng)目中直接拉的源碼是2.0之前的),以后有空分析一下2.0的變化部分</strong>

讀懂代碼之前,最好能提前看一下 <code>CAKeyframeAnimation</code>,因?yàn)閯?dòng)畫最后的操作,最終都是通過這個(gè)完成的。

<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

本文假設(shè)已經(jīng)了解過<code>CAKeyframeAnimation</code>,所以對(duì)此不做深入討論

<code>JHChainableAnimations</code> 是一個(gè)方便進(jìn)行動(dòng)畫操作的三方庫(kù),是基于<code>CAAnimation</code>進(jìn)行封裝的

<code>JHChainableAnimations</code>支持鏈?zhǔn)秸{(diào)用。
鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)如下:

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

函數(shù)返回一個(gè)<code>block block</code>中返回<code>self</code>所以在執(zhí)行完bock之后可以繼續(xù)調(diào)用self的方法。

下面通過一個(gè)栗子來看看具體是如何實(shí)現(xiàn)的。

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

栗子的作用是<code>Y</code>方向移動(dòng)100 0.2s完成,并且指定了完成這個(gè)動(dòng)畫后做的block

要讀懂代碼,首先要知道幾個(gè)數(shù)組,所有動(dòng)畫開始,執(zhí)行,結(jié)束的block都放在這些數(shù)組中

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

就不做翻譯了,就是動(dòng)畫執(zhí)行前,執(zhí)行的動(dòng)畫,執(zhí)行動(dòng)畫的group 以及動(dòng)畫執(zhí)行后的block數(shù)組.

-(JHChainableFloat)moveY {
    JHChainableFloat chainable = JHChainableFloat(f) {
        /// 把block加入到動(dòng)畫執(zhí)行前的數(shù)組中
        [self addAnimationCalculationAction:^(UIView *weakSelf) {
            JHKeyframeAnimation *positionAnimation = [weakSelf basicAnimationForKeyPath:@"position.y"];
            positionAnimation.fromValue = @(weakSelf.layer.position.y);
            positionAnimation.toValue = @(weakSelf.layer.position.y+f);
            /// 要在CAAnimationGroup 中執(zhí)行的動(dòng)畫,統(tǒng)一放在JHAnimations 數(shù)組中,
            [weakSelf addAnimationFromCalculationBlock:positionAnimation];
        }];
        
        /// 把 block 加入到動(dòng)畫執(zhí)行完成后的數(shù)組中
        [self addAnimationCompletionAction:^(UIView *weakSelf) {
            CGPoint position = weakSelf.layer.position;
            position.y += f;
            weakSelf.layer.position = position;
        }];
        
        return self;
    };
    return chainable;
}

<code> moveY </code>做的事情比較簡(jiǎn)單

  • 生成動(dòng)畫開始執(zhí)行前的block 加入到開始執(zhí)行前的的鏈?zhǔn)秸{(diào)用數(shù)組中,該block的主要作用是 生成 <code> JHKeyframeAnimation </code>對(duì)象并且放入到數(shù)組中,這個(gè)數(shù)組的動(dòng)畫是要放入<code> CAAnimationGroup </code>中的。<strong>A</strong>,我們標(biāo)記一下,后面會(huì)用到這個(gè)block
  • 生成動(dòng)畫完成后的block ,并且加入完成后的數(shù)組<code>JHAnimationCompletionActions</code>中。
-(void) addAnimationCalculationAction:(JHAnimationCalculationAction)action {
    NSMutableArray *actions = [self.JHAnimationCalculationActions lastObject];
    [actions addObject:action];
}

<code> addAnimationCalculationAction </code>加單的把block加入到 <code>JHAnimationCalculationActions</code> 數(shù)組中,取出last 數(shù)組,每一組動(dòng)畫都是單獨(dú)放在一個(gè)數(shù)組中,鏈?zhǔn)秸{(diào)用有可能有好幾組動(dòng)畫過程。每一個(gè)動(dòng)畫過程放在一個(gè)單獨(dú)的數(shù)組。其他的數(shù)組作用也是這樣子。

-(JHChainableAnimationWithCompletion)animateWithCompletion {
    JHChainableAnimationWithCompletion chainable = JHChainableAnimationWithCompletion(duration, completion) {
        /// 取出動(dòng)畫group 
        CAAnimationGroup *group = [self.JHAnimationGroups lastObject];
        /// 設(shè)置時(shí)間
        group.duration = duration;
        /// 設(shè)置動(dòng)畫完成后的block
        self.animationCompletion = completion;
        /// 開始動(dòng)畫過程
        [self animateChain];
        /// 返回self 支持鏈?zhǔn)秸{(diào)用
        return self;
    };
    return chainable;
}

<code> animateWithCompletion </code> 中設(shè)置了<code>group</code>, 然后調(diào)用 <code> animateChain </code>執(zhí)行動(dòng)畫。

-(void) animateChain {
    /// 檢查數(shù)據(jù)是否合法
    [self sanityCheck];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    /// 設(shè)置動(dòng)畫完成后的block
    [CATransaction setCompletionBlock:^{
        [self.layer removeAnimationForKey:@"AnimationChain"];
        /// 會(huì)檢查動(dòng)畫數(shù)組里還有沒有要執(zhí)行的動(dòng)畫,如果沒有clean 如果有,繼續(xù)調(diào)用 animateChain 函數(shù)。鏈?zhǔn)秸{(diào)用的精髓這里
        [self chainLinkDidFinishAnimating];
    }];
    /// 執(zhí)行動(dòng)畫過程
    [self animateChainLink];
    
    [CATransaction commit];
    
    [self executeCompletionActions];
}

-(void) animateChainLink {
    /// 錨點(diǎn)放在中心點(diǎn)
    [self makeAnchorFromX:0.5 Y:0.5];
    NSMutableArray *actionCluster = [self.JHAnimationCalculationActions firstObject];
    /// 調(diào)用剛才 標(biāo)記的 A 處
    for (JHAnimationCalculationAction action in actionCluster) {
        __weak UIView *weakSelf = self;
        /// 執(zhí)行 動(dòng)畫開始前的block 生成 JHKeyframeAnimation 對(duì)象,并且加入到動(dòng)畫的數(shù)組中
        action(weakSelf);
    }
    CAAnimationGroup *group = [self.JHAnimationGroups firstObject];
    NSMutableArray *animationCluster = [self.JHAnimations firstObject];
    for (JHKeyframeAnimation *animation in animationCluster) {
        animation.duration = group.duration;
        /// 生成 values 數(shù)組,(動(dòng)畫過程)
        [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 函數(shù)首先把錨點(diǎn)放在中心,然后執(zhí)行 剛才標(biāo)記的 A 處的block(生成JHKeyframeAnimation 對(duì)象,并且放入 動(dòng)畫數(shù)組中)

-(void) chainLinkDidFinishAnimating {
    /// 刪除已經(jīng)執(zhí)行過的動(dòng)畫
    [self.JHAnimationCompletionActions removeObjectAtIndex:0];
    [self.JHAnimationCalculationActions removeObjectAtIndex:0];
    [self.JHAnimations removeObjectAtIndex:0];
    /// 刪除已經(jīng)執(zhí)行過的group
    [self.JHAnimationGroups removeObjectAtIndex:0];
    [self sanityCheck];
    /// 如果響應(yīng)連中沒有要執(zhí)行的動(dòng)畫了就清空
    if (self.JHAnimationGroups.count == 0) {
        [self clear];
        if (self.animationCompletion) {
            JHAnimationCompletion completion = self.animationCompletion;
            self.animationCompletion = nil;
            completion();
        }
    }
    else {
        /// 如果響應(yīng)鏈中還有要執(zhí)行的動(dòng)畫,繼續(xù)調(diào)用
        [self animateChain];
    }
}

<code>chainLinkDidFinishAnimating</code>一組動(dòng)畫執(zhí)行完畢后,結(jié)束的調(diào)用。

  • 首先會(huì)刪除已經(jīng)執(zhí)行過的動(dòng)畫。
  • 然后檢查一下剩余動(dòng)畫group的的數(shù)量是否合法。
  • 如果有剩余的動(dòng)畫組沒執(zhí)行,則繼續(xù)調(diào)用<code>animateChain</code>,如果沒有,則調(diào)用<code>animationCompletion</code>block

<strong>下面代碼過程就是根據(jù) JHKeyframeAnimation 的 fromValue 和 toValue 計(jì)算出一組值,賦值給 values對(duì)象。 </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> 計(jì)算出一組值賦值給<code>values</code>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,172評(píng)論 4 61
  • B1組婷婷#周檢視# 0708-0714 百日目標(biāo)檢視 1. 目標(biāo)1每天平甩功,30分鐘 2. 目標(biāo)2熊貓讀書每天...
    金金Amy閱讀 155評(píng)論 2 2
  • 他愛你,你也剛好愛他,這是最好的愛情。 2017年9月8日凌晨4點(diǎn),薛之謙和高磊鑫同時(shí)發(fā)微博,宣布兩人復(fù)合!微博...
    半世大叔閱讀 860評(píng)論 0 3
  • 昨昔燈火通明,今日孤枕難眠。 見一孩與父母游走嬉戲,余心生羨慕。想昨朝,余亦隨父母游走四方,心未生孤寂,今日感人世...
    若魚_吻風(fēng)閱讀 424評(píng)論 8 2
  • 鄭璐 宜昌 焦點(diǎn)網(wǎng)絡(luò)初級(jí)七期 原創(chuàng)持續(xù)分享第18天 今天朋友圈刷爆了“請(qǐng)給我一頂圣誕帽@微信官網(wǎng)”,這是...
    迷你旅客閱讀 539評(píng)論 1 4

友情鏈接更多精彩內(nèi)容