登录动画

前言

来源于raywenderlich
本文可以看做是入门CoreAnimation的一片文章。其中你会学习到
CABasicAnimation CAAnimationGroup CAKeyframeAnimation CASpringAnimation以及部分UIView动画的使用
先看下最终的效果

登录效果.gif
  • 开始
    开始的样子应该是这样,你如果觉得闲太麻烦,可以直接使用xib拖拽,位置大致对齐就可以,因为我们的重点是学习动画


    初始样子
  • 接下来是我们第一个动画---飞入


    飞入

    效果是让标题和用户名、密码的输入框依次从左边飞入到屏幕中间
    首先我们要把这三个控件的横坐标都往左移除一个屏幕的宽度。


    飞入.png
    CABasicAnimation *flyRightAnimation  = [CABasicAnimation animationWithKeyPath:@"position.x"];
    flyRightAnimation.delegate           = self;
    [flyRightAnimation setValue:@"form" forKey:@"name"];
    [flyRightAnimation setValue:self.headingLabel.layer forKey:@"layer"];
    flyRightAnimation.toValue            = [NSValue valueWithCGPoint: CGPointMake(MainSize.width * 0.5, 0)];
    flyRightAnimation.fromValue          = [NSValue valueWithCGPoint: CGPointMake(-MainSize.width * 0.5, 0)];//-@(MainSize.width * 0.5);
    flyRightAnimation.duration           = .5;
    flyRightAnimation.fillMode           = kCAFillModeBoth;
    [self.headingLabel.layer addAnimation:flyRightAnimation forKey:nil];
    flyRightAnimation.beginTime          = CACurrentMediaTime() + 0.3;
    [flyRightAnimation setValue:self.userNameTextField.layer forKey:@"layer"];
    [self.userNameTextField.layer addAnimation:flyRightAnimation forKey:nil];
    flyRightAnimation.beginTime          = CACurrentMediaTime() + 0.4;
    [flyRightAnimation setValue:self.passWordTextField.layer forKey:@"layer"];
    [self.passWordTextField.layer addAnimation:flyRightAnimation forKey:nil];

通过KeyPath设置属性动画还可以是position, bounds, transform 等等,设置了key、value是方便待会动画结束后的后序动画,简单说下属性:

  • beginTime:设置动画开始的绝对时间,所以我们要先获取当前时间然后再添加所需的延迟时间
  • fillMode:这个属性控制你动画队列的开始和结束的行为
  • kCAFillModeRemoved是默认属性,他会在动画完成的时候清除动画做的改变
    kCAFillModeRemoved
  • kCAFillModeBackwards会显示第一帧,不管你的动画开始时间,然后在进行动画
    kCAFillModeBackwards.png
  • kCAFillModeForwards这是最常用的一种,他会保持动画的最后一帧知道你移除动画。
    kCAFillModeForwards.png
  • kCAFillModeBoth这是组合了kCAFillModeForwards和kCAFillModeBackwards,你会马上看到第一帧,当动画结束后悔显示最后一帧
    Snip20170418_6.png
  • 脉冲动画
    接着刚才我们已经把标题和输入框都相继飞入屏幕中,会有一个变大的效果。首先我们要等待动画结束,因为我们刚才在使用动画的时候已经设置了代理,所以我们只需要在动画结束的回调函数- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;中添加变大效果就可以。这里我们使用核心动画给的CASpringAnimation
    NSString *name                         = [anim valueForKey:@"name"];
    if ([name isEqualToString:@"form"]) {
        CALayer *layer                     = [anim valueForKey:@"layer"];
        [anim setValue:nil forKey:@"layer"];
        //脉冲动画
        CASpringAnimation *pulseAnimation   = [CASpringAnimation animationWithKeyPath:@"transform.scale"];
        pulseAnimation.damping             = 7.5;
        pulseAnimation.fromValue           = @(1.25);
        pulseAnimation.toValue             = @(1.);
        pulseAnimation.duration            = pulseAnimation.settlingDuration;
        [layer addAnimation:pulseAnimation forKey:nil];
    }

先简单说下CASpringAnimation的一些属性:
damping:阻尼和弹簧一样,在其他因素不变的情况下阻尼越大回复形变越快
mass:质量,在胡克定律中没有质量这个变量,但是在有阻尼的情况下质量会影响弹簧的收缩快慢等。
stiffness:弹簧的刚度与重量
initialVelocity:初始速度
效果:

