CALayer 和 Core Animation

CALayer

一、简介

1.CALayer

简单的说是层(图层)的概念,类似与PS中的图层。一个用来完成绘制、渲染、动画等效果的可见的容器。

2.CALayer 与 UIView 的联系与区别

UIView中有一个layer属性,为根图层。UIView之所以能显示在屏幕上,完全是因为它内部的CALayer对象。在创建UIView对象时,UIView内部会自动创建一个层(即CALayer对象),通过UIView的layer属性可以访问这个层。当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示。UIView本身不具备显示的功能,显示的是UIView的CALayer。
UIView可以通过subviews属性访问所有的子视图,同样地,CALayer也可以通过sublayers属性访问所有的子层;UIView可以通过superview属性访问父视图,同样地,CALayer也可以通过superlayer属性访问父层。
UIView可以通过addSubview:方法添加子视图,同样地,CALayer可以通过addSublayer:方法添加子层。

在使用时,往往CALayer和UIView都能实现相同的显示效果,但是UIView具有交互的能力,可以处理事件。如果显示出来的东西需要跟用户进行交互的话,那么就要使用用UIView。

3.常用属性

属性 说明 是否支持隐式动画
anchorPoint 和中心点position重合的一个点,称为“锚点”,锚点的描述是相对于x、y位置比例而言的默认在图像中心点(0.5,0.5)的位置
backgroundColor 图层背景颜色
borderColor 边框颜色
borderWidth 边框宽度
bounds 图层大小
contents 图层显示的内容
contentsRect 图层显示内容的大小和位置
cornerRadius 圆角半径
doubleSided 图层背面是否显示,默认为YES
frame 图层大小和位置
hidden 是否隐藏
mask 图层蒙版
maskToBounds 子图层是否剪切图层边界,默认为NO
opacity 透明度
position 图层中心点位置
shadowColor 阴影颜色
shadowOffset 阴影偏移量
shadowOpacity 阴影透明度,默认为0,设置阴影必须设置此属性
shadowPath 阴影的形状
shadowRadius 阴影模糊半径
sublayers 子图层
sublayerTransform 子图层形变
transform 图层形变

注:

  • 在CALayer中很少使用frame属性,因为frame本身不支持隐式动画,通常使用bounds和position代替
  • anchorPoint属性是图层的锚点,范围在(01,01)表示在x、y轴的比例,这个点决定着CALayer身上的哪个点会与position所指定的位置重合,当图层中心点固定后,调整anchorPoint即可达到调整图层显示位置的作用(因为它永远和position重合)

4.隐式动画属性

每一个UIView内部都默认关联着一个CALayer,这个CALayer为Root Layer(根层)。对于所有的非Root Layer,即手动创建的CALayer对象,当对非Root Layer的部分属性进行相应的修改时,默认会自动产生一些动画效果,这些属性称为Animatable Properties(可动画属性)。隐式属性动画的本质是这些属性的变动默认隐含了CABasicAnimation动画实现,例如修改bounds属性会产生缩放动画,修改backgroundColor属性会产生背景色的渐变动画,修改position属性会产生平移动画。

可以通过动画事务(CATransaction)关闭默认的隐式动画效果
[CATransactionbegin];
[CATransactionsetDisableActions:YES];
self.myview.layer.position= CGPointMake(100, 100);
[CATransactioncommit];

二、简单使用

1. 设置阴影

  imageView.layer.shadowColor = [UIColor grayColor].CGColor;  //阴影颜色
  imageView.layer.shadowOffset = CGSizeMake(10, 10); //阴影偏移量
  imageView.layer.shadowOpacity = 0.5;  //阴影透明度

2.设置圆角

 imageView.layer.masksToBounds = YES;   
 imageView.layer.cornerRadius = 10;     //当cornerRadius等于边长的一半时,就是圆

3.设置边框

 imageView.layer.borderWidth = 5;
 imageView.layer.borderColor = [UIColor redColor].CGColor;

