A-关键帧动画
关键帧动画就是在动画控制过程中开发者指定主要的动画状态,各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),好处就是开不用逐个控制每个动画帧,只要关心几个关键帧的状态即可。
关键帧动画开发分为两种形式:
- 通过设置不同的属性值(
property
)进行关键帧控制, - 通过绘制路径(
path
)进行关键帧控制。
2的优先级高于1,如果设置了路径则属性值就不再起作用。
#pragma mark - 关键帧动画
-(void)translationAnimation{
//1.创建关键帧动画并设置动画属性
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
//2.设置关键帧,这里有四个关键帧(使用property)
NSValue *key1=[NSValue valueWithCGPoint:_layer.position];//对于关键帧动画初始值不能省略
NSValue *key2=[NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue *key3=[NSValue valueWithCGPoint:CGPointMake(45, 300)];
NSValue *key4=[NSValue valueWithCGPoint:CGPointMake(55, 400)];
NSArray *values=@[key1,key2,key3,key4];
keyframeAnimation.values=values;
//各个关键帧的时间控制
/*默认情况下每两帧之间的间隔为:8/(4-1)秒。
如果要控制动画从第一帧到第二针占用时间4秒,
从第二帧到第三帧时间为2秒,从第三帧到第四帧时间2秒的话,
就可以通过keyTimes进行设置。keyTimes中存储的是时间占用比例点,
设置keyTimes的值为0.0,0.5,0.75,1.0(当然必须转换为NSNumber),
也就是1到2帧运行到总时间的50%,2到3帧运行到75%,3到4帧运行到8秒结束。*/
keyframeAnimation.keyTimes = @[@0.0,@0.5,@0.7,@1.0];
/* 2.设置路径(使用path)
//绘制贝塞尔曲线
CGPathRef path=CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);//移动到起始点
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);//绘制二次贝塞尔曲线
keyframeAnimation.path=path;//设置path属性
CGPathRelease(path);//释放路径对象*/
//设置其他属性
keyframeAnimation.duration=8.0;
keyframeAnimation.beginTime=CACurrentMediaTime()+2;//设置延迟2秒执行
//3.添加动画到图层,添加动画后就会执行动画
[_layer addAnimation:keyframeAnimation forKey:@"XDKeyframeAnimation_Position"];
}
caculationMode
:动画计算模式。之所以1到2帧能形成连贯性动画而不是直接从第1帧经过8/3秒到第2帧是因为动画模式是连续的(值为kCAAnimationLinear
,这是计算模式的默认值);而如果指定为kCAAnimationDiscrete
离散的那么动画从第1帧经过8/3秒直接到第2帧,中间没有任何过渡。其他动画模式还有:kCAAnimationPaced
(均匀执行,会忽略keyTimes)、kCAAnimationCubic
(平滑执行,对于位置变动关键帧动画运行轨迹更平滑)、kCAAnimationCubicPaced
(平滑均匀执行)。
下图描绘出了几种动画模式的关系(横坐标是运行时间,纵坐标是动画属性[例如位置、透明度等]):
B-组合动画
#pragma mark 基础旋转动画
-(CABasicAnimation *)rotationAnimation{
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
CGFloat toValue=M_PI_2*3;
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
// basicAnimation.duration=6.0;
basicAnimation.autoreverses=true;
basicAnimation.repeatCount=HUGE_VALF;
basicAnimation.removedOnCompletion=NO;
[basicAnimation setValue:[NSNumber numberWithFloat:toValue] forKey:@"KCBasicAnimationProperty_ToValue"];
return basicAnimation;
}
#pragma mark 关键帧移动动画
-(CAKeyframeAnimation *)translationAnimation{
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
CGPoint endPoint= CGPointMake(55, 400);
CGPathRef path=CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, endPoint.x, endPoint.y);
keyframeAnimation.path=path;
CGPathRelease(path);
[keyframeAnimation setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"KCKeyframeAnimationProperty_EndPosition"];
return keyframeAnimation;
}
#pragma mark 创建动画组
-(void)groupAnimation{
//1.创建动画组
CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
//2.设置组中的动画和其他属性
CABasicAnimation *basicAnimation=[self rotationAnimation];
CAKeyframeAnimation *keyframeAnimation=[self translationAnimation];
animationGroup.animations=@[basicAnimation,keyframeAnimation];
animationGroup.delegate=self;
animationGroup.duration=10.0;//设置动画时间,如果动画组中动画已经设置过动画属性则不再生效
animationGroup.beginTime=CACurrentMediaTime()+5;//延迟五秒执行
//3.给图层添加动画
[_layer addAnimation:animationGroup forKey:nil];
}
#pragma mark - 代理方法
#pragma mark 动画完成
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
CAAnimationGroup *animationGroup=(CAAnimationGroup *)anim;
CABasicAnimation *basicAnimation=animationGroup.animations[0];
CAKeyframeAnimation *keyframeAnimation=animationGroup.animations[1];
CGFloat toValue=[[basicAnimation valueForKey:@"KCBasicAnimationProperty_ToValue"] floatValue];
CGPoint endPoint=[[keyframeAnimation valueForKey:@"KCKeyframeAnimationProperty_EndPosition"] CGPointValue];
[CATransaction begin];
[CATransaction setDisableActions:YES];
//设置动画最终状态
_layer.position=endPoint;
_layer.transform=CATransform3DMakeRotation(toValue, 0, 0, 1);
[CATransaction commit];
}
C-转场动画
转场动画
就是从一个场景以动画的形式过渡到另一个场景。
- 创建转场动画
- 设置转场类型,子类型(可选)以及其他属性
- 设置转场后的新视图并添加动画到图层
转场类型:
-
fade
淡出效果(kCATransitionFade
) -
movein
新视图移动到旧视图上(kCATransitionMoveIn
) -
push
新视图推出旧视图(kCATransitionPush
) -
reveal
移开旧视图显示新视图(kCATransitionReveal
)
[私有API效果:cube
(立方体翻转效果),oglFlip
(翻转效果),suckEffect
(收缩效果),rippleEffect
(水滴波纹效果),pageCurl
(向上翻页效果),pageUnCurl
(向下翻页效果),cameralIrisHollowOpen
(摄像头打开效果),cameraIrisHollowClose
(摄像头关闭效果)]
对于支持方向设置的动画类型还包含子类型:
-
kCATransitionFromRight
从右侧转场 -
kCATransitionFromLeft
从左侧转场 -
kCATransitionFromTop
从顶部转场 -
kCATransitionFromBottom
从底部转场
//
// ThirdViewController.m
// CartoonLearn
//
// Created by xiaodoubaba on 16/2/18.
// Copyright © 2016年 xiaodoubaba. All rights reserved.
//
#import "ThirdViewController.h"
#define ImageCount 6
@interface ThirdViewController ()
@property (strong, nonatomic) UIImageView *imgView;
@property (assign, nonatomic) int curIndex;
@end
@implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.imgView];
UISwipeGestureRecognizer *leftSwipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];
UISwipeGestureRecognizer *rightSwipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}
#pragma mark - gesture
- (void)leftSwipe:(UISwipeGestureRecognizer *)gesture
{
//其实就是向下切换图片
[self transitionAnimation:YES];
}
- (void)rightSwipe:(UISwipeGestureRecognizer *)gesture
{
//其实就是向上切换动画
[self transitionAnimation:NO];
}
#pragma mark - transitionAnimation
- (void)transitionAnimation:(BOOL)isNext
{
//1.创建转场动画对象
CATransition *transition = [CATransition new];
//2.设置动画类型
transition.type = @"cube";
//设置子类型
if (isNext) {
transition.subtype = kCATransitionFromRight;
}else{
transition.subtype = kCATransitionFromLeft;
}
//设置动画时常
transition.duration = 1.0f;
//3.设置转场后的新视图并且添加转场动画(动画其实就是切换图片动画)
self.imgView.image = [self getImg:isNext];
[self.imgView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
}
- (UIImage *)getImg:(BOOL)isNext
{
if (isNext) {
_curIndex = (_curIndex + 1) % ImageCount;
}else{
_curIndex = (_curIndex - 1 + ImageCount) % ImageCount;
}
NSString *imgName = [NSString stringWithFormat:@"%d",_curIndex];
return [UIImage imageNamed:imgName];
}
#pragma mark - getter
- (UIImageView *)imgView
{
if (!_imgView) {
_imgView = [UIImageView new];
_imgView.frame = [UIScreen mainScreen].applicationFrame;
_imgView.contentMode = UIViewContentModeScaleAspectFill;
_imgView.image = [UIImage imageNamed:@"0"];
}
return _imgView;
}
@end
D-逐帧动画
通过设置UIImageView
的animationImages
属性,然后调用它的startAnimating
方法去播放这组图片,这就是一个逐帧动画。但是它一次性加载大量图片,性能存在问题,并且中间过程无法控制。(如果iOS
的定时器NSTimer
定时更新图片来达到逐帧动画的效果,缺点就是定时器方法调用有时可能会因为当前系统执行某种比较占用时间的任务造成动画连续性出现问题。)
CADisplayLink
是一个计时器,和NSTimer
不同的是,CADisplayLink
的刷新周期同屏幕完全一致。例如在iOS
中屏幕刷新周期是60次/秒,CADisplayLink
刷新周期同屏幕刷新一致也是60次/秒,这样一来使用它完成的逐帧动画(又称为“时钟动画”)完全感觉不到动画的停滞情况。
#import "ViewController.h"
#define IMAGE_COUNT 10
@interface ViewController (){
CALayer *_layer;
int _index;
NSMutableArray *_images;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景
self.view.layer.contents=(id)[UIImage imageNamed:@"bg.png"].CGImage;
//创建图像显示图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 87, 32);
_layer.position=CGPointMake(160, 284);
[self.view.layer addSublayer:_layer];
//由于鱼的图片在循环中会不断创建,而10张鱼的照片相对都很小
//与其在循环中不断创建UIImage不如直接将10张图片缓存起来
_images=[NSMutableArray array];
for (int i=0; i<10; ++i) {
NSString *imageName=[NSString stringWithFormat:@"fish%i.png",i];
UIImage *image=[UIImage imageNamed:imageName];
[_images addObject:image];
}
//定义时钟对象
CADisplayLink *displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(step)];
//添加时钟对象到主运行循环
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
#pragma mark 每次屏幕刷新就会执行一次此方法(每秒接近60次)
-(void)step{
//定义一个变量记录执行次数
static int s=0;
//每秒执行6次
if (++s%10==0) {
UIImage *image=_images[_index];
_layer.contents=(id)image.CGImage;//更新图片
_index=(_index+1)%IMAGE_COUNT;
}
}
@end