CAAnimation wiki


CAAnimation是一个动画抽象类,但是不要直接使用CAAnimation类,而是使用它的子类,如上图所示展示的就是它的家族成员子类们,CAAnimation遵守CAMediaTimingCAAction协议,它是直接作用在CALayer上的,并非UIView上,动画执行过程不在主线程上进行,所以不会阻塞主线程。先来看看CAnimation都定义了哪些属性,方法。

+ animation初始化一个CAAnimation 对象

+ (instancetype)animation;---
**`CAAnimation`**是一个动画抽象类,但是不要直接使用`CAAnimation`类,而是使用它的子类,如上图所示展示的就是它的家族成员子类们,`CAAnimation`遵守`CAMediaTiming`和`CAAction`协议,它是直接作用在`CALayer`上的,并非`UIView`上,动画执行过程不在主线程上进行,所以不会阻塞主线程。先来看看`CAnimation`都定义了哪些属性,方法。


**`+ animation`**初始化一个CAAnimation 对象
  • (instancetype)animation;

`+defaultValueForKey:`  `- shouldArchiveValueForKey:`后者通过传入一个关键字对动画对象 进行序列化本地存储,并且返回是否成功。然后使用相同的关键字调用前者来获取这个持久化的对象。
  • (nullable id)defaultValueForKey:(NSString *)key;
  • (BOOL)shouldArchiveValueForKey:(NSString *)key;

 
**Timing Function (CAMediaTimingFunction)**
Timing Function会被用于变化起点到终点之间的插值计算,形象的说是Timing Function决定了动画运行的节奏(Pacing),比如均匀变化(相同时间变化量相同),先快后慢,先慢后快还是不停变化速度。
Timing Function对应的类是`CAMediaTimingFunction`。

@property(nullable, strong) CAMediaTimingFunction *timingFunction;

`CAMediaTimingFunction`提供了两种获得时间函数的方法,一种是使用系统提供的五种时间函数,一种是自定义的时间函数。先来看第一种:
  • (id)functionWithName:(NSString *)name;
设置不同的`name`就能调用不同的系统提供的时间函数,`name`的可选值有:

/** Timing function names. **/

//线性 :匀速(速度不变,加速度为0)
A_EXTERN NSString * const kCAMediaTimingFunctionLinear
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

//渐进:动画缓慢进入,然后加速到达目的地
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseIn
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

//渐出:动画全速进入,然后再减速到达目的地
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseOut
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

//渐进渐出:动画缓慢进入,中间加速,然后减速到达目的地,这个是默认的动画行为
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseInEaseOut
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

CA_EXTERN NSString * const kCAMediaTimingFunctionDefault
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_3_0);

我们来看下系统提供的这五种时间函数的具体效果:(Ease的意思是缓和)


* Ease in (`kCAMediaTimingFunctionEaseIn`
):