4.设置旋转

 imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);// 顺时针旋转45

5.绘制图层

绘制图层有两种方法

1.通过图层代理drawLayer: inContext:方法绘制
2.通过自定义图层drawInContext:方法绘制
不管使用哪种方法绘制完必须调用图层的setNeedDisplay方法.

① 使用代理方法绘制图层
通过代理方法进行图层绘图只要指定图层的代理,然后在代理对象中重写-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法即可。需要注意这个方法虽然是代理方法但是不用手动实现CALayerDelegate,因为CALayer定义中给NSObject做了分类扩展,所有的NSObject都包含这个方法。另外设置完代理后必须要调用图层的setNeedDisplay方法,否则绘制的内容无法显示。

- (void)viewDidLoad {
    [super viewDidLoad];

    CALayer * layer = [[CALayer alloc]init];
    layer.bounds = CGRectMake(0, 0, 100, 100);
    layer.position = CGPointMake(50, 50);
    layer.masksToBounds = YES;
    layer.cornerRadius = 50;
    layer.borderWidth = 2;
    layer.borderColor = [UIColor yellowColor].CGColor;

    layer.delegate = self; //设置代理
    [self.view.layer addSublayer:layer]; //添加图层
    [layer setNeedsDisplay]; //重绘
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    CGContextSaveGState(ctx);
    CGContextScaleCTM(ctx, 1, -1); //转换坐标系
    CGContextTranslateCTM(ctx, 0, -100);
    UIImage * image = [UIImage imageNamed:@"me"];
    CGContextDrawImage(ctx, CGRectMake(0, 0, 100, 100), image.CGImage);
    CGContextRestoreGState(ctx);
}

② 使用自定义图层绘制图层
创建一个CALayer的子类,然后重写drawInContext:方法,在方法中使用Quartz2D API进行绘图;
同样,使用时,必须要调用图层的setNeedDisplay方法,才会触发drawInContext:方法的调用。

Core Animation

一、简介

1.概念

Core Animation 是作用在CALayer上的,使用Core Animation 创建动画不仅简单,而且还有更好的性能,因为Core Animation 是在单独的线程中完成的,不会阻塞主线程,而且只会重绘界面上变化的部分(局部刷新)。

2.分类

类别 说明
CAAnimation 所有动画类的基类,不能直接使用,实现了CAMediaTiming协议,提供了动画的持续时间、速度和重复计数等,还实现了CAAction协议,该协议为CALayer动画触发的动作提供标准化响应。
CATransition CAAnimation的子类,转场动画,能够为层提供移出屏幕和移入屏幕的动画效果
CAAnimationGroup CAAnimation的子类,动画组,是一种组合模式设计,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CAPropertyAnimation CAAnimation的子类,属性动画的基类,通过控制可动画属性慢慢地变化,不能直接使用。有两个子类CABasicAnimation 和 CAKeyframeAnimation。
CABasicAnimation CAPropertyAnimation 的子类,基础动画,简单地控制CALayer层的属性慢慢改变,从而实现动画。
CAKeyframeAnimation CAPropertyAnimation 的子类,关键帧动画,同样是通过属性进行动画参数控制,通过values属性指定多个关键帧,通过多个关键帧可以指定动画的各阶段的关键值。

基础动画、关键帧动画都属于属性动画,是通过修改属性值产生动画效果,只需要设置初始值和结束值,中间的过程动画(“补间动画”)是由系统自动计算产生。和基础动画不同的是关键帧动画可以设置多个属性值,每两个属性中间的补间动画由系统自动完成,基础动画也可以看成是有两个关键帧的关键帧动画。

二、使用

1. CAAnimation

CAAnimation 是基类,其属性它的子类也拥有。它不能直接使用。

