前记
关于实现一个iOS动画,如果简单的,我们可以直接调用UIView
的代码块来实现,虽然使用UIView
封装的方法很方便,但是这只能用于一些简答的动画,如果是一些复杂的动画呢?这就不得不去研究下核心动画Core Animation
(包含在Quartz Core
框架中)了。这这之前我们必须了解,CALayer
就包含在Quartz Core
框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中。在使用Core Animation
开发动画的本质就是将CALayer
中的内容转化为位图从而供硬件操作,所以要熟练掌握动画操作必须熟悉CALayer
,关于CALayer
就不在这里讲了。今天主要是分析核心动画,iOS 中的核心动画又分为下面几种:基础动画、关键帧动画、动画组、转场动画、弹簧动画。下面我们先来了解下各个动画之间的关系
动画简介
CAAnimation
@interface CAAnimation : NSObject
{
@private
void *_attr;
uint32_t _flags;
}
这是核心动画的基类,不能直接使用,主要负责动画的时间、速度等,从上面可以看出是准守CAMediaTiming
协议的。
CAPropertyAnimation
属性动画的基础类,继承自CAAnimation
,不能直接使用。何谓属性动画呢?即通过修改属性值就可以产生动画效果。
CAAnimationGroup
动画组,继承自CAAnimation
,顾名思义就是一种组合动画,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CATransition
转场动画,继承自CAAnimation
,主要是通过滤镜来进行动画的效果设置
CABasicAnimation
基础动画,继承自CAPropertyAnimation
,通过属性控制动画的参数,只要初始状态和结束状态
CAKeyframeAnimation
关键帧动画,继承自CAPropertyAnimation
,也是通过属性控制动画参数,但是与基础动画不同的是有多个控制状态,并且可以通过path
来实现动画
CASpringAnimation
弹簧动画,是在iOS 9中引入的,继承自CABasicAnimation
,用于制作弹簧动画
动画使用
在使用动画之前,先补充个知识点---UIBezierPath, 这在动画使用的过程中会经常用到
核心动画
要使用核心动画,我们必须先了解下其属性,这里我们先看其遵守的协议
属性 | 说明 |
---|---|
beginTime | 指定动画开始的时间。开始延迟几秒的话,设置为CACurrentMediaTime() + 秒数 的方式即可 |
duration | 动画的时长 |
speed | 动画的速度 |
timeOffset | 详细说明 |
repeatCount | 动画重复的次数,如果要一直持续设置为HUGE_VALF 即可 |
repeatDuration | 设置动画的时间。在该时间内动画一直执行,不计次数 |
autoreverses | 动画结束时是否执行逆动画 |
fillMode | 分四种情况,分别为kCAFillModeForwards 、kCAFillModeBackwards 、kCAFillModeBoth 、kCAFillModeRemoved ,决定当前对象在非active时间段的行为,比如动画开始之前或者动画结束之 |
CAAnimation
属性
属性 | 说明 |
---|---|
timingFunction | 速度控制函数,控制动画运行的节奏 |
removedOnCompletion | 默认为YES ,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO ,不过还要设置fillMode 为kCAFillModeForwards |
关于fillMode
的四种情况:
kCAFillModeRemoved
默认值,动画结束后,layer会恢复到之前的状态kCAFillModeForwards
当动画结束后,layer会一直保持着动画最后的状态,而removedOnCompletion
的默认属性值是YES
,所以为了使动画结束之后layer保持结束状态,应将removedOnCompletion
设置为NO
。kCAFillModeBackwards
在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。kCAFillModeBoth
这个其实就是上面两个的合成,动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
关于速度CAMediaTimingFunction
控制的四种情况
kCAMediaTimingFunctionLinear
(线性):匀速,给你一个相对静态的感觉kCAMediaTimingFunctionEaseIn
(渐进):动画缓慢进入,然后加速离开kCAMediaTimingFunctionEaseOut
(渐出):动画全速进入,然后减速的到达目的地kCAMediaTimingFunctionEaseInEaseOut
(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。
属性动画
从上图中我们知道,属性动画是继承自核心动画,在其API中我们可以看到有如下函数和属性
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
@property(nullable, copy) NSString *keyPath;
其中都有keyPath
,这又是什么呢?这就是属性动画与动画组和转场动画不同之处。通过指定CALayer
的一个属性名称为keyPath
(NSString类型),并且对CALayer
的这个属性的值进行修改,达到相应的动画效果。比如,指定@"opacity"
为keyPath
,就修改CALayer
的opacity
属性的值,以达到透明度变化的动画效果
下面我们列举一些常用的animationWithKeyPath
值
常用值 | 说明 | 使用方式 |
---|---|---|
transform | 3D变换 | [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)] ,直接进行3D变换 |
transform.scale | 缩放 | @(1.5) ,放大1.5倍 |
transform.scale.x | 宽度缩放 | @(1.5) ,宽放大1.5倍 |
transform.scale.y | 高度缩放 | @(1.5) ,高放大1.5倍 |
transform.rotation.x | 围绕x轴旋转 | @(M_PI) ,x轴旋转180度 |
transform.rotation.y | 围绕y轴旋转 | @(M_PI) ,y轴旋转180度 |
transform.rotation.z | 围绕z轴旋转 | @(M_PI) ,z轴旋转180度 |
position | 位置(中心点的改变) | [NSValue valueWithCGPoint:CGPointMake(100, 100)] ,中心点变为(100,100) |
opacity | 透明度 | @(0.5) ,透明度变为0.5 |
bounds | 大小的改变 中心点保持不变 | [NSValue valueWithCGRect:CGRectMake(0, 0, 300, 300)] ,大小变为(300,300) |
cornerRadius | 圆角的设置 | @(5) ,圆角设置为5 |
backgroundColor | 背景颜色变换 | (id)[UIColor redColor].CGColor ,背景改为红色 |
contents | 可以改变layer展示的图片 | (id)[UIImage imageNamed:@"12.png"].CGImage ,将UIView的展示图片改为12.png |
strokeStart | 从起始点开始变化 | fromValue = 0 ,toValue = 1 ,为CAShapeLayer 的属性 |
strokeEnd | 从结束的位置开始变化 | fromValue = 1 ,toValue = 0.5 ,为CAShapeLayer 的属性 |
path | 根据路径来改变 | fromValue = (__bridge id)(start.CGPath); ,toValue = (__bridge id)((end.CGPath)) |
基础动画
基础动画是继承自属性动画,所以在使用的时候,我们最主要的就是通过属性来控制动画,比如设置初始值、结束值,当然还有核心动画的其他属性。
- (void)showAnimation
{
CAShapeLayer * circle = [CAShapeLayer layer];
circle.frame = self.view.bounds;
//
UIBezierPath * circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)) radius:self.normalSize.width/2 -1 startAngle:0 endAngle:2*M_PI clockwise:YES];
circle.path = circlePath.CGPath;
circle.strokeColor = [UIColor blueColor].CGColor;
circle.fillColor = nil;
[self.view.layer addSublayer:circle];
//通过圆的strokeStart 改变来进行改变
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.2);
strokeStartAnimation.toValue = @(0);
//通过圆的strokeEnd 改变来进行改变
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @0.5;
strokeEndAnimation.toValue = @(1.0);
//通过圆的transform.rotation.z 改变来进行改变
CABasicAnimation * rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.fromValue = @(0);
rotationAnimation.toValue = @(-M_PI * 2);
//组合动画
CAAnimationGroup * group = [CAAnimationGroup animation];
group.duration = 5;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
group.animations = @[strokeStartAnimation,strokeEndAnimation,rotationAnimation];
[circle addAnimation:group forKey:nil];
}
在上面的基础动画代码中,我只用了最基础的两个属性,fromValue
和toValue
,而其它属性呢?由于后面用到了动画组,所以讲其它属性在动画组进行了设置。[circle addAnimation:group forKey:nil];
这句代码中,key
我设置的为nil
,如果不设置为nil
的时候,是什么意思呢?这个其实就是我们的动画设置了一个key
,可以用来区别是哪一个动画,在后面我会举例说明。
上面代码对应的效果如下:
关键帧动画
关键帧动画也是属性动画,与基础动画最主要不同的是在两个参数上,NSArray *values
和CGPathRef path
,通过这个我们可以知晓,关键帧动画可以设置多个控制状态
如下:
//用value的方式进行展示动画
- (void)showKeyFrameAnimationWithValues
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue *key1 = [NSValue valueWithCGPoint:_beginPoint];
NSValue *key2 = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *key3 = [NSValue valueWithCGPoint:CGPointMake(150, 50)];
NSValue *key4 = [NSValue valueWithCGPoint:CGPointMake(300, 250)];
animation.values = @[key1,key2,key3,key4];
animation.duration = 5.0;
animation.delegate = (id)self;
// animation.autoreverses = true;//是否按路径返回
// animation.repeatCount = HUGE;//是否重复执行
animation.removedOnCompletion = NO;//执行后移除动画
animation.fillMode = kCAFillModeForwards;
//存储位置
[animation setValue:key4 forKey:@"keyframeAnimationLocation"];
[_fishImageView.layer addAnimation:animation forKey:@"keyframeAnimation_fish"];
}
而且还可以设置路径,这也是其最大的特点
如下:
//用CGPathRef的方式进行展示动画
- (void)showKeyFrameAnimationWithPath
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:_fishImageView.layer.position];
//三次贝塞尔曲线
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(150, 400) controlPoint2:CGPointMake(230, -100)];
animation.path = path.CGPath;
animation.duration = 5.0;
animation.delegate = (id)self;
[_fishImageView.layer addAnimation:animation forKey:@"keyframeAnimation_path_fish"];
}
效果如下,这里就暂时只给出values
的效果
在上面的两个方法中,对layer
设置了两个不同的key
,分别为keyframeAnimation_fish
和keyframeAnimation_path_fish
前面我提到过,通过这个可以判断是哪一种动画,这里我们在动画结束的地方进行区分一下
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if ([anim isEqual:[_fishImageView.layer animationForKey:@"keyframeAnimation_fish"]])
{
// [CATransaction begin];
// //禁用隐式动画
// [CATransaction setDisableActions:YES];
_fishImageView.layer.position = [[anim valueForKey:@"keyframeAnimationLocation"] CGPointValue];
// //提交事务
// [CATransaction commit];
}
else if ([anim isEqual:[_fishImageView.layer animationForKey:@"keyframeAnimation_path_fish"]])
{
}
}
动画组
动画组其实很简单,就是将许多动画组合在一起
在上面的基础动画中,我也用到了动画组,下面再贴上一组动画组合效果
//发射
- (void)launchAnimation
{
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
animation1.fromValue = @(1.0);
animation1.toValue = @(1.5);
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
animation2.fromValue = @(1.0);
animation2.toValue = @(1.5);
CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"position"];
animation3.fromValue = [NSValue valueWithCGPoint:_ballLayer.position];
animation3.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.frame.size.width - (30 * 1.3)/2.0 , _ballLayer.position.y - 200)];
CAAnimationGroup *anima = [CAAnimationGroup animation];
anima.animations = @[animation1, animation2,animation3];
anima.duration = 1.0;
anima.delegate = (id)self;
anima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anima.fillMode = kCAFillModeForwards;
anima.removedOnCompletion = NO;
[_ballLayer addAnimation:anima forKey:@"group_launch"];
}
在后面的demo里有完整的代码,这里只是截取了部分代码
效果如下:
转场动画
在了解转场动画之前,我们先了解其两个参数,通过这些参数,我们就能很清楚的了解其效果
-
type
转场类型
转场动画类型 | 说明 | 常量 | 是否支持方向设置 |
---|---|---|---|
公开API | |||
fade | 淡出 | kCATransitionFade |
是 |
movein | 新视图移动到旧视图上面 | kCATransitionMoveIn |
是 |
push | 新视图退出旧视图 | kCATransitionPush |
是 |
reveal | 移开旧视图显示新的 | kCATransitionReveal |
是 |
私有API | 苹果未公开的类型,但是目前还是可以用的 | 私有API只能通过下面的字符串进行访问 | |
cube | 立体翻转 | 是 | |
oglFlip | 翻转 | 是 | |
suckEffect | 收缩 | 否 | |
rippleEffect | 水滴波纹效果 | 否 | |
pageCurl | 向上翻页效果 | 是 | |
pageUnCurl | 向下翻页效果 | 是 | |
cameraIrisHollowOpen | 摄像头打开效果 | 否 | |
cameraIrisHollowClose | 摄像头关闭效果 | 否 |
-
subtype
动画子类型
属性 | 说明 |
---|---|
kCATransitionFromRight | 从右 |
kCATransitionFromLeft | 从左 |
kCATransitionFromTop | 从顶部 |
kCATransitionFromBottom | 从底部 |
在了解上面两个属性后,对应转场动画,就差不多了
部分代码如下
- (void)transition:(BOOL)next
{
CATransition *transition = [CATransition animation];
transition.type = @"cube";
if (next)
{
transition.subtype = kCATransitionFromLeft;
}
else
{
transition.subtype = kCATransitionFromRight;
}
transition.duration = 1.0;
_imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld.jpg",(long)_currentIndex]];
[_imageView.layer addAnimation:transition forKey:@"transitionAnimation"];
}
效果如下
弹簧动画
弹簧动画是在iOS 9后才出现的,在这之前,我们可以通过下面的方法来实现
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
} completion:nil];
iOS 9后苹果公开了这一API,我们先对其中的属性进行分析,因为代码中有注释,所以就直接贴一部分代码
//质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大
positionAnimation.mass = 0.1;
//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快
positionAnimation.damping = 2;
//刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快
positionAnimation.stiffness = 50;
//初始速率,动画视图的初始速度大小
//速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反
positionAnimation.initialVelocity = -10;
关于弹簧动画,我也写了一个例子,效果如下
写在最后
关于核心动画,差不多就简单的介绍这么点,如有什么不对的还望各位多多指教,不甚感激。
最后,附上Demo