本文主要内容:
1、 基于CALayer的动画
2、 基于UIView的动画
先来了解一下苹果提供的动画API — Core Animation
一个简单的转盘实例https://github.com/shihuaixing/HXWheelDemo.git
Core Animation是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍,使用它需要先添加QuartzCore.framework和引入对应的框架<QuartzCore/QuartzCore.h>
开发步骤:
① 初始化一个动画对象(CAAnimation)并设置一些动画相关属性
② 添加动画对象到层(CALayer)中,开始执行动画
CALayer中很多属性都可以通过CAAnimation实现动画效果,包括:opacity、position、transform、bounds、contents等(可以在API文档中搜索:CALayer Animatable Properties),通过调用CALayer的addAnimation:forKey增加动画到层(CALayer)中,这样就能触发动画了。通过调用removeAnimationForKey可以停止层中的动画。Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
CAAnimation继承结构:(引用自MJ老师)
CAAnimation
所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类。
属性解析:(红色代表来自CAMediaTiming协议的属性)
duration:动画的持续时间
repeatCount:动画的重复次数
repeatDuration:动画的重复时间
removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
timingFunction:速度控制函数,控制动画运行的节奏
delegate:动画代理
CAPropertyAnimation
是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation
属性解析:
keyPath:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。比如,指定@”position”为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果
CABasicAnimation(简单的平移,缩放,旋转)
CAPropertyAnimation的子类
属性解析:
fromValue:keyPath相应属性的初始值
toValue:keyPath相应属性的结束值
(随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue)
如果fillMode=kCAFillModeForwards和removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。比如,CALayer的position初始值为(0,0),CABasicAnimation的fromValue为(10,10),toValue为(100,100),虽然动画执行完毕后图层保持在(100,100)这个位置,实质上图层的position还是为(0,0)
CAKeyframeAnimation
CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值
属性解析:
values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的。CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
CAAnimationGroup
CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行
属性解析:
animations:用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间
CATransition
CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
属性解析:
type:动画过渡类型
subtype:动画过渡方向
startProgress:动画起点(占整体动画的百分比)
endProgress:动画终点(占整体动画的百分比)
以上就是基于CALAyer的核心动画,下面通过代码演示这些神奇的动画吧。
CABasicAnimation(简单的平移,缩放,旋转)
/**
* 平移
*/
- (void)testTranslation {
// 1.创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
// 2.设置动画属性
// keyPath:决定了执行什么样的动画(调整哪个属性(可动画属性)来执行动画)
anim.keyPath = @"position";
anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];// 不设置frameValue时,默认从当前状态执行动画
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
// 动画执行时间
anim.duration = 2.0;
// 动画执行完毕后,让图层停在动画结束的位置
anim.removedOnCompletion = NO;// 不执行结束之后的动画
anim.fillMode = kCAFillModeForwards;
// 添加动画到图层并执行
[self.redView.layer addAnimation:anim forKey:nil];
}
/**
* 平移
*/
- (void)testTransform {
// 1.创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
// 2.设置动画属性
// keyPath:决定了执行什么样的动画(调整哪个属性(可动画属性)来执行动画)
// keyPath = @"transform.translation.y":在y轴上执行平移动画
// keyPath = @"transform.translation":在某条线上(从一个点到一个点)执行平移动画
anim.keyPath = @"transform.translation.y";
anim.fromValue = @(100);// 不设置frameValue时,默认从当前状态执行动画
anim.toValue = @(400);
// 动画执行时间
anim.duration = 2.0;
// 动画执行完毕后,让图层停在动画结束的位置
anim.removedOnCompletion = NO;// 不执行结束之后的动画
anim.fillMode = kCAFillModeForwards;
// 添加动画到图层并执行
[self.redView.layer addAnimation:anim forKey:nil];
}
/**
* 旋转3D
*/
- (void)testtransform3D
{
// 1.创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
// 2.设置动画对象
// keyPath决定了执行怎样的动画, 调整哪个属性来执行动画
anim.keyPath = @"transform";
// anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
anim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, -1, 0)];
anim.duration = 2.0;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
// 3.添加动画
[self.redView.layer addAnimation:anim forKey:nil];
}
/**
* 缩放
*/
- (void)testScale
{
// 1.创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
// 2.设置动画对象
// keyPath决定了执行怎样的动画, 调整哪个属性来执行动画
anim.keyPath = @"bounds";
anim.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 200, 200)];
anim.duration = 2.0;
/**让图层保持动画执行完毕后的状态**/
// 动画执行完毕后不要删除动画
anim.removedOnCompletion = NO;
// 保持最新的状态
anim.fillMode = kCAFillModeForwards;
// 3.添加动画
[self.redView.layer addAnimation:anim forKey:nil];
}
CAKeyframeAnimation(关键帧)
/**
* 让redView走一个圆
*/
- (void)testKeyFrame {
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
// 多组动画
anim.keyPath = @"position";
// 执行一段路径
// 创建一个可变路径
CGMutablePathRef path = CGPathCreateMutable();
// 向路径中加入圆(即向路径中添加点)
CGPathAddEllipseInRect(path, NULL, CGRectMake(100, 100, 100, 100));
// 设置执行动画的路径
anim.path = path;
// 释放
CGPathRelease(path);
// 设置动画的执行节奏
// kCAMediaTimingFunctionEaseInEaseOut : 一开始比较慢, 中间会加速, 临近结束的时候, 会变慢
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.delegate = self;
anim.duration = 2.0;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
[self.redView.layer addAnimation:anim forKey:nil];
}
/**
* 让redView走一个矩形
*/
- (void)testMove
{
// CABasicAnimation fromValue --> toValue
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
// 多组动画
anim.keyPath = @"position";
NSValue *v1 = [NSValue valueWithCGPoint:CGPointZero];
NSValue *v2 = [NSValue valueWithCGPoint:CGPointMake(100, 0)];
NSValue *v3 = [NSValue valueWithCGPoint:CGPointMake(100, 200)];
NSValue *v4 = [NSValue valueWithCGPoint:CGPointMake(0, 200)];
anim.values = @[v1, v2, v3, v4];
// anim.keyTimes = @[@(0.5), @(0.25), @(0.25)];// 每段动画执行的时间
anim.duration = 2.0;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
[self.redView.layer addAnimation:anim forKey:@”ANIM”];
}
注意可以调用这个方法移除图层上的动画[self.redView.layer removeAnimationForKey:@"ANIM"];该Key值就是添加动画时,设置的key
CATransition(利用一个图片浏览例子)
- (IBAction)previous {
self.currentIndex --;
if (self.currentIndex < 1) {
self.currentIndex = 7;
}
NSString *name = [NSString stringWithFormat:@"%d.jpg", self.currentIndex];
self.imageView.image = [UIImage imageNamed:name];
// 设置转场动画
CATransition *anim = [CATransition animation];
// 设置转场动画属性
// 转场动画类型
anim.type = @"cube";
/* 过渡效果
<span style="white-space:pre"> </span>fade //交叉淡化过渡(不支持过渡方向) kCATransitionFade
<span style="white-space:pre"> </span>push //新视图把旧视图推出去 kCATransitionPush
<span style="white-space:pre"> </span>moveIn //新视图移到旧视图上面 kCATransitionMoveIn
<span style="white-space:pre"> </span>reveal //将旧视图移开,显示下面的新视图 kCATransitionReveal
<span style="white-space:pre"> </span>cube //立方体翻滚效果
<span style="white-space:pre"> </span>oglFlip //上下左右翻转效果
<span style="white-space:pre"> </span>suckEffect //收缩效果,如一块布被抽走(不支持过渡方向)
<span style="white-space:pre"> </span>rippleEffect //滴水效果(不支持过渡方向)
<span style="white-space:pre"> </span>pageCurl //向上翻页效果
<span style="white-space:pre"> </span>pageUnCurl //向下翻页效果
<span style="white-space:pre"> </span>cameraIrisHollowOpen //相机镜头打开效果(不支持过渡方向)
<span style="white-space:pre"> </span>cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向)
*/
anim.subtype = kCATransitionFromRight;
/* 过渡方向
<span style="white-space:pre"> </span>kCATransitionFromRight
<span style="white-space:pre"> </span>kCATransitionFromLeft
<span style="white-space:pre"> </span>kCATransitionFromBottom
<span style="white-space:pre"> </span>kCATransitionFromTop
*/
// 时间
anim.duration = 1.0;
// 添加动画
[self.imageView.layer addAnimation:anim forKey:nil];
}
- (IBAction)next {
self.currentIndex ++;
if (self.currentIndex > 7) {
self.currentIndex = 1;
}
NSString *name = [NSString stringWithFormat:@"%d.jpg", self.currentIndex];
self.imageView.image = [UIImage imageNamed:name];
// 设置转场动画
CATransition *anim = [CATransition animation];
// 设置转场动画属性
// 转场动画类型
anim.type = @"cube";
anim.subtype = kCATransitionFromLeft;
// 时间
anim.duration = 1.0;
// 添加动画
[self.imageView.layer addAnimation:anim forKey:nil];
}
CAAnimationGroup(实现一边旋转,一边缩放,再一边平移的动画)
- (void)testAnimGroup {
// 1.创建旋转动画对象
CABasicAnimation *rotate = [CABasicAnimation animation];
rotate.keyPath = @"transform.rotation";
rotate.toValue = @(M_PI);
// 2.创建缩放动画对象
CABasicAnimation *scale = [CABasicAnimation animation];
scale.keyPath = @"transform.scale";
scale.toValue = @(0.0);
// 3.平移动画
CABasicAnimation *move = [CABasicAnimation animation];
move.keyPath = @"transform.translation";
move.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
// 4.将所有的动画添加到动画组中
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[rotate, scale, move];
group.duration = 2.0;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
[self.myvie.layer addAnimation:group forKey:nil];
}
基于
CALayer
的动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。为了解决这个问题,苹果将基于
CALayer
的动画封装在了
UIView
中。执行
UIVIew
的动画就可以真真实实的改变
UIView
的属性。
基于UIView的动画
UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画支持
执行动画所需要的工作由UIView类自动完成,但仍要在希望执行动画时通知视图,为此需要将改变属性的代码放在[UIViewbeginAnimations:nil context:nil]和[UIView commitAnimations]之间。
常见方法解析:
+ (void)setAnimationDelegate:(id)delegate
设置动画代理对象,当动画开始或者结束时会发消息给代理对象
+(void)setAnimationWillStartSelector:(SEL)selector
当动画即将开始时,执行delegate对象的selector,并且把beginAnimations:context:中传入的参数传进selector
+(void)setAnimationDidStopSelector:(SEL)selector
当动画结束时,执行delegate对象的selector,并且把beginAnimations:context:中传入的参数传进selector
+(void)setAnimationDuration:(NSTimeInterval)duration
动画的持续时间,秒为单位
+(void)setAnimationDelay:(NSTimeInterval)delay
动画延迟delay秒后再开始
+ (void)setAnimationStartDate:(NSDate*)startDate
动画的开始时间,默认为now
+(void)setAnimationCurve:(UIViewAnimationCurve)curve
动画的节奏控制
+(void)setAnimationRepeatCount:(float)repeatCount
动画的重复次数
+(void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses
如果设置为YES,代表动画每次重复执行的效果会跟上一次相反
+(void)setAnimationTransition:(UIViewAnimationTransition)transitionforView:(UIView *)view cache:(BOOL)cache
设置视图view的过渡效果, transition指定过渡类型, cache设置YES代表使用视图缓存,性能较好
UIView还提供Block动画
+(void)animateWithDuration:(NSTimeInterval)durationdelay:(NSTimeInterval)delay options:(UIViewAnimationOptions)optionsanimations:(void (^)(void))animations completion:(void (^)(BOOLfinished))completion
参数解析:
duration:动画的持续时间
delay:动画延迟delay秒后开始
options:动画的节奏控制
animations:将改变视图属性的代码放在这个block中
completion:动画结束后,会自动调用这个block
+(void)transitionWithView:(UIView*)viewduration:(NSTimeInterval)durationoptions:(UIViewAnimationOptions)options animations:(void (^)(void))animationscompletion:(void (^)(BOOL finished))completion<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
参数解析:
duration:动画的持续时间
view:需要进行转场动画的视图
options:转场动画的类型
animations:将改变视图属性的代码放在这个block中
completion:动画结束后,会自动调用这个block
+(void)transitionFromView:(UIView *)fromView toView:(UIView *)toViewduration:(NSTimeInterval)duration options:(UIViewAnimationOptions)optionscompletion:(void (^)(BOOL finished))completion
方法调用完毕后,相当于执行了下面两句代码:
// 添加toView到父视图
[fromView.superview addSubview:toView];
// 把fromView从父视图中移除
[fromView removeFromSuperview];
参数解析:
duration:动画的持续时间
options:转场动画的类型
animations:将改变视图属性的代码放在这个block中
completion:动画结束后,会自动调用这个block
UIImageView的帧动画
UIImageView可以让一系列的图片在特定的时间内按顺序显示
相关属性解析:
animationImages:要显示的图片(一个装着UIImage的NSArray)
animationDuration:完整地显示一次animationImages中的所有图片所需的时间
animationRepeatCount:动画的执行次数(默认为0,代表无限循环)
相关方法解析:
- (void)startAnimating;// 开始动画
- (void)stopAnimating; // 停止动画
- (BOOL)isAnimating; // 是否正在运行动画
UIActivityIndicatorView
是一个旋转进度轮,可以用来告知用户有一个操作正在进行中,一般用initWithActivityIndicatorStyle初始化
方法解析:
- (void)startAnimating; // 开始动画
- (void)stopAnimating; // 停止动画
- (BOOL)isAnimating; // 是否正在运行动画
/*
UIActivityIndicatorViewStyle有3个值可供选择:
UIActivityIndicatorViewStyleWhiteLarge //大型白色指示器
UIActivityIndicatorViewStyleWhite //标准尺寸白色指示器
UIActivityIndicatorViewStyleGray //灰色指示器,用于白色背景
*/
来看一下view和layer执行动画的区别:
/**
* view移动到(200, 300)点
*/
- (void)testViewSimpleAnim
{
[UIView beginAnimations:nil context:nil];
// 动画执行完毕后, 会自动调用self的animateStop方法
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animateStop)];
self.redView.center = CGPointMake(200, 300);
[UIView commitAnimations];
[UIView animateWithDuration:1.0 animations:^{
self.redView.center = CGPointMake(200, 300);
} completion:^(BOOL finished) {
}];
}
/**
* 图层移动到(0, 0)点
*/
- (void)testLayerSimpleAnim
{
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"position";
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
anim.duration = 2.0;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
anim.delegate = self;
// 图层动画都是假象, 在动画执行过程中, 图层的position属性一直都没有变过
[self.redView.layer addAnimation:anim forKey:nil];
// self.myview.layer.position == CGPointMake(0, 0)
}
- (void)animateStop
{
NSLog(@"%@", NSStringFromCGPoint(self.redView.layer.position));
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSLog(@"%@", NSStringFromCGPoint(self.redView.layer.position));
}
动画执行完毕后:
view(正常移动到(200, 300)点) layer(居然不是(0, 0)点)
view执行动画例子:(点击控制器view,浏览图片)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentIndex++;
if (self.currentIndex == 3) {
self.currentIndex = 0;
}
NSString *filename = [NSString stringWithFormat:@"%d.jpg", self.currentIndex + 1];
self.imageView.image = [UIImage imageNamed:filename];
[UIView transitionWithView:self.imageView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromTop animations:nil completion:nil];
}
运行效果:(向里翻滚的效果)
到这里基于CALaye和r基于UIView的动画介绍完了,后者相比于前者是不是很简单呢