属性 说明
beginTime 可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
duration 动画的持续时间
delegate 动画代理
repeatCount 重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT
repeatDuration 重复时间
removedOnCompletion 默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,同时还要设置fillMode为kCAFillModeForwards
fillMode 决定当前对象在非active时间段的行为。(要想fillMode有效,最好设置removedOnCompletion = NO),kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态;kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态;kCAFillModeBackwards 在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始;kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
timingFunction 速度控制函数,控制动画运行的节奏,为 CAMediaTimingFunction 类型。kCAMediaTimingFunctionLinear(线性):匀速动画;kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开;kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地;kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。

代理方法 : 给NSObject添加了分类,所以任何对象,都可以成为CAAnimation的代理。

动画开始的时候调用 - (void)animationDidStart:(CAAnimation *)anim;
动画停止的时候调用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

2.CAPropertyAnimation

属性或方法 说明
+ (instancetype)animationWithKeyPath:(nullable NSString *)path; 类方法,创建一个新的动画对象,同时设置该对象的keyPath属性
keyPath 属性,用来描述动画是作用在哪一个属性上
additive 属性,指定该属性动画是否以当前动画效果为基础
cumulative 属性,指定动画是否为累加效果
valueFunction 该属性值是一个 CAValueFunction 对象,负责对属性改变进行插值计算,系统已经提供了默认的插值计算方式,一般无须指定该属性。

位移动画通常使用属性动画控制 CALayer 的 position 属性持续改变,如果要实现 CALayer 的旋转、缩放等动画效果,若在平面上(二维空间)上,则修改 CALayer 的 affineTransform 属性,该属性值为一个CGAffineTransform 对象;若在三维空间上,则修改 CALayer 的 transform 属性,该属性值为一个 CATransform3D 对象。

CATransform3D : 三维变换矩阵

方法 说明
CATransform3DIsIdentity (CATransform3D t) 返回bool值,判断 t 矩阵是否为单位矩阵
CATransform3DEqualToTransform (CATransform3D a, CATransform3D b) 返回bool值,判断 a , b 两个矩阵是否相等
CATransform3DMakeTranslation (CGFloat tx, CGFloat ty, CGFloat tz) 创建一个在X方向上移动 tx、在Y方向上移动 ty、在Z方向上移动 tz 的变化矩阵
CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz) 创建一个在X方向上缩放 sx、在Y方向上缩放 sy、在Z方向上缩放 sz 的变化矩阵
CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 创建基于指定旋转轴旋转 angle 弧度的变换,x、y、z 用于确定旋转轴,(1,0,0)指定旋转轴为X轴,(0,1,0)指定旋转轴为Y轴,(0,0,1)指定旋转轴为Z轴
CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz) 在 t 变化矩阵的基础上进行位移变换
CATransform3DScale (CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz) 在 t 变化矩阵的基础上进行缩放变换
CATransform3DRotate (CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 在 t 变化矩阵的基础上进行旋转变换
CATransform3DConcat (CATransform3D a, CATransform3D b) 将a,b两个矩阵相乘
CATransform3DInvert (CATransform3D t) 对 t 矩形进行反转
CATransform3DMakeAffineTransform (CGAffineTransform m) 将 CGAffineTransform 矩阵转化成 CATransform3D 矩阵,转化后也只有X,Y维度上的变换
CATransform3DIsAffine (CATransform3D t) 返回bool值,判断 t 矩阵是否是 CGAffineTransform 变换矩阵
CATransform3DGetAffineTransform (CATransform3D t) 获取 t 矩阵所包含的 CGAffineTransform 变换矩阵

3.CABasicAnimation 和 CAKeyframeAnimation

CAPropertyAnimation的子类,首先创建初始化一个CALayer,初始化动画CABasicAnimation是设置 fromValue 和 toValue ,CAKeyframeAnimation是设置 values ,再设置其他动画属性,将动画添加到图层上。

CALayer 为动画提供的方法

方法 说明
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key 为 CALayer 添加一个动画,并为动画指定一个唯一标识
- (nullable CAAnimation *)animationForKey:(NSString *)key 让 CALayer 执行 key 所对应的动画
- (void)removeAllAnimations 删除 CALayer 上添加的所有动画
- (void)removeAnimationForKey:(NSString *)key 删除 key 所对应的动画
- (nullable NSArray<NSString *> *)animationKeys 获取 CALayer 上所有动画的 key 组成的数组

① 位移动画
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"position"];
anim.fromValue = [NSValue valueWithCGPoint:self.layer.position];
CGPoint toPoint = CGPointMake(300, 300);
anim.toValue = [NSValue valueWithCGPoint:toPoint];
anim.duration = 1.0;
self.layer.position = toPoint;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Position"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue * value1 = [NSValue valueWithCGPoint:self.layer.position];
NSValue * value2 = [NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue * value3 = [NSValue valueWithCGPoint:CGPointMake(45, 300)];
NSValue * value4 = [NSValue valueWithCGPoint:CGPointMake(55, 400)];
NSArray * values=@[value1,value2,value3,value4];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];    

