Core Animation译为核心动画,它是一组非常强大的动画处理API。Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。要注意的是,Core Animation是直接作用在CALayer上的,并非UIView。
CALayer与UIView
- 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
- 在 View显示的时候,UIView 做为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display
- CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 做动画的时候,View 作为 Layer 的代理,Layer 通过 actionForLayer:forKey:向 View请求相应的 action(动画行为)
- layer 内部维护着三分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树), Render Tree (渲染树),在做 iOS动画的时候,我们修改动画的属性,在动画的其实是 Layer 的 presentLayer的属性值,而最终展示在界面上的其实是提供 View的modelLayer
- 两者最明显的区别是 View可以接受并处理事件,而 Layer 不可以
Core Animation的结构
核心动画中所有类都遵守CAMediaTiming协议。
CAAnaimation是个抽象类,不具备动画效果,必须用它的子类才有动画效果。
CAAnimationGroup和CATransition才有动画效果,CAAnimationGroup是个动画组,可以同时进行缩放,旋转(同时进行多个动画)。
CATransition是转场动画,界面之间跳转(切换)都可以用转场动画。
CAPropertyAnimation也是个抽象类,本身不具备动画效果,只有子类才有。
CABasicAnimation和CAKeyframeAnimation:
CABasicAnimation基本动画,做一些简单效果。
CAKeyframeAnimation帧动画,做一些连续的流畅的动画。
CAAnimation协议-CAMediaTiming的属性
//持续时间,默认值是0.25秒
@property CFTimeInterval duration;
//重复次数,无线循环可以设置HUGE_VALF或者CGFLOAT_MAX
@property float repeatCount;
//重复时间
@property CFTimeInterval repeatDuration;
/*
可以用来设置动画延时执行,若想延迟2s,就设置为CACurrentMediaTIme() + 2
CACurrentMediaTIme():图层的当前时间
*/
@property CFTimeInterval beginTime;
//动画执行速度
@property float speed;
//动画的时间时间偏移量
@property CFTimeInterval timeOffset;
//是否自动返转动画,默认NO.
@property BOOL autoreverses;
//决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
@property(copy) NSString *fillMode;
/*
kCAFillModeForwards //当动画结束后,layer会一直保持着动画最后的状态.
kCAFillModeBackwards //layer进入动画的初始状态并等待动画开始
kCAFillModeBoth //动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状
kCAFillModeRemoved //动画结束后,layer会恢复到之前的状态.
*/
CAAnimation的属性
/*
速度控制函数,控制动画运行的节奏
kCAMediaTimingFunctionLinear 匀速
kCAMediaTimingFunctionEaseIn 慢进快出
kCAMediaTimingFunctionEaseOut 快进慢出
kCAMediaTimingFunctionEaseInEaseOut 慢进慢出 中间加速
kCAMediaTimingFunctionDefault 默认
*/
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
/*
默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。
如果想让图层保持显示动画执行后的状态,那就设置为NO,并且设置fillMode为kCAFillModeForwards
*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
//动画的代理回调
@property(nullable, strong) id delegate;
delegate方法
//动画开始回调
- (void)animationDidStart:(CAAnimation *)anim;
//动画结束回调
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
CABaseAnimation 基础动画
主要提供了对于CALayer对象中的可变属性进行简单动画的操作
CABasicAnimation的属性:
//开始值
@property(nullable, strong) id fromValue;
//结束值
@property(nullable, strong) id toValue;
//过渡值
@property(nullable, strong) id byValue;
三个属性之间的规则
-
fromValue
和toValue
不为空,动画的效果会从fromValue
的值变化到toValue
. -
fromValue
和byValue
都不为空,动画的效果将会从fromValue
变化到fromValue+byValue
-
toValue
和byValue
都不为空,动画的效果将会从toValue-byValue
变化到toValue
- 只有
fromValue
的值不为空,动画的效果将会从fromValue
的值变化到当前的状态. - 只有
toValue
的值不为空,动画的效果将会从当前状态的值变化到toValue
的值. - 只有
byValue
的值不为空,动画的效果将会从当前的值变化到(当前状态的值+byValue
)的值.
平移动画
- (void)startSimpleAnimation
{
//初始化平移动画
CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
//设置属性
baseAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
baseAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
baseAnimation.duration = 5.0;
baseAnimation.repeatCount = 2;
//动画执行完,保持在最后的状态
baseAnimation.removedOnCompletion = NO;
baseAnimation.fillMode = kCAFillModeForwards;
baseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//layer添加动画
[self.simpleView.layer addAnimation:baseAnimation forKey:@"positionAniamtion"];
}
注:
- 如果removedOnComletion=NO并且fillMode=kCAFillModeForwards,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。比如,CALayer的position初始值为(0,0),CABasicAnimation的fromValue为(100,100),toValue为(200,300),虽然动画执行完毕后图层保持在(200,300)这个位置,实质上图层的position还是为(0,0)
- 如果不设置removedOnComletion=NO,那么默认为YES,动画执行后,图层会回到原来的位置。为什么动画结束后返回原状态?首先我们需要搞明白一点的是,layer动画运行的过程是怎样的?其实在我们给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。
- 这个同样也可以解释为什么在动画移动过程中,我们为何不能对其进行任何操作。所以在我们完成layer动画之后,最好将我们的layer属性设置为我们最终状态的属性,然后将presentation layer 移除掉。
透明度变化动画
-(void)opacityAniamtion{
CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"opacity"];
anima.fromValue = [NSNumber numberWithFloat:1.0f];
anima.toValue = [NSNumber numberWithFloat:0.2f];
anima.duration = 1.0f;
[self.simpleView.layer addAnimation:anima forKey:@"opacityAniamtion"];
}
背景色变化动画
-(void)backgroundAnimation{
CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
anima.toValue =(id) [UIColor greenColor].CGColor;
anima.duration = 1.0f;
[self.simpleView.layer addAnimation:anima forKey:@"backgroundAnimation"];
}
KeyPath的值
CASpringAnimation 弹性动画(iOS9+)
继承与CABasicAnimation
属性
//质量,振幅和质量成反比
@property CGFloat mass;
//刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快
@property CGFloat stiffness;
//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快,可以认为它是阻力系数
@property CGFloat damping;
//初始速率,动画视图的初始速度大小速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反.
@property CGFloat initialVelocity;
//结算时间,只读.返回弹簧动画到停止时的估算时间,根据当前的动画参数估算通常弹簧动画的时间使用结算时间比较准确
@property(readonly) CFTimeInterval settlingDuration;
应用
- (void)springAnimation{
CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"position.y"];
spring.damping = 5;
spring.stiffness = 100;
spring.mass = 1;
spring.initialVelocity = 0;
spring.duration = spring.settlingDuration;
spring.fromValue = @(self.simpleView.center.y);
spring.toValue = @(self.simpleView.center.y + 200);
spring.fillMode = kCAFillModeForwards;
[self.simpleView.layer addAnimation:spring forKey:nil];
}
CAKeyframeAnimation 关键帧动画
Keyframe顾名思义就是关键点的frame,你可以通过设定CALayer的始点、中间关键点、终点的frame,时间,动画会沿你设定的轨迹进行移动 。
属性解析
/*
NSArray对象。里面的元素称为”关键帧”(keyframe)。
动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 .
*/
@property(nullable, copy) NSArray *values;
/*
可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。
path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
*/
@property(nullable) CGPathRef path;
/*
可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.
当keyTimes没有设置的时候,各个关键帧的时间是平分的 .
*/
@property(nullable, copy) NSArray *keyTimes;
CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
anchorPoint和position的区别
- position用来设置CALayer在父层中的位置以父层的左上角为原点(0, 0)
- anchorPoint称为“定位点”、“锚点”,决定着CALayer身上的哪个点会在position属性所指的位置。以自己的左上角为原点(0, 0),它的x、y取值范围都是0~1,默认值为中心点(0.5, 0.5)
- 假如锚点anchorPoint为默认值即中点(0.5,0.5),而该层的position设置为(0,0)即为父层的左上点。anchorPoint默认值情况可以理解position为center。
- (void)positionAniamtion
{
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//这个点是初始点,从这个点开始动画
NSValue *value_1 = [NSValue valueWithCGPoint:CGPointMake(100, 500)];
NSValue *value_2 = [NSValue valueWithCGPoint:CGPointMake(100, 200)];
NSValue *value_3 = [NSValue valueWithCGPoint:CGPointMake(100, 400)];
//keyTimes中的值是从0.0递增到1.0
NSNumber *time_1 = [NSNumber numberWithFloat:0.1];
NSNumber *time_2 = [NSNumber numberWithFloat:0.6];
NSNumber *time_3 = [NSNumber numberWithFloat:1.0];
keyAnimation.values = [NSArray arrayWithObjects:value_1,value_2,value_3, nil];
//keyTimes是对应values的
keyAnimation.keyTimes = [NSArray arrayWithObjects:time_1,time_2,time_3, nil];
/*
设置path,values会失效
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 200)];
keyAnimation.path = path.CGPath;
*/
keyAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
/*
kCAAnimationLinear //连续运算模式,线性。表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
kCAAnimationDiscrete //离散运算模式,只显示关键帧。就是不进行插值计算,所有关键帧直接逐个进行显示;
kCAAnimationPaced //均匀执行运算模式,线性。使动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效;
kCAAnimationCubic //平滑运算模式。对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑;
kCAAnimationCubicPaced//平滑均匀运算模式。在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.
*/
keyAnimation.calculationMode = kCAAnimationPaced;
keyAnimation.duration = 5.0;
[self.simpleView.layer addAnimation:keyAnimation forKey:@"positionAniamtion"];
}
CAAnimationGroup 动画组
可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行.
animations:用来保存一组动画对象的NSArray.
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间.
CATransition 转场动画
用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果.
属性
//动画过渡类型
@property(copy) NSString *type;
//动画过渡方向
@property(nullable, copy) NSString *subtype;
//动画起点(在整体动画的百分比)
@property float startProgress;
//动画终点(在整体动画的百分比)
@property float endProgress;
应用
/*type过渡效果
fade //交叉淡化过渡(不支持过渡方向) kCATransitionFade
push //新视图把旧视图推出去 kCATransitionPush
moveIn //新视图移到旧视图上面 kCATransitionMoveIn
reveal //将旧视图移开,显示下面的新视图 kCATransitionReveal
cube //立方体翻滚效果
oglFlip //上下左右翻转效果
suckEffect //收缩效果,如一块布被抽走(不支持过渡方向)
rippleEffect //滴水效果(不支持过渡方向)
pageCurl //向上翻页效果
pageUnCurl //向下翻页效果
cameraIrisHollowOpen //相机镜头打开效果(不支持过渡方向)
cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向)
*/
/*subtype过渡方向
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromBottom
kCATransitionFromTop
*/
// CATransition的使用
CATransition *transition = [CATransition animation];
transition.type = @"pageUnCurl";
transition.subtype = kCATransitionFromBottom;
transition.duration = 2.0;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.simpleView.layer addAnimation:transition forKey:@"transition"];
暂停动画/恢复动画
//暂停主要是设置timeOffset
- (void)pauseAnimation
{
// t = (tp - begin) * speed + timeOffset
//将图层当前时间(CACurrentMediaTime())转换成parent time
CFTimeInterval pauseTime = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
/*
speed = parent time / local time (默认为1.0)
speed = 0.0时意味暂停;
speed = 2.0时是local time的进度是parent time的2倍,
即同一动画效果parent time需要2秒,而local time只需要1秒
*/
self.simpleView.layer.speed = 0.0;
/*
timeOffset是local time的时间偏移量
与speed = 0结合,表示layer在timeOffset时间偏移量的位置暂停
*/
self.simpleView.layer.timeOffset = pauseTime;
}
/* beginTime的理解
1. 默认beginTime=0.0,并且是相对parent time的
2. 所以在某种意义上 0.0 是等于 [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
可以通过代理方法animationDidStart,animationDidStop,获取parent time来确认
3. beginTime是用来设置动画执行时间的,设置方式如下:
beginTime = CACurrentMediaTIme()+2,即延迟2秒执行动画,且动画执行时间还是duration
beginTime = CACurrentMediaTIme()-2,即从动画的第2秒开始执行,并且动画执行时间为duration-2,意味舍弃了前2秒的动画
4. 当speed = 1.0(默认值)&& timeOffset = 0.0(默认值)&& beginTime = 0.0(默认值)时
CACurrentMediaTime() = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
因为[self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil]做的事就是t = (tp-begin) * speed+timeOffset
*/
//恢复开始主要是将timeOffset转化为beginTime
- (void)startAnimation
{
CFTimeInterval pausedTime = self.simpleView.layer.timeOffset;
// 1. 让CALayer的时间继续行走
self.simpleView.layer.speed = 1.0;
// 2. 取消上次记录的停留时刻
self.simpleView.layer.timeOffset = 0.0;
// 3. 取消上次设置的时间
self.simpleView.layer.beginTime = 0.0;
// 4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
self.simpleView.layer.beginTime = timeSincePause;
}
参考资料:
iOS动画-从不会到熟练应用
iOS中的动画
iOS Core Animation详解
详解 CALayer 和 UIView 的区别和联系