1、动画协议介绍
有一种通过CAAnimation实现的协议叫做CAMediaTiming,也就是CABasicAnimation和CAKeyframeAnimation的基类(指CAAnimation)。像duration,beginTime和repeatCount这些时间相关的属性都在这个类中。大体而言,协议中定义了8个属性,这些属性通过一些方式结合在一起,准确的控制着时间。
2、duration
为了显示相关属性的不同时间,无论是他们自己还是混合状态,我都会动态的将橙色变为蓝色。下面的块状显示了从开始到结束的动画过程,时间线上每一个标志代表一秒钟。你可以看到时间线上的任意一点,当前颜色即表示动画中的当前时间。比如,duration像下面一样可视。
duration设置为1.5秒,所以动画全程花费了1.5秒变为蓝色。
一旦动画完成后,CAAnimation默认从layer上移除。这从上面的图形中也表现出来
3、beginTime
一旦动画达到最终值,它会从layer上移除。如果layer的颜色是橙色(开始的颜色),那么颜色又会便会橙色。在这个可视化界面中,layer的颜色是白色,所以你也可以看到动画加入layer后的两秒钟又会变白,因为那是动画已经结束了。
我们也形象的描述一下动画的beginTime,这样让人更容易理解。
durations设置为1.5秒了,开始时间设为当前时间。(CACurrentMediaTime())加一秒,所以动画在2.5s后结束。动画加入到layer上之后,花费一秒钟时间来启动并呈现出来。结果是1+1.5=2.5
为了让动画从fromValue开始显示,你可以将动画设置为fill backwards。我们可以通过设置fillMode为kCAFillModeBackwards。
4、autoreverse
autoreverses属性可以产生从初始值到最终值,并反过来回到初始值的动画。这意味着动画发生了两次。
Autoreverses使得动画结束后又回到起始状态
5、repeatCount
和repeatCount比起来,repeatCount可以将动画重复两次(如下所示)或者任意次(你甚至可以使用像1.5这样的分数来完成一个半动画)。一旦动画达到它的最终值,他就会立马跳回到初始值并重新开始
Repeat count可以让动画运行超过一次
6、repeat duration
和repeat count类似,但很少用到的就是repeat duration了。它将会根据给定的一个duration简单的重复动画(如下2秒所示)。经过一个repeat duration时,如果它小于动画的duration,那么动画就会提前结束(repeat duration之后结束)
Repeat duration会让动画根据一个给定duration重复
这些都可以组合起来将一个反转动画重复多次或在一个给定的duration间重复。
7、speed
一个跟时间相关有趣的属性是speed。通过设置duration为3秒,但是speed为2,动画快速的执行了1.5秒,因为它的速度是之前的两倍。
速度为2时,动画执行速度是之前的两倍,所以3秒的动画只需要执行1.5秒
如果只是配置了一个简单的动画,那么你也可以分开使用beginTime和duration以达到相同的效果。但是使用speed属性的优点在于这两个事实:
1.动画的speed是分等级的。(hierarchical)
2.CAAnimation不是唯一一个实现CAMediaTiming的类。
Hierarchical speed
速度为2的动画组有一部分动画的速度为1.5,那么这个动画就是3倍于正常速度。
CAMediaTiming的其他实现
CAMediaTiming是CAAnimation实现的一个协议,但是CALayer(所有Core Animationlayers的基类)也实现了相同的协议,这就意味着你可以设置layer的speed为2.0,这样,所有加入到layer的动画运行都要快两倍。同样的,如果一个速度为3的动画加到一个速度为0.5的layer上,这个动画最终将会以1.5倍的常速运行。
为了控制动画或layer的速度,通常还可以设置speed为0,从而暂停动画。和timeOffset结合在一起时,可以通过像slider类似的控件控制动画,我们在下文中也会讲到。
刚开始timeOffset属性是非常奇怪的。正如名字所示那样,它对时间进行偏移(offset),从而计算出动画的状态。如下图所示。duration为3秒,offset为1秒的动画。
你可以offset整个动画,但是动画所有部分任然会执行
动画开始运行时跳过第一秒进入从橙色到蓝色的过度,直接运行剩下的两秒,直到完全变蓝。然后动画直接跳回完全橙色的时候,并完成第一秒的颜色转换。这看起来有点像我们把动画的第一秒切下来,然后放到最后。
这个属性很少用,但是它可以和一个暂停的动画(speed=0)结合在一起控制’current time’。一个暂停的动画停留在第一帧。如果你观察offset动画每次的第一个颜色,你可以看到它的颜色值一秒就进入颜色转换。将time offset设置为其他值,你可以让那段时间进入转换。
控制动画时间
同时使用speed和timeOffset可以控制动画的当前时间。这几乎不会涉及到什么代码,但是概念却比较难以理解(我希望插图能有所帮助)。为了方便,我将动画的duration设为1.0。因为time offset是绝对值。这样做就意味着当time offset为0.0时,此时就是动画的0%处(动画开始),time offset为1.0时,就是动画的100%处(动画结束)。
8、代码展示遮罩效果动画
// ViewController.m
#import "ViewController.h"
// 声明全局变量 设置图片的宽度和高度
static CGFloat const kImageViewWidth = 104;
static CGFloat const kImageViewHeight = 157;
@interface ViewController ()
{
BOOL isAnimating;
CAShapeLayer *maskLayer;;
}
@property (weak, nonatomic) IBOutlet UIImageView *topImageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, kImageViewWidth, kImageViewHeight);
maskLayer = [CAShapeLayer layer];
// 设置遮罩图层从贝塞尔曲线获取形状
maskLayer.path = [UIBezierPath bezierPathWithRect:rect].CGPath;
// 设置最上层图片的遮罩效果图层
_topImageView.layer.mask = maskLayer;
}
- (IBAction)startAnimation:(id)sender {
isAnimating = YES;
CGRect rect = CGRectMake(0, 0, kImageViewWidth, kImageViewHeight);
UIBezierPath *fromPath = [UIBezierPath bezierPathWithRect:rect];
UIBezierPath *toPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, kImageViewWidth, 0)];
// 遮罩图层从贝塞尔曲线获取形状
maskLayer.path = fromPath.CGPath;
// 遮罩覆盖的速度
maskLayer.speed = 1;
// 反复点击时产生动画效果
[maskLayer removeAllAnimations];
// 创建基础动画对象并设置动画属性
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
basicAnimation.fromValue = (id)(fromPath.CGPath);
basicAnimation.toValue = (id)(toPath.CGPath);
basicAnimation.duration = 5;
[maskLayer addAnimation:basicAnimation forKey:@"pathAnimation"];
maskLayer.path = toPath.CGPath;
}
- (IBAction)resumeAnimation:(id)sender {
if(isAnimating)
{
return;
}
isAnimating = YES;
CFTimeInterval pausedTime = maskLayer.timeOffset;
maskLayer.speed = 1.0;
maskLayer.timeOffset = 0;
maskLayer.beginTime = 0;
CFTimeInterval timeSincePause = [maskLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 设置动画的开始时间为上一次的结束时间
maskLayer.beginTime = timeSincePause;
}
- (IBAction)pauseAnimation:(id)sender {
isAnimating = NO;
CFTimeInterval pausedTime = [maskLayer convertTime:CACurrentMediaTime() fromLayer:nil];
maskLayer.speed = 0;
maskLayer.timeOffset = pausedTime;
}
@end
9、动画效果
本文文字部分来自http://www.cocoachina.com/programmer/20131218/7569.html
10、路径动画
// ViewController.m
// 路径动画
// Created by JackChen on 2016/11/4.
// Copyright © 2016年 JackChen. All rights reserved.
#import "ViewController.h"
#import "CABasicAnimationDemo.h"
@interface ViewController ()
{
UIBezierPath *smallOvalPath;
UIBezierPath *ovalPath;
CAShapeLayer *maskLayer;
UIView *recordButton;
BOOL isReverse;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect2 = CGRectMake(0, 0, 200, 200);
smallOvalPath = [UIBezierPath bezierPathWithRoundedRect:rect2 cornerRadius:0];
ovalPath = [UIBezierPath bezierPathWithRoundedRect:rect2 cornerRadius:100];
maskLayer = [CAShapeLayer layer];
maskLayer.path = smallOvalPath.CGPath;
recordButton = [[UIView alloc]init];
recordButton.backgroundColor = [UIColor redColor];
recordButton.frame = CGRectMake(100, 100, 200, 200);
recordButton.layer.mask = maskLayer;
[self.view addSubview:recordButton];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[recordButton.layer removeAllAnimations];
[maskLayer addAnimation:[CABasicAnimationDemo shapePathAnimationWithFromPath:smallOvalPath toPath:ovalPath reverse:!isReverse] forKey:@"pathAnimation"];
isReverse = !isReverse;
}
@end
#import "CABasicAnimationDemo.h"
static CGFloat const kAnimationDuration = 1;
@implementation CABasicAnimationDemo
+ (CABasicAnimation *)shapePathAnimationWithFromPath:(UIBezierPath *)fromPath toPath:(UIBezierPath *)toPath reverse:(BOOL)isReverse
{
if (isReverse) {
return [CABasicAnimationDemo animationWithKeyPath:@"path" duration:kAnimationDuration fromValue:(id)fromPath.CGPath toValue:(id)toPath.CGPath];
}else{
return [CABasicAnimationDemo animationWithKeyPath:@"path" duration:kAnimationDuration fromValue:(id)toPath.CGPath toValue:(id)fromPath.CGPath];
}
}
+ (CABasicAnimation *)animationWithKeyPath:(NSString *)keyPath
duration:(CFTimeInterval)duration
fromValue:(id)from
toValue:(id)to{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.duration = duration;
animation.fromValue = from;
animation.toValue = to;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
return animation;
}
@end