通过描绘路径进行关键帧动画控制

CAKeyframeAnimation * keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, self.layer.position.x, self.layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);
keyframeAnimation.path = path;
CGPathRelease(path);
keyframeAnimation.duration = 4.0;
keyframeAnimation.removedOnCompletion = YES;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];

② 旋转动画
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
anim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D toValue = CATransform3DRotate(fromValue, M_PI, 1, 0, 0);
anim.toValue = [NSValue valueWithCATransform3D:toValue];
anim.duration = 0.5;
self.layer.transform = toValue;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Rotate"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue * value1 = [NSValue valueWithCATransform3D:self.layer.transform];
NSValue * value2 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 0, 0)];
NSValue * value3 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 1, 0)];
NSValue * value4 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 0, 1)];
NSArray * values = @[value1,value2,value3,value4];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Rotate"];

③ 缩放动画
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
anim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D toValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
anim.toValue = [NSValue valueWithCATransform3D:toValue];
anim.duration = 0.5;
self.layer.transform = toValue;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Scale"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue * value1 = [NSValue valueWithCATransform3D:self.layer.transform];
NSValue * value2 = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 1)];
NSValue * value3 = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
NSArray * values = @[value1,value2,value3];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Scale"];

④ 动画组
CABasicAnimation

CGPoint fromPoint = self.layer.position;
CGPoint toPoint = CGPointMake(280, fromPoint.y + 300);

CABasicAnimation * moveAnim = [CABasicAnimation animationWithKeyPath:@"position"];
moveAnim.fromValue = [NSValue valueWithCGPoint:fromPoint];
moveAnim.toValue = [NSValue valueWithCGPoint:toPoint];
moveAnim.removedOnCompletion = YES;

CABasicAnimation * transformAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
transformAnim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D scaleValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
CATransform3D rotateValue = CATransform3DRotate(fromValue, M_PI, 0, 0, 1);
CATransform3D toValue = CATransform3DConcat(scaleValue, rotateValue);
transformAnim.toValue = [NSValue valueWithCATransform3D:toValue];
transformAnim.cumulative = YES;
transformAnim.repeatCount = 2;
transformAnim.duration = 3;

CAAnimationGroup * animGroup = [CAAnimationGroup animation];
animGroup.animations = @[moveAnim,transformAnim];
animGroup.duration = 6;
[self.layer addAnimation:animGroup forKey:@"KCBasicAnimation_Group"];

CAKeyframeAnimation

CAKeyframeAnimation * moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue * move1 = [NSValue valueWithCGPoint:self.layer.position];
NSValue * move2 = [NSValue valueWithCGPoint:CGPointMake(300, 100)];
NSValue * move3 = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
NSValue * move4 = [NSValue valueWithCGPoint:CGPointMake(100, 300)];
NSValue * move5 = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSArray * values = @[move1,move2,move3,move4,move5];
moveAnim.values = values;
moveAnim.removedOnCompletion = YES;

