前言
来源于raywenderlich
本文可以看做是入门CoreAnimation的一片文章。其中你会学习到
CABasicAnimation
CAAnimationGroup
CAKeyframeAnimation
CASpringAnimation
以及部分UIView
动画的使用
先看下最终的效果
-
开始
开始的样子应该是这样,你如果觉得闲太麻烦,可以直接使用xib拖拽,位置大致对齐就可以,因为我们的重点是学习动画
-
接下来是我们第一个动画---飞入
效果是让标题和用户名、密码的输入框依次从左边飞入到屏幕中间
首先我们要把这三个控件的横坐标都往左移除一个屏幕的宽度。
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
是默认属性,他会在动画完成的时候清除动画做的改变
-
kCAFillModeBackwards
会显示第一帧,不管你的动画开始时间,然后在进行动画
-
kCAFillModeForwards
这是最常用的一种,他会保持动画的最后一帧知道你移除动画。 -
kCAFillModeBoth
这是组合了kCAFillModeForwards和kCAFillModeBackwards,你会马上看到第一帧,当动画结束后悔显示最后一帧
- 脉冲动画
接着刚才我们已经把标题和输入框都相继飞入屏幕中,会有一个变大的效果。首先我们要等待动画结束,因为我们刚才在使用动画的时候已经设置了代理,所以我们只需要在动画结束的回调函数- (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
:一开始慢然后快
-
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