学完上一篇iOS-CAlayer之后,我们就可以学习一下核心动画了。
一. CoreAnimation简介
1. 层级
首先看了解一下层级: 框架层级.png其中上面的层级可以访问下面的层级,可以看出CoreAnimation也会使用CoreGraphics里面的一些东西。
2. CoreAnimation可以做什么?
进入CoreAnimation.h文件
#include <QuartzCore/CABase.h>
#include <QuartzCore/CATransform3D.h>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CADisplayLink.h> //定时器相关
#import <QuartzCore/CAEAGLLayer.h>
#import <QuartzCore/CAEmitterCell.h>
#import <QuartzCore/CAEmitterLayer.h> //一些layer层
#import <QuartzCore/CAGradientLayer.h>
#import <QuartzCore/CALayer.h> //我们熟悉的layer层
#import <QuartzCore/CAMediaTiming.h>
#import <QuartzCore/CAMediaTimingFunction.h> //时间相关
#import <QuartzCore/CAReplicatorLayer.h>
#import <QuartzCore/CAScrollLayer.h>
#import <QuartzCore/CAShapeLayer.h>
#import <QuartzCore/CATextLayer.h>
#import <QuartzCore/CATiledLayer.h>
#import <QuartzCore/CATransaction.h>
#import <QuartzCore/CATransform3D.h> //矩阵转换
#import <QuartzCore/CATransformLayer.h>
#import <QuartzCore/CAValueFunction.h>
#endif
可以发现CoreAnimation不单单包含动画还包含一些其他的东西,具体可看上面注释。
- Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
- Core Animation是跨平台的,可以用在Mac OS X和iOS平台。
- Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。不阻塞主线程,可以理解为在执行动画的时候还能点击(按钮)。
- 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView。
3. CAAnimation继承关系
CAAnimation继承图如下: CAAnimation.png- CAPropertyAnimation是CAAnimation的子类,但是不能直接使用,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation。
- CAMediaTiming是一个协议(protocol)。
- 能用的动画类只有4个子类:CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup、CATransition。
部分属性解析:
其中带*的表示来自CAMediaTiming协议的属性。
duration: *//动画的持续时间
repeatCount: *//动画的重复次数
repeatDuration: *//动画的重复时间
fillMode: *//决定当前对象在非active时间段的行为,比如动画开始之前,动画结束之后
beginTime: *//可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
removedOnCompletion://默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
timingFunction://速度控制函数,控制动画运行的节奏
delegate://动画代理
二. CABasicAnimation
CABasicAnimation是CAPropertyAnimation的子类。
属性解析:
fromValue: //keyPath相应属性的初始值
toValue: //keyPath相应属性的结束值
随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue。
如果 fillMode = kCAFillModeForwards 和 removedOnComletion = NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。
1. CABasicAnimation的简单使用
比如进行平移、旋转、缩放的动画,代码如下:
#import "ViewController.h"
@interface ViewController () <CAAnimationDelegate>
@property(nonatomic,strong) CALayer *myLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//创建layer
CALayer *myLayer=[CALayer layer];
//设置layer的属性
myLayer.bounds=CGRectMake(0, 0, 150, 60);
myLayer.backgroundColor=[UIColor yellowColor].CGColor;
myLayer.position=CGPointMake(100, 100);
myLayer.anchorPoint=CGPointMake(0, 0);
myLayer.cornerRadius=40;
//添加layer
self.myLayer=myLayer;
[self.view.layer addSublayer:myLayer];
}
//设置动画
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//[self pingyi];
//[self suofang];
[self xuanzhuan];
}
//平移
- (void)pingyi {
//1.创建核心动画
// CABasicAnimation *anima=[CABasicAnimation animationWithKeyPath:<#(NSString *)#>]
CABasicAnimation *anima=[CABasicAnimation animation];
//1.1告诉系统要执行什么样的动画
anima.keyPath=@"position";
//设置通过动画,将layer从哪儿移动到哪儿
anima.fromValue=[NSValue valueWithCGPoint:CGPointMake(0, 0)];
anima.toValue=[NSValue valueWithCGPoint:CGPointMake(200, 300)];
//1.2设置动画执行完毕之后不删除动画
anima.removedOnCompletion=NO;
//1.3设置保存动画的最新状态
anima.fillMode = @"forwards";
//anim.fillMode = kCAFillModeForwards; // 和上面一行一样的
anima.delegate = self;
NSString *str = NSStringFromCGPoint(self.myLayer.position);
NSLog(@"执行前:%@",str);
//2.添加核心动画到layer
[self.myLayer addAnimation:anima forKey:nil];
}
//缩放
- (void)suofang {
//缩放动画可以实现心跳效果
//创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
//设置属性值
anim.keyPath = @"transform.scale";
anim.toValue = @0;
//设置动画执行次数
anim.repeatCount = MAXFLOAT;
//设置动画执行时长
anim.duration = 3;
//自动反转(怎么样去 怎么样回来)
anim.autoreverses = YES;
//添加动画
[self.myLayer addAnimation:anim forKey:nil];
}
//旋转
- (void)xuanzhuan {
//1.创建动画
CABasicAnimation *anima=[CABasicAnimation animationWithKeyPath:@"transform"];
//1.1设置动画执行时间
anima.duration=2.0;
//1.2修改属性,执行动画
anima.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2+M_PI_4, 1, 1, 0)];
//1.3设置动画执行完毕后不删除动画
anima.removedOnCompletion=NO;
//1.4设置保存动画的最新状态
anima.fillMode=kCAFillModeForwards;
//2.添加动画到layer
[self.myLayer addAnimation:anima forKey:nil];
}
#pragma mark - CAAnimationDelegate代理
-(void)animationDidStart:(CAAnimation *)anim
{
NSLog(@"开始执行动画");
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
//动画执行完毕,打印执行完毕后的position值
NSString *str = NSStringFromCGPoint(self.myLayer.position);
NSLog(@"执行后:%@",str);
}
@end
平移:
平移.gif
缩放:
缩放.gif
旋转:
旋转.gif
打印结果:
2019-11-08 14:21:41.030264+0800 核心动画[10803:9330751] 执行前:{100, 100}
2019-11-08 14:21:41.031078+0800 核心动画[10803:9330751] 开始执行动画
2019-11-08 14:21:41.282279+0800 核心动画[10803:9330751] 执行后:{100, 100}
打印position的属性值,验证了,进行平移动画后,图层的属性值还是动画执行前的初始值{100,100},并没有真正被改变为{200,300}。
2. 贝塞尔曲线
UIBezierPath是苹果封装的路径,一般作为动画的路径(推荐使用)。
下面使用贝塞尔曲线和CABasicAnimation实现如下效果: 效果.gif代码:
- (void)animationCaseOne {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
imageView.clipsToBounds = YES;
[self.view addSubview:imageView];
//添加一层浅白色layer
layer = [CAShapeLayer layer];
//CAShapeLayer占用空间比较少,CAShapeLayer和UIBezierPath是黄金搭档
/*
圆心
半径
开始角度
结束角度
顺时针
*/
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:-M_PI_2 endAngle:3*M_PI/2 clockwise:YES];
layer.path = path.CGPath;
layer.fillColor = [UIColor clearColor].CGColor; //填充色,默认是黑色的
layer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //绘制色
//线宽设置为半径100
layer.lineWidth = 100.f;
[imageView.layer addSublayer:layer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//strokeStart单位属性0-1 开始画 结束画
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
anim.duration = 4.f;
anim.fromValue = @0;
anim.toValue = @1;
[layer addAnimation:anim forKey:nil];
}
下面有另外一个需求,如下实现如下图片:
效果图.png
有三种方式实现,代码如下,就不解释了
/*
shapelayer.fillRule:1、kCAFillRuleEvenOdd 2、kCAFillRulenonzero
nonzero字面意思是“非零”。按该规则,要判断一个点是否在图形内,从该点作任意方向的一条射线,然后检测射线与图形路径的交点情况。从0开始计数,路径从左向右穿过射线则计数加1,从右向左穿过射线则计数减1。得出计数结果后,如果结果是0,则认为点在图形外部,否则认为在内部
evenodd字面意思是“奇偶”。按该规则,要判断一个点是否在图形内,从该点作任意方向的一条射线,然后检测射线与图形路径的交点的数量。如果结果是奇数则认为点在内部,是偶数则认为点在外部
*/
- (void)animationCaseTwoMethod1 {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
[self.view addSubview:imageView];
//矩形
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
[path addArcWithCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.fillRule = kCAFillRuleEvenOdd; //改变判断是不是在圆内的规则
layer.fillColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //然后画上去
[imageView.layer addSublayer:layer];
}
- (void)animationCaseTwoMethod2 {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
[self.view addSubview:imageView];
//rct
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
UIBezierPath *pathOne = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
[path appendPath:[pathOne bezierPathByReversingPath]]; //两条线当成一条线
layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.fillColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //然后画上去
[imageView.layer addSublayer:layer];
}
- (void)animationCaseTwoMethod3 {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
[self.view addSubview:imageView];
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
maskView.backgroundColor = [UIColor colorWithWhite:1.f alpha:0.4f];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
UIBezierPath *pathOne = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
[path appendPath:[pathOne bezierPathByReversingPath]];
layer = [CAShapeLayer layer];
layer.path = path.CGPath;
maskView.layer.mask = layer; //layer的遮罩就是我们画出的两条线
[imageView addSubview:maskView];
}
三. CAKeyframeAnimation
- CAKeyframeAnimation也是CApropertyAnimation的子类。
- 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。
代码:
#import "ViewController.h"
@interface ViewController () <CAAnimationDelegate>
@property (weak, nonatomic) UIView *customView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor orangeColor];
self.customView = view;
[self.view addSubview:view];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.创建核心动画
CAKeyframeAnimation *keyAnima=[CAKeyframeAnimation animation];
//平移
keyAnima.keyPath=@"position";
//1.1告诉系统要执行什么动画
NSValue *value1=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(200, 100)];
NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(200, 200)];
NSValue *value4=[NSValue valueWithCGPoint:CGPointMake(100, 200)];
NSValue *value5=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
keyAnima.values=@[value1,value2,value3,value4,value5];
//1.2设置动画执行完毕后,不删除动画
keyAnima.removedOnCompletion=NO;
//1.3设置保存动画的最新状态
keyAnima.fillMode=kCAFillModeForwards;
//1.4设置动画执行的时间
keyAnima.duration=4.0;
//1.5设置动画的节奏
keyAnima.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//设置代理,开始—结束
keyAnima.delegate=self;
//2.添加核心动画
[self.customView.layer addAnimation:keyAnima forKey:nil];
}
-(void)animationDidStart:(CAAnimation *)anim
{
NSLog(@"开始动画");
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSLog(@"结束动画");
}
@end
动画效果:
位移.gif
除了使用values也可以使用path,让动画跟着路径移动。上面的动画也可以使用path,代码如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.创建动画对象
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.duration = 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(300, 50)];
[path addLineToPoint:CGPointMake(300, 400)];
anim.keyPath = @"position";
anim.path = path.CGPath;
//设置代理
anim.delegate=self;
self.customView.layer.anchorPoint = CGPointZero;
[self.customView.layer addAnimation:anim forKey:nil];
}
动画效果:
path.gif
也可以设置弧度实现抖动效果,如下:
#define angle2Radian(angle) ((angle) / 180.0 * M_PI)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.创建核心动画
CAKeyframeAnimation *keyAnima=[CAKeyframeAnimation animation];
keyAnima.keyPath=@"transform.rotation";
//设置动画时间
keyAnima.duration=0.1;
//设置图标抖动弧度
//把度数转换为弧度 度数/180*M_PI
keyAnima.values=@[@(-angle2Radian(4)),@(angle2Radian(4)),@(-angle2Radian(4))];
//设置动画的重复次数(设置为最大值)
keyAnima.repeatCount=MAXFLOAT;
keyAnima.fillMode=kCAFillModeForwards;
keyAnima.removedOnCompletion=NO;
//2.添加动画
[self.customView.layer addAnimation:keyAnima forKey:nil];
}
效果图:
抖动.gif
最后不要忘记移除动画:
[self.customView.layer removeAnimationForKey:@"wendingding"];
示例:
下面再讲一个如何使用关键帧动画和贝塞尔曲线实现如下动画
关键帧动画.gif代码:
#import "EOCKeyFrameAnimationViewController.h"
@interface EOCKeyFrameAnimationViewController ()<CAAnimationDelegate> {
CAShapeLayer *shapeLayer;
UIBezierPath *path;
}
@end
@implementation EOCKeyFrameAnimationViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(40.f, 175.f)];
[path addCurveToPoint:CGPointMake(300.f, 175.f) controlPoint1:CGPointMake(50.f, 40.f) controlPoint2:CGPointMake(200.f, 300.f)];
pathLayer.path = path.CGPath;
pathLayer.lineWidth = 2.f;
pathLayer.fillColor = [UIColor clearColor].CGColor;
[pathLayer setStrokeColor:[UIColor redColor].CGColor];
[self.view.layer addSublayer:pathLayer];
shapeLayer = [CAShapeLayer layer];
shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
shapeLayer.bounds = CGRectMake(0.f, 0.f, 50.f, 50.f);
shapeLayer.position = CGPointMake(40.f, 175.f);
[self.view.layer addSublayer:shapeLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
anim.duration = 4.f;
anim.repeatCount = 1;
anim.rotationMode = kCAAnimationRotateAuto;
// anim.autoreverses = YES;
anim.calculationMode = kCAAnimationCubicPaced; //如果用keyTimes和values,会卡顿着走,用这个平滑一些
anim.path = path.CGPath;
anim.keyTimes = @[@0.4, @0.7, @0.9];
anim.values = @[[NSValue valueWithCGPoint:CGPointMake(60, 200)], [NSValue valueWithCGPoint:CGPointMake(100, 200)], [NSValue valueWithCGPoint:CGPointMake(140, 200)]];
[shapeLayer addAnimation:anim forKey:nil];
}
@end
四. CAAnimationGroup 动画组
CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行。
属性解析:
animations://用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间。
例如,平移、旋转、缩放作为一组动画一起执行,代码如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 平移动画
CABasicAnimation *a1 = [CABasicAnimation animation];
a1.keyPath = @"transform.translation.y";
a1.toValue = @(200);
// 缩放动画
CABasicAnimation *a2 = [CABasicAnimation animation];
a2.keyPath = @"transform.scale";
a2.toValue = @(0.3);
// 旋转动画
CABasicAnimation *a3 = [CABasicAnimation animation];
a3.keyPath = @"transform.rotation";
a3.toValue = @(M_PI_2);
// 组动画
CAAnimationGroup *groupAnima = [CAAnimationGroup animation];
groupAnima.animations = @[a1, a2, a3];
//设置组动画的时间
groupAnima.duration = 2;
// groupAnima.fillMode = kCAFillModeForwards;
// groupAnima.removedOnCompletion = NO;
[self.customView.layer addAnimation:groupAnima forKey:nil];
}
效果如下:
CAAnimationGroup.gif
五. CATransition
CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果,iOS比Mac OS X的转场动画效果少一点。
UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
属性解析:
type://动画过渡类型
subtype://动画过渡方向
startProgress://动画起点(在整体动画的百分比)
endProgress://动画终点(在整体动画的百分比)
合法的转场动画类型有:
fade://默认,faker淡出,layer淡入
moveIn://layer移入覆盖faker
push://layer推入,faker推出
reveal://覆盖在layer上面的faker被移出
私有(被苹果ban了,不建议直接使用):
cube://立方体旋转,layer将会在呈现的面,faker在不可见的面
suckEffect://覆盖在layer上面的faker被抽离
oglFlip://将背面的layer翻转到前面,faker翻转到背面、、
rippleEffect://伴随着水面波动动画,faker淡出,layer淡入
pageCurl://翻到下一页,faker被翻走,呈现layer
pageUnCurl://翻回上一页,layer被翻回并覆盖faker
cameraIrisHollowOpen://下面这两个是特殊的。镜头开,同时呈现部分为透明,而不是layer
cameraIrisHollowClose://类似上面,镜头关
subtype:
//4个子类型,表示上左下右4个转场动画方向
fromTop
fromLeft
fromBottom
fromRight
示例:
利用转场动画做立体翻页效果,代码如下:
- (void)leftBtnClicked {
self.index--;
if (self.index<1) {
self.index=5;
}
self.iconView.image=[UIImage imageNamed: [NSString stringWithFormat:@"%d.png",self.index]];
[self addAnimWithLeft:YES];
}
- (void)rightBtnClicked {
self.index++;
if (self.index>5) {
self.index=1;
}
self.iconView.image=[UIImage imageNamed: [NSString stringWithFormat:@"%d.png",self.index]];
[self addAnimWithLeft:NO];
}
- (void)addAnimWithLeft:(BOOL)isleft {
//创建核心动画
CATransition *ca = [CATransition animation];
//告诉要执行什么动画
//设置过度效果
ca.type = @"cube";
//设置动画的过度方向(向左)
ca.subtype = isleft ? kCATransitionFromLeft : kCATransitionFromRight;
//设置动画的时间
ca.duration = 2.0;
//设置动画的起始位置 比如翻页功能就能设置从哪里开始翻页
// ca.startProgress = 0.3;
// //设置动画的结束位置
// ca.endProgress = 0.5;
//添加动画
[self.iconView.layer addAnimation:ca forKey:nil];
}
动画效果:
立体翻页.gif
其实利用UIView的动画也可以做转场动画,还简单一点呢,如下:
[UIView transitionWithView:self.iconView duration:0.5 options:UIViewAnimationOptionTransitionCurlUp animations:^{
//转场代码
self.index = self.index + 1;
if (self.index > 5) {
self.index = 1;
}
NSString *imageName = [NSString stringWithFormat:@"%d.png",self.index];
self.iconView.image = [UIImage imageNamed:imageName];
} completion:^(BOOL finished) {
}];
动画效果:
UIView转场动画.gif
六. 动画时间
下面使用一个小案例介绍下动画时间,主要掌握这三个参数:beginTime speed timeOffset。
效果图: 动画时间.gif代码:
#import "EOCCAMediaTimingViewController.h"
@interface EOCCAMediaTimingViewController ()<CAAnimationDelegate> {
CAShapeLayer *shapeLayer;
int i; //是否暂停
CFTimeInterval pausedTime; //暂停时间
}
@end
@implementation EOCCAMediaTimingViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
i = 0;
//1.跟时间相关的要掌握:beginTime speed timeOffset
shapeLayer = [CAShapeLayer layer];
shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
shapeLayer.speed = 0.f; //默认1
shapeLayer.bounds = CGRectMake(0.f, 0.f, 50.f, 50.f);
shapeLayer.position = CGPointMake(25.f, 100.f);
[self.view.layer addSublayer:shapeLayer];
CAShapeLayer *lineLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(25.f, 100.f)];
[path addLineToPoint:CGPointMake(300.f, 300.f)];
lineLayer.lineWidth = 2.f;
lineLayer.strokeColor = [UIColor redColor].CGColor;
lineLayer.path = path.CGPath;
[self.view.layer addSublayer:lineLayer];
//CAKeyframeAnimation使用UIBezierPath当做路径,优先使用路径而不是values
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
anim.duration = 10.f;
// anim.beginTime = CACurrentMediaTime() + 5.f; //让动画推迟5s执行,默认马上执行
anim.path = path.CGPath;
// anim.autoreverses = YES; //动画是否原路返回回到起点,默认是NO
// anim.timeOffset = 3.f;
[shapeLayer addAnimation:anim forKey:nil];
//新建UISlider, 改变timeOffset
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(100.f, 300.f, 200.f, 30.f)];
slider.minimumValue = 0.f;
slider.maximumValue = 10.f;
[slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:slider];
}
- (void)sliderAction:(UISlider *)slider {
shapeLayer.timeOffset = slider.value;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// t(本层的时间) = (tp(父层的时间) - begin) * speed + timeOffset
if (0 == i) { //暂停
pausedTime = [shapeLayer convertTime:CACurrentMediaTime() fromLayer:nil];
shapeLayer.speed = 0.f;
shapeLayer.timeOffset = pausedTime;
} else if (1 == i) { //启动
// t = (tp - begin) * speed + offset
shapeLayer.speed = 1.f;
shapeLayer.beginTime = CACurrentMediaTime();
}
I++;
i %= 2;
}
@end
七. 综合动画案例
效果图: 综合案例.gif代码:
#import "EOCAnimationCaseView.h"
@interface EOCAnimationCaseView () {
CAShapeLayer *topLineLayer;
CAShapeLayer *leftLineLayer;
CAShapeLayer *bottomLineLayer;
CAShapeLayer *rightLineLayer;
}
@end
@implementation EOCAnimationCaseView
#define firstColor [UIColor colorWithRed:90 / 255.0 green:200 / 255.0 blue:200 / 255.0 alpha:1.0]
#define secondColor [UIColor colorWithRed:250 / 255.0 green:85 / 255.0 blue:78 / 255.0 alpha:1.0]
#define thirdColor [UIColor colorWithRed:92 / 255.0 green:201 / 255.0 blue:105 / 255.0 alpha:1.0]
#define fourthColor [UIColor colorWithRed:253 / 255.0 green:175 / 255.0 blue:75 / 255.0 alpha:1.0]
- (instancetype)init
{
if (self = [super init]) {
self.frame = CGRectMake(100.0f, 200.0f, 80.0f, 80.0f);
self.backgroundColor = [UIColor lightGrayColor];
[self createLayer];
}
return self;
}
- (void)createLayer {
//新建四条不同颜色的线
CGPoint topPoint = CGPointMake(3*80.f/4, 5.f);
CGPoint leftPoint = CGPointMake(5.f, 80.f/4);
CGPoint bottomPoint = CGPointMake(80.f/4, 80.f-5.f);
CGPoint rightPoint = CGPointMake(80.f-5.f, 3*80.f/4);
CGFloat lineLength = 80.f - 2*5.f;
topLineLayer = [self createLineLayer:topPoint toPoint:CGPointMake(topPoint.x , topPoint.y + lineLength) withColor:firstColor];
leftLineLayer = [self createLineLayer:leftPoint toPoint:CGPointMake(leftPoint.x + lineLength , leftPoint.y) withColor:secondColor];
bottomLineLayer = [self createLineLayer:bottomPoint toPoint:CGPointMake(bottomPoint.x , bottomPoint.y - lineLength) withColor:thirdColor];
rightLineLayer = [self createLineLayer:rightPoint toPoint:CGPointMake(rightPoint.x - lineLength , rightPoint.y) withColor:fourthColor];
[self.layer addSublayer:topLineLayer];
[self.layer addSublayer:leftLineLayer];
[self.layer addSublayer:bottomLineLayer];
[self.layer addSublayer:rightLineLayer];
self.transform = CGAffineTransformMakeRotation(-M_PI/6);
}
- (CAShapeLayer *)createLineLayer:(CGPoint)fromPoint toPoint:(CGPoint)toPoint withColor:(UIColor *)color {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:fromPoint];
[path addLineToPoint:toPoint];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineCap = kCALineCapRound;
layer.strokeColor = color.CGColor;
layer.lineWidth = 10.f;
layer.opacity = 0.8f;
layer.path = path.CGPath;
return layer;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self createAnimations];
}
- (void)createAnimations {
//自身旋转
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
anim.toValue = @(M_PI*2-M_PI/6);
anim.duration = 3.5f;
anim.repeatDuration = 7.f;
[self.layer addAnimation:anim forKey:nil];
//让线条缩短
[self createLineLayerAnim:topLineLayer pointX:0.f pointY:15.f];
[self createLineLayerAnim:leftLineLayer pointX:15.f pointY:0.f];
[self createLineLayerAnim:bottomLineLayer pointX:0.f pointY:-15.f];
[self createLineLayerAnim:rightLineLayer pointX:-15.f pointY:0.f];
//此时停止了旋转,让线条变长
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
anim1.fillMode = kCAFillModeForwards;
anim1.removedOnCompletion = NO;
anim1.beginTime = CACurrentMediaTime() + 7.f;
anim1.fromValue = @0;
anim1.toValue = @1;
anim1.duration = 3.f;
[topLineLayer addAnimation:anim1 forKey:nil];
[leftLineLayer addAnimation:anim1 forKey:nil];
[bottomLineLayer addAnimation:anim1 forKey:nil];
[rightLineLayer addAnimation:anim1 forKey:nil];
//self变大
CAKeyframeAnimation *anim3 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
anim3.duration = 8.0f;
anim3.beginTime = CACurrentMediaTime()+7.0f;
NSValue *fromValue = @1;
NSValue *toValue = @1.2;
anim3.keyTimes = @[@0.1, @0.4, @0.5];
anim3.values = @[fromValue, toValue, fromValue];
[self.layer addAnimation:anim3 forKey:nil];
}
- (void)createLineLayerAnim:(CAShapeLayer *)layer pointX:(CGFloat)pointx pointY:(CGFloat)pointy {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
anim.fillMode = kCAFillModeForwards;
anim.removedOnCompletion = NO;
anim.fromValue = @1;
anim.toValue = @0;
anim.duration = 3.f;
[layer addAnimation:anim forKey:nil];
//让这个点前后有位移
CAKeyframeAnimation *anim1 = [CAKeyframeAnimation animation];
anim1.keyPath = @"transform"; //课堂上这里是transform.translation 所以没效果,因为我的fromValue值设置的就是transform的值
anim1.repeatDuration = 4.0f;
anim1.beginTime = CACurrentMediaTime()+3.0f;
anim1.duration = 1.0f;
NSValue *fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 0, 0.f)];
NSValue *toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(pointx, pointy, 0.f)];
anim1.values = @[fromValue, toValue, fromValue, toValue, fromValue];
[layer addAnimation:anim1 forKey:nil];
}
@end
网友评论