CAKeyframeAnimation * transformAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
CATransform3D rotate1 = self.layer.transform;
CATransform3D rotate2 = CATransform3DMakeRotation(M_PI, 1, 0, 0);
CATransform3D rotate3 = CATransform3DMakeRotation(M_PI, 0, 1, 0);
CATransform3D rotate4 = CATransform3DMakeRotation(M_PI, 0, 0, 1);
CATransform3D rotate5 = CATransform3DMakeRotation(M_PI, 1, 1, 1);

CATransform3D scale1 = self.layer.transform;
CATransform3D scale2 = CATransform3DMakeScale(0.5, 0.5, 1);
CATransform3D scale3 = CATransform3DMakeScale(1, 1, 1);
CATransform3D scale4 = CATransform3DMakeScale(0.5, 0.5, 1);
CATransform3D scale5 = CATransform3DMakeScale(1, 1, 1);

NSValue * transform1 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate1, scale1)];
NSValue * transform2 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate2, scale2)];
NSValue * transform3 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate3, scale3)];
NSValue * transform4 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate4, scale4)];
NSValue * transform5 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate5, scale5)];
transformAnim.values = @[transform1,transform2,transform3,transform4,transform5];
transformAnim.removedOnCompletion = YES;

CAAnimationGroup * animGroup = [CAAnimationGroup animation];
animGroup.animations = @[moveAnim,transformAnim];
animGroup.duration = 8;
self.layer.position = CGPointMake(100, 100);
[self.layer addAnimation:animGroup forKey:@"KCBasicAnimation_Group"];

④ 动画暂停

-(void)pauseLayer{
    CFTimeInterval interval=[self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    [self.layer setTimeOffset:interval];
    self.layer.speed=0;
}

⑤ 动画恢复

-(void)resumeLayer{
    CFTimeInterval beginTime= CACurrentMediaTime() - self.layer.timeOffset;
    self.layer.timeOffset = 0;
    self.layer.beginTime = beginTime;
    self.layer.speed=1.0;
}

4.CATransition

属性 说明
type 动画过渡效果
subtype 动画过渡方向
startProgress 动画起点(在整体动画的百分比)
endProgress 动画终点(在整体动画的百分比)

CATransition的type属性用于控制动画类型,还有私有动画。

值| 说明 |是否支持方向设置
-----|-----
kCATransitionFade|通过渐隐效果过渡,默认属性值|是
kCATransitionMoveIn|通过移入动画过渡,新视图移到旧视图上面|是
kCATransitionPush|通过推入动画过渡,新视图把旧视图推出去|是
kCATransitionReveal|通过揭开动画过渡,将旧视图移开,同时显示下面的新视图|是
@"cube"|通过立方体旋转动画过渡|是
@"oglFlip"|通过翻转动画过渡|是
@"suckEffect"|通过收缩(吸入)动画过渡|否
@"rippleEffect"|通过水波动画过渡|否
@"pageCurl"|通过向后翻页动画过渡|是
@"pageUnCurl"|通过向前翻页动画过渡|是
@"cameraIrisHollowOpen"|通过相机镜头打开效果动画过渡|否
@"cameraIrisHollowClose"|通过相机镜头关闭效果动画过渡|否

CATransition的subtype属性用于控制动画方向

说明
kCATransitionFromRight 从右侧转场
kCATransitionFromLeft 从左侧转场
kCATransitionFromTop 从顶部转场
kCATransitionFromBottom 从底部转场
#import "ViewController.h"
#define IMAGE_COUNT 5

typedef enum {
    GestureDirectionUp = 1,
    GestureDirectionDown,
    GestureDirectionLeft,
    GestureDirectionRight
}GestureDirection;

@interface ViewController () <UIPickerViewDelegate,UIPickerViewDataSource>

@property (nonatomic,strong) UIImageView * imageView;
@property (nonatomic,assign) int currentIndex;
@property (nonatomic,strong) CATransition * transition;
@property (nonatomic,strong) UIPickerView * pickerView;
@property (nonatomic,copy) NSArray * transitions;
@property (nonatomic,assign) BOOL isPop;

@end

@implementation ViewController

- (NSArray *)transitions{
    if (_transitions == nil) {
        _transitions = @[kCATransitionFade,kCATransitionMoveIn,kCATransitionPush,kCATransitionReveal,@"cube",@"oglFlip",@"suckEffect",@"rippleEffect",@"pageCurl",@"pageUnCurl",@"cameralIrisHollowOpen",@"cameraIrisHollowClose"];
    }
    return _transitions;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];

    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;

    self.navigationItem.title = @"转场";
    UIBarButtonItem * rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"效果" style:UIBarButtonItemStyleDone target:self action:@selector(selectTransition:)];
    self.navigationItem.rightBarButtonItem = rightBarButtonItem;

    self.imageView = [[UIImageView alloc]initWithFrame:self.view.frame];
    self.imageView.backgroundColor = [UIColor whiteColor];
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    self.imageView.image = [UIImage imageNamed:@"0.jpg"];
    [self.view addSubview:self.imageView];

    self.pickerView = [[UIPickerView alloc]initWithFrame:CGRectMake(0, screenHeight, screenWidth, 300)];
    self.pickerView.dataSource = self;
    self.pickerView.delegate = self;
    [self.view addSubview:self.pickerView];

    UISwipeGestureRecognizer * upSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [upSwipeGesture setDirection:UISwipeGestureRecognizerDirectionUp];
    [self.view addGestureRecognizer:upSwipeGesture];

    UISwipeGestureRecognizer * downSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [downSwipeGesture setDirection:UISwipeGestureRecognizerDirectionDown];
    [self.view addGestureRecognizer:downSwipeGesture];

    UISwipeGestureRecognizer * leftSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [leftSwipeGesture setDirection:UISwipeGestureRecognizerDirectionLeft];
    [self.view addGestureRecognizer:leftSwipeGesture];

    UISwipeGestureRecognizer * rightSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [rightSwipeGesture setDirection:UISwipeGestureRecognizerDirectionRight];
    [self.view addGestureRecognizer:rightSwipeGesture];

    self.transition = [[CATransition alloc]init];
    self.transition.type = kCATransitionFade;
    self.isPop = NO;
}

