实例化
使用方法animationWithKeyPath:
对 CABasicAnimation进行实例化,并指定Layer的属性作为关键路径进行注册。
//围绕y轴旋转
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
设定动画
设定动画的属性和说明
属性 | 说明 |
---|---|
duration | 动画的时长 |
repeatCount | 重复的次数。不停重复设置为 HUGE_VALF |
repeatDuration | 设置动画的时间。在该时间内动画一直执行,不计次数。 |
beginTime | 指定动画开始的时间。从开始延迟几秒的话,设置为【CACurrentMediaTime() + 秒数】 的方式 |
timingFunction | 设置动画的速度变化 |
autoreverses | 动画结束时是否执行逆动画 |
fromValue | 所改变属性的起始值 |
toValue | 所改变属性的结束时的值 |
byValue | 所改变属性相同起始值的改变量 |
transformAnima.fromValue = @(M_PI_2);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transformAnima.autoreverses = YES;
transformAnima.repeatCount = HUGE_VALF;
transformAnima.beginTime = CACurrentMediaTime() + 2;
防止动画结束后回到初始状态
只需设置removedOnCompletion
、fillMode
两个属性就可以了。
transformAnima.removedOnCompletion = NO;
transformAnima.fillMode = kCAFillModeForwards;
解释:为什么动画结束后返回原状态?
首先我们需要搞明白一点的是,layer动画运行的过程是怎样的?其实在我们给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。
这个同样也可以解释为什么在动画移动过程中,我们为何不能对其进行任何操作。
所以在我们完成layer动画之后,最好将我们的layer属性设置为我们最终状态的属性,然后将presentation layer 移除掉。
添加动画
[self.imageView.layer addAnimation:transformAnima forKey:@"A"];
需要注意的两点
- 一个 CABasicAniamtion 的实例对象只是一个数据模型,和他绑定到哪一个layer上是没有关系的
- 方法
addAnimation:forKey:
是将 CABasicAniamtion 对象进行了 copy 操作的。所以在将其添加到一个layer上之后,我们还是将其再次添加到另一个layer上的。
fillMode属性的理解
该属性定义了你的动画在开始和结束时的动作。默认值是 kCAFillModeRemoved
。
取值的解释
- kCAFillModeRemoved 设置为该值,动画将在设置的 beginTime 开始执行(如没有设置beginTime属性,则动画立即执行),动画执行完成后将会layer的改变恢复原状。
- kCAFillModeForwards 设置为该值,动画即使之后layer的状态将保持在动画的最后一帧,而removedOnCompletion的默认属性值是 YES,所以为了使动画结束之后layer保持结束状态,应将removedOnCompletion设置为NO。
- kCAFillModeBackwards 设置为该值,将会立即执行动画的第一帧,不论是否设置了 beginTime属性。观察发现,设置该值,刚开始视图不见,还不知道应用在哪里。
- kCAFillModeBoth 该值是 kCAFillModeForwards 和 kCAFillModeBackwards的组合状态
Animation Easing的使用
也即是属性timingFunction
值的设定,有种方式来获取属性值
(1)使用方法functionWithName:
这种方式很简单,这里只是简单说明一下取值的含义:
kCAMediaTimingFunctionLinear 传这个值,在整个动画时间内动画都是以一个相同的速度来改变。也就是匀速运动。
-
kCAMediaTimingFunctionEaseIn 使用该值,动画开始时会较慢,之后动画会加速。
-
kCAMediaTimingFunctionEaseOut 使用该值,动画在开始时会较快,之后动画速度减慢。
-
kCAMediaTimingFunctionEaseInEaseOut 使用该值,动画在开始和结束时速度较慢,中间时间段内速度较快。
(2)使用方法functionWithControlPoints: : : :
实现,这个之后再说,占个坑先。
其他的一些设置属性
- repeatCount 设置动画的执行次数
- autoreverses 默认值为 NO,将其设置为 YES
- speed 改变动画的速度 可以直接设置动画上的speed属性,这样只有这个动画速度。
animation.speed = 2;
或者在layer上设置speed属性,这样在该视图上的所有动画都提速,该视图上的所有子视图上的动画也会提速。
speed两点需注意的:
(1) 如果设置动画时间为4s,speed设置为2,则动画只需2s即可执行完。
(2)如果同时设置了动画的speed和layer 的speed,则实际的speed为两者相乘。
使用总结
- 在动画执行完成之后,最好还是将动画移除掉。也就是尽量不要设置
removedOnCompletion
属性为NO -
fillMode
尽量取默认值就好了,不要去设置它的值。只有在极个别的情况下我们会修改它的值,以后会说到,这里先占个坑。 - 解决有时视图会闪动一下的问题,我们可以将layer的属性值设置为我们的动画最后要达到的值,然后再给我们的视图添加layer动画。
例子(移动动画实现)
直接上代码
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.duration = 0.8;
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
positionAnima.repeatCount = HUGE_VALF;
positionAnima.repeatDuration = 2;
positionAnima.removedOnCompletion = NO;
positionAnima.fillMode = kCAFillModeForwards;
[self.imageView.layer addAnimation:positionAnima forKey:@"AnimationMoveY"];
组合动画的实现
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
transformAnima.fromValue = @(0);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animaGroup = [CAAnimationGroup animation];
animaGroup.duration = 2.0f;
animaGroup.fillMode = kCAFillModeForwards;
animaGroup.removedOnCompletion = NO;
animaGroup.animations = @[positionAnima,transformAnima];
[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];
动画开始和结束时的事件
为了获取动画的开始和结束事件,需要实现协议
positionAnima.delegate = self;
代理方法实现
//动画开始时
- (void)animationDidStart:(CAAnimation *)anim
{
NSLog(@"开始了");
}
//动画结束时
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
//方法中的flag参数表明了动画是自然结束还是被打断,比如调用了removeAnimationForKey:方法或removeAnimationForKey方法,flag为NO,如果是正常结束,flag为YES。
NSLog(@"结束了");
}
其实比较重要的是有多个动画的时候如何在代理方法中区分不同的动画
两种方式
方式一:
如果我们添加动画的视图是全局变量,可使用该方法。
添加动画时,我们使用了
[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];
所以,可根据key来区分不同的动画
//动画开始时
- (void)animationDidStart:(CAAnimation *)anim
{
if ([anim isEqual:[self.imageView.layer animationForKey:@"Animation"]]) {
NSLog(@"动画组执行了");
}
}
Note:把动画存储为一个属性然后再回调中比较,用来判定是哪个动画是不可行的。应为委托传入的动画参数是原始值的一个深拷贝,不是同一个值
方式二
添加动画的视图是局部变量时,可使用该方法
添加动画给动画设置key-value对
[positionAnima setValue:@"PositionAnima" forKey:@"AnimationKey"];
[transformAnima setValue:@"TransformAnima" forKey:@"AnimationKey"];
所以,可以根据key中不同的值来进行区分不同的动画
//动画结束时
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"PositionAnima"]) {
NSLog(@"位置移动动画执行结束");
}
else if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"TransformAnima"]){
NSLog(@"旋转动画执行结束");
}
}
解决循环引用问题
由于CAAnimation的delegate使用的strong类型,
所以在全局变量如下设置时会产生循环引用的情况
self.animation.delegate = self;//可通过复用dealloc方法来验证
解决方案
- 声明一个单独的类实现delegate的回调
//.h
#import <UIKit/UIKit.h>
@interface AnimationDelegate : NSObject
@end
//.m
#import "AnimationDelegate.h"
@implementation AnimationDelegate
- (void)animationDidStart:(CAAnimation *)anim
{
NSLog(@"Animation Start");
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSLog(@"Animation Stop");
}
- (void)dealloc
{
NSLog(@"Delegate Dealloc");
}
@end
使用方式
self.animation.delegate = [[AnimationDelegate alloc]init];
- 使用
NSProxy
来解决
该类可直接引用YYKit中的YYWeakProxy
类
使用方法
self.animation.delegate = [YYWeakProxy proxyWithTarget:self];
一些常用的animationWithKeyPath值的总结
值 | 说明 | 使用形式 |
---|---|---|
transform.scale | 比例转化 | @(0.8) |
transform.scale.x | 宽的比例 | @(0.8) |
transform.scale.y | 高的比例 | @(0.8) |
transform.rotation.x | 围绕x轴旋转 | @(M_PI) |
transform.rotation.y | 围绕y轴旋转 | @(M_PI) |
transform.rotation.z | 围绕z轴旋转 | @(M_PI) |
cornerRadius | 圆角的设置 | @(50) |
backgroundColor | 背景颜色的变化 | (id)[UIColor purpleColor].CGColor |
bounds | 大小,中心不变 | [NSValue valueWithCGRect:CGRectMake(0, 0, 200, 200)]; |
position | 位置(中心点的改变) | [NSValue valueWithCGPoint:CGPointMake(300, 300)]; |
contents | 内容,比如UIImageView的图片 | imageAnima.toValue = (id)[UIImage imageNamed:@"to"].CGImage; |
opacity | 透明度 | @(0.7) |
contentsRect.size.width | 横向拉伸缩放 | @(0.4)最好是0~1之间的 |