脉冲动画

  • 云彩动画
    在标题和输入框都相继飞入屏幕中的时候我们需要把背景上的云朵都一个一个的显示出来
 //cloud
    CABasicAnimation *fadeAnimation      = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeAnimation.fromValue              = @0.;
    fadeAnimation.toValue                = @1.;
    fadeAnimation.duration               = .5;
    fadeAnimation.fillMode               = kCAFillModeBackwards;
    fadeAnimation.beginTime              = CACurrentMediaTime() + .5;
    [self.cloud1ImageV.layer addAnimation:fadeAnimation forKey:nil];
    fadeAnimation.beginTime              = CACurrentMediaTime() + .7;
    [self.cloud2ImageV.layer addAnimation:fadeAnimation forKey:nil];
    fadeAnimation.beginTime              = CACurrentMediaTime() + .9;
    [self.cloud3ImageV.layer addAnimation:fadeAnimation forKey:nil];
    fadeAnimation.beginTime              = CACurrentMediaTime() + 1.1;
    [self.cloud4ImageV.layer addAnimation:fadeAnimation forKey:nil];

云彩出来了,还需要让他们不断循环的往右移动

CABasicAnimation *moveAnimation   = [CABasicAnimation animationWithKeyPath:@"position.x"];
    [moveAnimation setValue:@"cloud" forKey:@"name"];
    [moveAnimation setValue:layer forKey:@"layer"];
    moveAnimation.delegate            = self;
    moveAnimation.duration            = duration;
    moveAnimation.fillMode            = kCAFillModeForwards;
    moveAnimation.fromValue           = @(layer.position.x);
    moveAnimation.toValue             = @(self.view.ai_width + layer.bounds.size.width * 0.5);
    [layer addAnimation:moveAnimation forKey:nil];
    layer.position                     = CGPointMake(-layer.bounds.size.width/2, layer.position.y);

然后在代理- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag中判断key,让他回到屏幕左边继续像右运动

if ([name isEqualToString:@"cloud"]) {
        CALayer *layer                     = [anim valueForKey:@"layer"];
        layer.position                     = CGPointMake(-layer.bounds.size.width/2, layer.position.y);
        [self animationCloud:layer];
    }

这里需要注意的是,这两个函数你调用我,我调用你会形成死循环,导致内存泄漏,所以在做动画的前面加一个全局变量房租内存泄漏

 if (!self.isAppear) {//如果没有显示直接return防止内存泄漏
        return;
    }
  • 登录按钮动画
    接下来是组合动画CAAnimationGroup
    组合动画你可以调整整体的属性,如:持续时间、代理、动画节奏等。
 //loginBtn
    CAAnimationGroup *groupAnimation   = [[CAAnimationGroup alloc]init];
    groupAnimation.beginTime           = CACurrentMediaTime() + .5;
    groupAnimation.duration            = .5;
    groupAnimation.fillMode            = kCAFillModeBackwards;
    groupAnimation.timingFunction      = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    
    CABasicAnimation *scaleDownAnimation   = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    scaleDownAnimation.fromValue           = @3.5;
    scaleDownAnimation.toValue             = @1.;
    
    CABasicAnimation *rotateAnimation      = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotateAnimation.fromValue              = @M_PI_4;
    rotateAnimation.toValue                = @0;
    
    CABasicAnimation *fadeAnimation        = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeAnimation.fromValue                = @0.;
    fadeAnimation.toValue                  = @1.;
    
    groupAnimation.animations              = @[fadeAnimation,scaleDownAnimation,rotateAnimation];
    [self.loginBtn.layer addAnimation:groupAnimation forKey:nil];
     self.loginBtn.alpha                   = 1;

timingFunction:你可以理解为动画的节奏,下面几个常见的值

  • kCAMediaTimingFunctionLinear:以匀速进行整个动画
  • kCAMediaTimingFunctionEaseIn:一开始慢然后快
    kCAMediaTimingFunctionEaseIn
  • kCAMediaTimingFunctionEaseOut:一开始快然后慢
    kCAMediaTimingFunctionEaseOut
  • 点击登录动画
    点击登录按钮的时候登录按钮,需要往下移动,然后变颜色,以及圆角变大,宽度变宽,显示指示器(也就是菊花)。


    点击登录
 //弹簧动画变宽
    AIWeakSelf
    [UIView animateWithDuration:1.5 delay:0. usingSpringWithDamping:.2 initialSpringVelocity:0. options:(UIViewAnimationOptionCurveLinear) animations:^{
        CGRect loginBounds                = self.loginBtn.bounds;
        loginBounds.size.width           += 80;
        weakSelf.loginBtn.bounds          = loginBounds;
    } completion:^(BOOL finished) {
        [weakSelf showMessageWithIndex:0];
        
    }];
    //spinner
    [UIView animateWithDuration:.33 delay:0 usingSpringWithDamping:.7 initialSpringVelocity:0 options:(UIViewAnimationOptionCurveLinear) animations:^{
        weakSelf.loginBtn.ai_centerY         += 60;
        weakSelf.spinner.ai_x                 = 40;
        weakSelf.spinner.alpha                = 1;
        weakSelf.spinner.ai_centerY           = weakSelf.loginBtn.ai_middleY;
    } completion:nil];