- (void)selectTransition:(UIBarButtonItem *)btn{
    self.isPop = !self.isPop;
    if (self.isPop) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:nil context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.6];
        [self.view bringSubviewToFront:self.pickerView];
        CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
        CGFloat pickerViewWidth = [UIScreen mainScreen].bounds.size.width;
        CGFloat pickerViewHeight = 300;
        self.pickerView.frame = CGRectMake(0, screenHeight - pickerViewHeight, pickerViewWidth, pickerViewHeight);
        [UIView commitAnimations];
}
else {
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:nil context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.6];
        CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
        CGFloat pickerViewWidth = [UIScreen mainScreen].bounds.size.width;
        CGFloat pickerViewHeight = 300;
        self.pickerView.frame = CGRectMake(0, screenHeight, pickerViewWidth, pickerViewHeight);
        [UIView commitAnimations];
    }
}

-(void)swipe:(UISwipeGestureRecognizer *)gesture{
    if (gesture.direction == UISwipeGestureRecognizerDirectionUp){
        [self transitionAnimation:GestureDirectionUp];
    }
    else if (gesture.direction == UISwipeGestureRecognizerDirectionDown){
        [self transitionAnimation:GestureDirectionDown];
    }
    else if (gesture.direction == UISwipeGestureRecognizerDirectionLeft){
        [self transitionAnimation:GestureDirectionLeft];
    }
    else {
        [self transitionAnimation:GestureDirectionRight];
    }
}

