动效微交互iOS动画技术程序员

登录动画

2017-04-25  本文已影响606人  艾江山

前言

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

登录效果.gif
    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是方便待会动画结束后的后序动画,简单说下属性:

    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;
    }
 //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:你可以理解为动画的节奏,下面几个常见的值

 //弹簧动画变宽
    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

源码位置
上一篇 下一篇

猜你喜欢

热点阅读