![](http://upload-images.jianshu.io/upload_images/276769-8e66fc4e8369fc4c.gif?imageMogr2/auto-orient/strip)

* Ease out (`kCAMediaTimingFunctionEaseOut`
):

![](http://upload-images.jianshu.io/upload_images/276769-97bc05022596c53a.gif?imageMogr2/auto-orient/strip)

* Ease in ease out (`kCAMediaTimingFunctionEaseInEaseOut`
):

![](http://upload-images.jianshu.io/upload_images/276769-25bc023663bf848e.gif?imageMogr2/auto-orient/strip)

* 默认 (kCAMediaTimingFunctionDefault
):

![](http://upload-images.jianshu.io/upload_images/276769-8de7f89e5091e4cd.gif?imageMogr2/auto-orient/strip)

**cubic-bezier() 时间函数**

你也可以使用`+ functionWithControlPoints::::`或者`- initWithControlPoints::::`自定义自己的时间函数(cubic-bezier() )。 

cubic-bezier() 定义了一条立方贝塞尔曲线(cubic Bézier curves)。这些曲线是连续的,一般用于动画的平滑变换,也被成为缓动函数(easing functions)。

系统提供的四种时间函数的曲线图(不包含默认的kCAMediaTimingFunctionDefault):

![](http://upload-images.jianshu.io/upload_images/276769-51a4a967a8e54563.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

纵轴x(t)的取值范围是0.0-1.0,横轴t的取值也是0.0-1.0,真实动画的持续时间,值的变化范围都是不定的,所以要按照比例进行换算,这个比较好理解就不细讲了。
纵轴x(t)代表的是值改变的量,横轴t代表时间。
值的改变量的意思就是当前时间的值与初始值的差值(是差值并不是当前时刻的值,因为原点是(0,0)),这个值的类型有很多下面会细讲。
**形象一点的话你可以把x(t)看作当前位置距离起点的距离,那么速度 v = dx/dt , 加速度 a = dv/dt(求导)。**所以通过这个曲线就可以表现出:动画在不同时刻的差值(与初始值相比较),值变化的速度,以及加速度。当v<0的时候就表示着x(t)的值是负增长,就好像汽车一直行驶的时候,开始倒车了。
  
这个图理解了,再来看看 Bézier curve (贝塞尔曲线)
贝塞尔曲线于1962年,由法国工程师Pierre Bézier所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。

`+ functionWithControlPoints::::`或者`- initWithControlPoints::::`构建的贝塞尔曲线是一条三次曲线,一条三次贝塞尔曲线如下图所示需要四个点来定义,P0、P1、P2 和 P3。P0和P3是起点和终点,这两个点被作为比例固定在坐标系上,横轴为时间比例,纵轴为完成状态。P0(0, 0)表示初始时间和初始状态,P3是(1,1),表示终止时间和终止状态。上述两个方法的四个参数就是P1 , P2的坐标。

![](http://upload-images.jianshu.io/upload_images/276769-8afe20d291d93a77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


三次贝塞尔曲线演示动画,*t*在[0,1]区间
![Bézier_3](http://upload-images.jianshu.io/upload_images/276769-75f690bd96dcf78e.gif?imageMogr2/auto-orient/strip)

对贝塞尔曲线不理解的可以多查查相关资料,讲了这么多,如果你想自己自定义一条贝塞尔曲线并看看实际效果可以利用http://cubic-bezier.com/ 这个网站自己动手实验。


![演示](http://upload-images.jianshu.io/upload_images/276769-4a77b31566eaa5f3.gif?imageMogr2/auto-orient/strip)

  • (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
  • (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
 

`removedOnCompletion`下面会利用`CAAnimation`的派生类`CABasicAnimation`进行讲解 。

@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;


**`CAAnimationDelegate`**

//当动画开始的时候被调用

  • (void)animationDidStart:(CAAnimation *)anim;

//当动画结束的时候被调用

  • (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

##CAPropertyAnimation

`CAPropertyAnimation`是`CAAnimation`的子类,`CAAnimation`定义的属性,声明的方法以及遵循的协议,`CAPropertyAnimation`也都全部适用。
`CAPropertyAnimation`有两个子类`CABasicAnimation`和`CAKeyframeAnimation`,先来看下`CAPropertyAnimation`。

创建一个新的animation 对象,并且设置它的属性`
keyPath`。

  • (instancetype)animationWithKeyPath:(nullable NSString *)path;

@property(nullable, copy) NSString *keyPath;

`keyPath`文档是这么描述的** The key-path describing the property to be animated.**这个keyPath其实对应的是`layer`的属性,设置了KeyPath就代表着,这个keyPath对应的`layer`属性将会做动画。
 关键来了,`layer`到底是有多少个属性是可以用来做动画的?你上网查是查不完全的,但是你可以查询`CALayer `的**API**文档:

/** Geometry and layer hierarchy properties. **/

/* The bounds of the layer. Defaults to CGRectZero. Animatable. */

@property CGRect bounds;

描述中只要包含`Animatable`的属性就都可以用来做动画。

继续进行之前,先看一个layer易混淆的地方:
`layer`是通过树形结构组织起来的,共有三种树形结构
> * model  layer tree (模型层树)
 > * presentation  tree (表示层树)
 > * render tree (渲染层树)

当改变layer的值的时候,model layer tree的值会马上改变,通过render tree渲染,presentation tree以动画的形式展现layer的某个属性值的渐变过程。
`model layer tree`中的`layer`是我们通常意义说的`layer`,当我们修改layer的属性的时候,就会立刻修改model layer tree。

view1.layer.anchorPoint = CGPointMake(0.6, 0.6);


 
refer tree(渲染层树)是动画的真实执行者,但是它是私有的,开发者不能调用。

@property(getter=isAdditive) BOOL additive;

默认是NO,当设置为YES的时候,使 Core Animation 在更新 presentation layer 之前将动画的值添加到 model layer 中去。

详细了解可以参考这篇文章:http://ronnqvi.st/multiple-animations/ 。

@property(getter=isCumulative) BOOL cumulative;


`cumulative`一般和`repeatCount`(动画重复的次数)结合使用,当`cumulative`被设为YES时,下次动画的值会在上一次动画值的基础上进行而不是重新回到初始状态,可以看下面的两个例子,`cumulative`分别被设置为YES或者NO。

![cumulative = YES](http://upload-images.jianshu.io/upload_images/276769-c7f9f9f814cafa91.gif?imageMogr2/auto-orient/strip)


![cumulative = NO](http://upload-images.jianshu.io/upload_images/276769-2dba8092054ff1b0.gif?imageMogr2/auto-orient/strip)


CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.fromValue = @25;
animation.toValue = @100;
//动画重复的次数
animation.repeatCount = 3;

//动画的持续时间
animation.duration = 1;
animation.cumulative = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
[_icon.layer addAnimation:animation forKey:@"animation"];


##CABasicAnimation

`CABasicAnimation`是`CAPropertyAnimation `的子类一般也是使用`animationWithKeyPath:`对`CABasicAnimation`进行实例化,并指定layer(带有Animatable表示)的属性作为关键路径进行注册。

###CAMediaTiming
`CAMediaTiming`是`CAAnimation`,`CALayer`所遵循的协议,上面没提是因为感觉结合具体动画的创建会更加直观。`CAMediaTiming`协议定义了一段动画内用来控制逝去时间的属性的结合,可以精准的控制时间。

@property CFTimeInterval beginTime;

指定动画的开始时间,如果动画需要延迟几秒开始应当这样设置:`animation.beginTime = CACurrentMediaTime() +?`。

@property CFTimeInterval duration;

动画`执行一次`的时长,无限重复可设置为`HUGE_VALF`。

@property float speed;

`speed`动画执行一次的时间系数,默认是1.0,如果设置了`speed`的数值,那么动画执行一次的时间就等于`duration /speed`,设置为0动画将不会执行。

@property CFTimeInterval timeOffset;

`timeOffset`和`speed`不一样,`timeOffset`不是一个时间系数,`timeOffset`是对时间进行偏移,计算出动画在那个时刻的状态然后开始执行。
** timeOffset = Duration > timeOffset ?  timeOffset : (Duration % timeOffset)**

有一点需要注意,不论timeOffset的值为多少,动画从哪个状态开始执行,动画都会完整执行一次(比如timeOffset = 3.0 , Duration = 6.0;那么动画将会从时间等于3秒的状态开始执行,然后回到初始状态执行 0 ~ 3的动画)。


![timeOffset = 3.0 , Duration = 6.0](http://upload-images.jianshu.io/upload_images/276769-1520d91e16a584d9.gif?imageMogr2/auto-orient/strip)

//你可以自己测试一下,设置一下repeatCount看看效果,也可以设置一下speed的值
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.duration = 6;
animation.fromValue = @25;
animation.toValue = @225;
animation.timeOffset = 3;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeBackwards;
[_icon.layer addAnimation:self.animation2 forKey:@"animation2"];

@property float repeatCount;

动画执行(重复)的次数,默认是0。

@property CFTimeInterval repeatDuration;

可以理解为动画执行的总时长,`duration`是动画执行一次的时长,两者不一样,默认值为0,不要和`repeatCount`共同使用。

@property BOOL autoreverses;

用来设置动画是否有回放的效果,默认为NO。

@property(copy) NSString *fillMode;

fillModel:填充模式。有四个可选值,只有在`removeOnCompletion`设置为NO的时候才会生效。
`removeOnCompletion`:动画结束时是否自动移除,如果设置为YES,那么你要手动给layer赋值,否则layer会自动回到动画开始之前的状态。
layer动画运行的过程:其实我们给一个视图添加 layer动画时,真正移动并不是我们视图本身,而是presentation layer 的一个缓存,动画开始的时候presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除原始layer显示,这就解释了为什么我们的视图在结束后又回到了原的状态,因为它根本没动过。

KCAFillModelRemoved 这个是默认值,也就是说动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态 , 动画将在设置的beginTime开始执行(如果没有设置beginTime,则动画立即执行)。
![KCAFillModelRemoved](http://upload-images.jianshu.io/upload_images/276769-73a6454a3db7f103.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

KACFillModelForwards 当动画结束后,layer会一直保持着动画最后的状态。

![KACFillModelForwards](http://upload-images.jianshu.io/upload_images/276769-2de1257bf05da36c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 KCAFillModeBackwards 会立即执行动画的第一帧,不论是否设置了beginTime属性。


![KCAFillModeBackwards](http://upload-images.jianshu.io/upload_images/276769-5cc188ba945d85d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 KCAFillModeBoth  动画加入开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态。

![KCAFillModeBoth](http://upload-images.jianshu.io/upload_images/276769-599f559edd7f06c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

以上是`CAMediaTiming `的内容,继续看`CABasicAnimation `三个关于layer值的属性。


@property(nullable, strong) id fromValue;


@property(nullable, strong) id toValue;


@property(nullable, strong) id byValue;


`fromValue`: KeyPath对应layer属性 的初始值。
    `toValue`: keyPath对应layer属性 的结束值。
伴随动画的进行,在`duration`的持续时间内,keyPath对应的layer属性值从fromValue渐渐地变为toValue。
`byValue`表示动画期间改变的值,以layer的`position.x`举例,当`fromeValue = @100;` `byValue = @100;`就表示着layer的`position.x`初始值为100 ,动画期间`position.x`改变的值是100,`position.x`从初始值100变为200。
`toValue`和`byValue`不要同时使用,作用冲突。


`CABasicAnimation`实践

##CAKeyframeAnimation 
关键帧(keyframe)使我们能够定义动画中任意的一个点,然后让Core Animation填充所谓的中间帧。
`CAKeyframeAnimation`与`CABasicAnimation`虽然都是`CAPropertyAnimation`的子类但是两者还是有区别的:
`CABasicAnimation`:只能让`keyPath`对应的`layer`的某个可动画(Animatable)的属性,从初始值(fromValue)转变到另外一个数值(toValue)。
`CAKeyframeAnimation`:可以使用一个数组` NSArray *values`,保存这些数值(数值的个数不定),并使用数组`NSArray<NSNumber *> *keyTimes`来保存这些数值对应的时间点。

@property(nullable, copy) NSArray *values;

存放放关键帧(keyframe)的数组,动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧。

@property(nullable, copy) NSArray<NSNumber *> *keyTimes;

存放对应的关键帧指定的对应的时间点,存放的类型必须为`NSNumber`类型,取值范围在0到0.1(会根据duration进行一个时间的换算),keyTimes里面每一个时间点对应数组values的每一帧,如果没有设置keyTimes或者`KeyTimes = @[]`,那么各个关键帧的时间会被系统平分。
**需要注意的是`keyTimes`数组里的时间点,必须要后面的值大于前面的值,而且最好不要使用分数设置像这样`@(1/7)`这样`3/7`或者这样`@(4/9)`因为你可能会得到一个除不尽的小数,动画效果会出现问题,不会按照你的预期执行,这个已经测试**。

![keyframe](http://upload-images.jianshu.io/upload_images/276769-24f49d490299f282.gif?imageMogr2/auto-orient/strip)

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];

keyframeAnimation.values = @[@(WIDTH/2 - 35) , @(WIDTH/2+35) , @(WIDTH/2 - 35) , @(WIDTH/2+35) , @(WIDTH/2 - 35),@(WIDTH/2+35), @(WIDTH/2 - 35),@(WIDTH/2)];

keyframeAnimation.keyTimes = @[@0, @(0.1), @(0.2), @(0.3),@(0.4),@(0.5) ,@(0.7),@1 ];

keyframeAnimation.duration = 3;

[icon.layer addAnimation:self.keyframeAnimation forKey:@"keyfra
meAnimation"];


@property(nullable) CGPathRef path;

`CAKeyframeAnimation`路径动画的路径,但是一般不直接使用它,使用的是`UIBezierPath`,`UIBezierPath`是`CGPathRef`数据类型的封装。

###UIBezierPath
`UIBezierPath`的作用就是创建直线,曲线,矩形圆弧或其它复杂的曲线 形状。
`UIBezierPath`的使用场景有三种:
* 1 . 与`CAKeyAnimation`结合使用,作为动画的路径。
* 2 . 在方法`- (void)drawRect:(CGRect)rect`中画图使用。
* 3 . 与`CAShapeLayer`配合使用,贝塞尔曲线给`CAShapeLayer`提供路径,`CAShapeLayer`在提供的路径中进行渲染。

这里先看看它与`CAKeyAnimation`的结合使用。

UIBezierPath 相关类方法
  • (instancetype)bezierPathWithRect:(CGRect)rect;
绘制一个矩形的贝塞尔曲线


![bezierPathWithRect](http://upload-images.jianshu.io/upload_images/276769-17ac311958269662.gif?imageMogr2/auto-orient/strip)
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100 , (WIDTH - 100) / 2, 200)];
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyFrameAnimation.duration = 2.0;
keyFrameAnimation.repeatCount = 3;
keyFrameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
keyFrameAnimation.fillMode = kCAFillModeForwards;
[_icon.layer addAnimation:keyFrameAnimation forKey:@"keyFrameAnimation"];

  • (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
绘制一个矩形的内切圆的贝塞尔曲线

![内切圆](http://upload-images.jianshu.io/upload_images/276769-72979c42987b8c37.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



![内切圆.gif](http://upload-images.jianshu.io/upload_images/276769-9b19fa453b3e56af.gif?imageMogr2/auto-orient/strip)

UIBezierPath *bezierPath2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, (WIDTH - 100)/2, 200)];

keyFrameAnimation.path = bezierPath2.CGPath;

[_icon.layer addAnimation:keyFrameAnimation forKey:@"keyFrameAnimation"];


  • (instancetype)bezierPathWithRoundedRect:(CGRect)rect
    cornerRadius:(CGFloat)cornerRadius;
绘制一个可以设置圆角的矩形贝塞尔曲线

![bezierPathWithRoundedRect:
cornerRadius:
](http://upload-images.jianshu.io/upload_images/276769-fb39196f7f521ea0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![bezierPathWithRoundedRect:
cornerRadius:
](http://upload-images.jianshu.io/upload_images/276769-ef62bc07666a9087.gif?imageMogr2/auto-orient/strip)
CGRect frame = CGRectMake(WIDTH/2 - 100, 150, 200, 200);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:65];
keyFrameAnimation.path = bezierPath.CGPath;
[_icon.layer addAnimation:keyFrameAnimation forKey:@"keyFrameAnimation"];

  • (instancetype)bezierPathWithRoundedRect:(CGRect)rect
    byRoundingCorners:(UIRectCorner)corners
    cornerRadii:(CGSize)cornerRadii;
画一个有**部分圆角**矩形的贝塞尔曲线
`rect`:矩形的frame
`corners`:一个枚举值,用来指定矩形哪些角被设置为圆角,五个可选值,左上角、右上角、左下角、右下角、全部。

typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
UIRectCornerTopLeft = 1 << 0,
UIRectCornerTopRight = 1 << 1,
UIRectCornerBottomLeft = 1 << 2,
UIRectCornerBottomRight = 1 << 3,
UIRectCornerAllCorners = ~0UL
};

cornerRadii:(CGSize)cornerRadii

***cornerRadii 是 CGSize 类型 , 你看其它的资料大部分都写的是 这是用来设置椭圆半径的,首先这不能叫半径,要叫也是叫长轴(2a),短轴(2b),其次你设置height(高)是根本没有效果的,只有width是有效果的,这个圆角其实就是半径 = width 圆的一段圆弧 , 如下图所示,你也可以自己做个测试,也可以自己做个路径动画***

![](http://upload-images.jianshu.io/upload_images/276769-c2b5d8fa8e7daa58.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
** 一句话总结就是height 是多少无所谓 , 类似与
layer.cornerRadius    **

  • (instancetype)bezierPathWithArcCenter:(CGPoint)center
    radius:(CGFloat)radius
    startAngle:(CGFloat)startAngle
    endAngle:(CGFloat)endAngle
    clockwise:(BOOL)clockwise;
自己定义一些参数画一段圆弧。

center:圆弧的圆心坐标

radius:圆弧的半径

startAngle:圆弧起始的弧度

endAngle:圆弧结束的弧度

clockwise:圆弧从起始弧度指向结束弧度的方向,YES是顺时针方向,NO是逆时针方向

**需要注意的startAngel endAngel并不是让你填入类似45度 , 30度这种数值,startAngel endAngel是弧度,圆一周的弧度数为2πr/r=2π,360°角=2π弧度,所以如果你想使用使用30度 , 90度这种数值请进行换算,代码里面M_PI等同于π。**
![](http://upload-images.jianshu.io/upload_images/276769-c70d9b73779a2da4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上图是iOS devlop文档的配图,看下面我做的图更好理解一点。

![](http://upload-images.jianshu.io/upload_images/276769-0992faecc25bbbf5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

下面你就可以自己画一个圆弧看一下效果。
  • (instancetype)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    self.backgroundColor = ColorWithHexValue(0x5EB35F);
    }
    return self;
    }

  • (void)drawRect:(CGRect)rect
    {
    //设置线的填充色
    [ColorWithHexValue(0xE9816D) setStroke];

    //新建一个bezier对象
    UIBezierPath bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:30 startAngle:0 endAngle:1.5M_PI clockwise:YES];

    //设置线宽度
    bezierPath.lineWidth = 1;
    //开始绘制
    [bezierPath stroke];
    }

图像如下所示:


![](http://upload-images.jianshu.io/upload_images/276769-bf0a5e12ab3ab325.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

构建路径Path的相关方法:

  • (void)moveToPoint:(CGPoint)point

改变当前点的位置到point点,你可以看作是设置一条Path的起点。

  • (void)addLineToPoint:(CGPoint)point
从当前点增加一条直线到该point,然后改变当前点把point作为当前点 。

-(void)addArcWithCenter:(CGPoint)center
radius:(CGFloat)radius
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
clockwise:(BOOL)clockwise

这个就跟上面说过的画一段圆弧的方法是一样的。

  • (void)addCurveToPoint:(CGPoint) controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2
构建一个三次的贝塞尔曲线。

  • (void)addCurveToPoint:(CGPoint) controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2
构建一段二次贝塞尔曲线。

贝塞尔曲线,上面时间函数的时候就已经说得比较清楚了,还想更深入的了解的话,维基百科写的很清晰,讲的也很清楚,有兴趣的可以科学的查一下。

UIBezierPath 就先到这里结束了,通常情况的具体三种用法就是一开始介绍它的时候写的,自己试试吧。
继续回到`@interface CAKeyframeAnimation : CAPropertyAnimation`

`calculationMdel`计算模式,主要针对的动画的没一怔的内容都是一个坐标点的情况,也就是针对anchortPoint和position进行的动画,当在平面坐标系中有多个离散的点的时候可以是离散的,也可以是直线相连后进行插值计算,还可以使用圆滑的曲线将他们相连后进行差着计算,calculationMdel提供如下几种模式:

**插值**:在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点.插值是离散函数逼近的重要方法,利用它通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值,插值:用来填充图像变换时像素之间的空隙。

CA_EXTERN NSString * const kCAAnimationLinear
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

CA_EXTERN NSString * const kCAAnimationDiscrete
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

CA_EXTERN NSString * const kCAAnimationPaced
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

CA_EXTERN NSString * const kCAAnimationCubic
__OSX_AVAILABLE_STARTING (__MAC_10_7, __IPHONE_4_0);

CA_EXTERN NSString * const kCAAnimationCubicPaced
__OSX_AVAILABLE_STARTING (__MAC_10_7, __IPHONE_4_0);


**kCAAnimationLinear**: calculationModel的默认值,表示当关键帧是坐标点的时候,关键这之间直接直线连接进行插值计算;

**kCAAnimationDiscrete**: 离散的,就是不进行插值计算,所有关键帧直接逐个进行显示 ;

**kCAAnimationPaced**:使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timeFunctions无效;

**kCAAnimationCubic**对关键帧为坐标点的关键帧进行圆滑曲线相连后插值计算,曲线的形状可以通过`tensionValues`,`continuityValues`,`biasValues`来进行调整自定义,使得运动的轨迹变得圆滑;

**kCAAnimationCubicPaced**:这个是基于**kCAAnimationCubic**的,它的作用是使动画运动变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的。

![calculationMdel.png](http://upload-images.jianshu.io/upload_images/276769-b3139a444759bf21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


@property(nullable, copy) NSArray<NSNumber *> *tensionValues;
@property(nullable, copy) NSArray<NSNumber *> *continuityValues;
@property(nullable, copy) NSArray<NSNumber *> *biasValues;


这三个属性是对每一帧的细节的设置,分别是张力值,持续性值,偏移值。

@property(nullable, copy) NSString *rotationMode;

**rotationModel** 有两个可选值:

CA_EXTERN NSString * const kCAAnimationRotateAuto
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAAnimationRotateAutoReverse
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

这两个都可以让做动画的时候沿着路径的切线方向,但是也有稍微的不同,看下面的演示就清楚了。


![kCAAnimationRotateAuto.gif](http://upload-images.jianshu.io/upload_images/276769-173f59186228680a.gif?imageMogr2/auto-orient/strip)


![kCAAnimationRotateAutoReverse.gif](http://upload-images.jianshu.io/upload_images/276769-ab2af1575b4fe1f3.gif?imageMogr2/auto-orient/strip)
##@interface CASpringAnimation : CABasicAnimation

**CASpringAnimation**是**CABasicAnimation**的子类,也就是说**CABasicAnimation**定义的属性它也都有,看看**CASpringAnimation**自己独有的属性有哪些。

***需要注意的是CASpringAnimation是iOS9才引入的动画类,按目前APP的开发,一般很少有 只支持iOS9以上的,一般都会兼容iOS7,个人建议使用POP。***

@property CGFloat mass;

质量,影响图层运动时的弹簧惯性,质量越大,动画运动的幅度越大,弹簧拉伸压缩的幅度越大,数值必须大于0,默认是1。


@property CGFloat stiffness;


弹簧的劲度系数(弹性系数),必须大于0,默认为100。它描述的是单位形变量时所产生弹力的大小,k值大,说明形变单位长度需要的力大,或者说弹簧"韧",劲度系数在数值上等于弹簧伸长(或缩短)单位长度的弹力。

@property CGFloat damping;

阻尼系数,必须大于或者等于0,默认为10。
**阻尼**是指任何振动系统在振动中,由于外界作用、摩擦力等)和/或系统本身固有的原因引起的振动幅度逐渐下降的特性,以及此一特性的量化表征。

在实际振动中,由于摩擦力总是存在的,所以振动系统最初所获得的能量,在振动过程中因阻力不断对系统做负功,使得系统的能量不断减少,振动的强度逐渐减弱,振幅也就越来越小,以至于最后的停止振动,像这样的因系统的力学能,由于摩擦及转化成内能逐渐减少,振幅随时间而减弱振动,称为阻尼振动。





![一个有阻尼的弹簧振子振动示意图](http://upload-images.jianshu.io/upload_images/276769-5d8867f525d87496.gif?imageMogr2/auto-orient/strip)



@property CGFloat initialVelocity;


连接到弹簧的对象得得初始速度,默认值是0,负值的时候,速度方向与运动方向相反。

@property(readonly) CFTimeInterval settlingDuration;

动画的结算时间,从spring动画开始到停止的一个估算时间,根据当前设置的动画参数进行估算。


CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position.x"];
springAnimation.fromValue = @100;
springAnimation.toValue = @150;
springAnimation.mass = 100.0;
springAnimation.stiffness = 100.0;
springAnimation.damping = 100.0;
springAnimation.initialVelocity = 10;
springAnimation.repeatCount = 3;
springAnimation.fillMode = kCAFillModeRemoved;

[_icon.layer addAnimation:springAnimation forKey:@"springAnimation"];



![springAnimation](http://upload-images.jianshu.io/upload_images/276769-fc52fc9ccd146414.gif?imageMogr2/auto-orient/strip)

参考列表:
[https://objccn.io/issue-12-1/](https://objccn.io/issue-12-1/) 
https://developer.mozilla.org/zh-CN/docs/Web/CSS/timing-function
http://geeklu.com/2012/09/animation-in-ios/

这次先到这里吧,能写的都尽量写到,有的东西一想就是一两天,真的不想敷衍没意思,每天可能只有一点点时间写这个。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容