- (void)transitionAnimation:(GestureDirection)gestureDirection{
    switch (gestureDirection) {
        case GestureDirectionUp:
            self.transition.subtype = kCATransitionFromTop;
            break;
        case GestureDirectionDown:
            self.transition.subtype = kCATransitionFromBottom;
           break;
        case GestureDirectionLeft:
            self.transition.subtype = kCATransitionFromRight;
            break;
        case GestureDirectionRight:
            self.transition.subtype = kCATransitionFromLeft;
            break;
        default:
            break;
    }
    self.transition.duration = 1.0f;
    self.imageView.image = [self getImage];
    [self.view.layer addAnimation:self.transition forKey:@"KCTransitionAnimation"];
}

- (UIImage *)getImage{
    self.currentIndex = (self.currentIndex + 1) % IMAGE_COUNT;
    NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
    return [UIImage imageNamed:imageName];
}

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    return self.transitions.count;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    return self.transitions[row];
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    self.transition.type = self.transitions[row];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

5.逐帧动画

通过设置UIImageView的animationImages属性,然后调用它的startAnimating方法去播放一组逐帧图片,但是它存在着很大的性能问题,并且这种方法一旦设置完图片中间的过程就无法控制了。使用CADisplayLink加入到主循环队列中,循环调用目标方法,在这个方法中更新视图内容就可以完成逐帧动画。

- (void)runAnimationWithImageCount:(NSInteger)count AndImageName:(NSString *)name
{
    if (self.myImageView.isAnimating)
    {
        return;
    }
    NSMutableArray * pictArray = [NSMutableArray array];
    for (int i = 0; i < count; i++)
    {
        NSString * pictNum = [NSString stringWithFormat:@"%@_%02d.jpg",name,i];
        // 使用imageNamed的方式加载图片会有缓存,使得占用的内存很大
        //[pictArray addObject:[UIImage imageNamed:pictNum]];
    
        // 直接从文件中读取图片就不会占用太大的内存
        NSBundle * boudle = [NSBundle mainBundle];
        NSString * path = [boudle pathForResource:pictNum ofType:nil];
        [pictArray addObject:[UIImage imageWithContentsOfFile:path]];
    }
    self.myImageView.animationImages = pictArray;
    self.myImageView.animationDuration = (self.myImageView.animationImages.count * 0.07);
    CGFloat delayTime = self.myImageView.animationDuration + 0.5;
    self.myImageView.animationRepeatCount = 1;
    [self.myImageView startAnimating];

    [self.myImageView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:delayTime];
}

6.UIView动画封装

UIView本身对于基本动画和关键帧动画、转场动画都有相应的封装。

方法 说明
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context 开始动画,animationID :动画块内部应用程序标识,context :附加的信息,都传递给动画代理
+ (void)commitAnimations 提交动画,结束动画
+ (void)setAnimationDelegate:(nullable id)delegate 设置动画的代理
+ (void)setAnimationWillStartSelector:(nullable SEL)selector 设置动画开始时的执行方法
+ (void)setAnimationDidStopSelector:(nullable SEL)selector 设置动画结束时的执行方法
+ (void)setAnimationDuration:(NSTimeInterval)duration 设置动画执行时间
+ (void)setAnimationDelay:(NSTimeInterval)delay 设置动画延迟执行的时间
+ (void)setAnimationStartDate:(NSDate *)startDate 设置动画开始执行的时间
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve 设置动画运行过程中相对的速度
+ (void)setAnimationRepeatCount:(float)repeatCount 设置动画重复次数
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses 设置是否自动逆向动画
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState 设置动画是否从当前状态开始执行,设置为YES,则当上一次动画正在执行中,那么当下一个动画开始时,上一次动画的当前状态将成为下一次动画的开始状态。设置为NO,则当上一个动画正在执行中,那么当下一个动画开始时,上一次动画需要先恢复到完成时的状态,然后在开始执行下一次动画
+ (void)setAnimationsEnabled:(BOOL)enabled 设置动画是否可用
+ (BOOL)areAnimationsEnabled 获取动画是否可用

动画使用block方式