这里直接使用UIView动画
duration:动画持续时间
delay:动画延迟时间
dampingRatio:弹簧阻尼
velocity:弹簧速度
options:这里可以选择动画的节奏、是否重复等
animations:动画的回调
completion:动画完成后的回调

然后是设置圆角的背景颜色动画

/**
 动画来改变layer的背景颜色
 
 @param layer   改变的layer
 @param toColor 改变的颜色
 */
- (void)tintBackgroundColorWithCALayer:(CALayer*)layer toColor:(UIColor*)toColor{
    CASpringAnimation *tintAnimation    = [CASpringAnimation animationWithKeyPath:@"backgroundColor"];
    tintAnimation.fromValue            = (__bridge id _Nullable)(layer.backgroundColor);
    tintAnimation.toValue              = (__bridge id _Nullable)(toColor.CGColor);
    tintAnimation.duration             = tintAnimation.settlingDuration;
    tintAnimation.damping              = 7.;
    tintAnimation.mass                 = 10.;
    [layer addAnimation:tintAnimation forKey:nil];
    layer.backgroundColor              = toColor.CGColor;
}

/**
 设置圆角动画

 @param layer  动画的layer
 @param radius 圆角半径
 */
- (void)roundCornersWithCALayer:(CALayer*)layer toRadius:(CGFloat)radius {
    CASpringAnimation *radiusAnimation     = [CASpringAnimation animationWithKeyPath:@"cornerRadius"];
    radiusAnimation.fromValue             = @(layer.cornerRadius);
    radiusAnimation.toValue               = @(radius);
    radiusAnimation.duration              = radiusAnimation.settlingDuration;
    radiusAnimation.damping               = 17.;
    [layer addAnimation:radiusAnimation forKey:nil];
    layer.cornerRadius                    = radius;
}

这里都单独提出一个方法来执行layer对应的动画,方便以后的项目如果还有这类似的改变颜色或者改变圆角的动画就可以直接把方法复制过去使用
我们看到中间还会显示一个信息条

/**
 显示一条信息

 @param index 第几条
 */
- (void)showMessageWithIndex:(NSInteger)index {
    self.label.text  = self.messages[index];
    AIWeakSelf
    [UIView transitionWithView:self.statusImageV duration:.33 options:(UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionTransitionFlipFromBottom) animations:^{
        weakSelf.statusImageV.hidden = NO;
    } completion:^(BOOL finished) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (index < (weakSelf.messages.count - 1)) {
                [weakSelf removeMessageWithIndex:index];
            }else{
                [weakSelf resetFrom];
            }
        });
    }];
}


/**
 提出一条信息

 @param index 第几条
 */
- (void)removeMessageWithIndex:(NSInteger)index {
    AIWeakSelf
    [UIView animateWithDuration:.33 animations:^{
        weakSelf.statusImageV.ai_centerX += MainSize.width;
    } completion:^(BOOL finished) {
        weakSelf.statusImageV.hidden      = YES;
        weakSelf.statusImageV.center      = self.statusPoint;
        [weakSelf showMessageWithIndex:index+1];
    }];
}

当几条信息显示完了后,我们需要重置登录按钮状态,这里有个标题有个摇晃的效果

摇晃

这里我们使用CAKeyframeAnimation

        CAKeyframeAnimation *wobbleAniamtion  = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
        wobbleAniamtion.duration              = 0.25;
        wobbleAniamtion.repeatCount           = 4;
        wobbleAniamtion.values                = @[@0.0, @(-M_PI_4), @0.0, @M_PI_4, @0.0];
        wobbleAniamtion.keyTimes              = @[@0.0, @0.25, @0.5, @0.75, @1.0];
        [weakSelf.headingLabel.layer addAnimation:wobbleAniamtion forKey:nil];

以CABasicAnimation一样的方式创建一个CAKeyframeAnimation设置他的重复次数,持续是时间等。
values:动画的关键点,选择角度从0°到-45°再到0°再到45°最后回到0°。(这里起始和结束一样可以让重复的动画的时候看上去很自然)
keyTimes:关键时间,一定要确保你的关键时间是从0开始到1结束。
还有些细节的动画,由于代码比较多又是比较重复的技术就不贴出来了有兴趣可以下载源码
喜欢的在github上给个star

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

推荐阅读更多精彩内容