方法 说明
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations 使用block方式
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式,有完成后的block
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式,可以设置动画延迟时间和动画参数
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 转场动画
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion 转场动画,toView添加到fromView的父视图中,fromView从它的福视图中移除
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式实现关键帧动画
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations 从开始时间添加一个持续时间的动画

UIViewAnimationCurve

说明
UIViewAnimationCurveEaseIn 先慢后快,在动画刚开始的时候执行速度慢
UIViewAnimationCurveEaseOut 先快后慢,在动画快结束的时候执行速度慢
UIViewAnimationCurveEaseInOut 先由慢变快,然后再变慢,在动画刚开始和快结束的时候速度慢
UIViewAnimationCurveLinear 匀速运行

UIViewAnimationOptions

说明
UIViewAnimationOptionLayoutSubviews 动画过程中保证子视图跟随运动
UIViewAnimationOptionAllowUserInteraction 动画过程中允许用户交互
UIViewAnimationOptionBeginFromCurrentState 所有视图从当前状态开始运行
UIViewAnimationOptionRepeat 重复执行动画
UIViewAnimationOptionAutoreverse 逆向动画,动画运行到结束点后仍然以动画方式回到初始点
UIViewAnimationOptionOverrideInheritedDuration 忽略嵌套动画时间设置
UIViewAnimationOptionOverrideInheritedCurve 忽略嵌套动画速度设置
UIViewAnimationOptionAllowAnimatedContent 动画过程允许重绘视图(仅适用在转场动画中)
UIViewAnimationOptionShowHideTransitionViews 视图切换时翻转隐藏旧视图、显示新视图,而不再是直接删除旧视图、添加新视图
UIViewAnimationOptionOverrideInheritedOptions 不继承任何参数或动画类型
UIViewAnimationOptionCurveEaseInOut 默认值,和UIViewAnimationCurveEaseInOut效果一样,只是这个值用于block动画参数,先由慢变快,然后再变慢,在动画刚开始和快结束的时候速度慢
UIViewAnimationOptionCurveEaseIn 和UIViewAnimationCurveEaseIn效果一样,先慢后快,在动画刚开始的时候执行速度慢
UIViewAnimationOptionCurveEaseOut 和UIViewAnimationCurveEaseOut效果一样,先快后慢,在动画快结束的时候执行速度慢
UIViewAnimationOptionCurveLinear 和UIViewAnimationCurveLinear效果一样,匀速运行
UIViewAnimationOptionTransitionNone 下面的值仅适用于转场动画设置,没有转场动画效果
UIViewAnimationOptionTransitionFlipFromLeft 从左侧翻转
UIViewAnimationOptionTransitionFlipFromRight 从右侧翻转
UIViewAnimationOptionTransitionCurlUp 向后翻页的动画过渡
UIViewAnimationOptionTransitionCrossDissolve 水波动画过渡
UIViewAnimationOptionTransitionFlipFromTop 从顶部翻转
UIViewAnimationOptionTransitionFlipFromBottom 从底部翻转

UIViewKeyframeAnimationOptions
其中一部分值和 UIViewAnimationOptions 是一样的,不一样的如下:

说明
UIViewKeyframeAnimationOptionCalculationModeLinear 连续运算模式
UIViewKeyframeAnimationOptionCalculationModeDiscrete 离散运算模式
UIViewKeyframeAnimationOptionCalculationModePaced 均匀执行运算模式
UIViewKeyframeAnimationOptionCalculationModeCubic 平滑运算模式
UIViewKeyframeAnimationOptionCalculationModeCubicPaced 平滑均匀运算模式

7.弹簧动画

方法 说明
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion UIViewAnimationWithBlocks中的方法,用来创建具有弹簧效果的动画。dampingRatio为阻尼,范围0~1,阻尼越接近于0,弹性效果越明显。velocity为弹性复位的速度。
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
    self.imageView.center = CGPointMake(300, 300);
} completion:nil];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容