1.谁来实现动画框架
![](https://img.haomeiwen.com/i8350700/39e50367bd90b4a6.jpeg)
- Core Animation 是一个框架,不仅仅只实现了动画功能,实际上它还实现了其他一些功能
- QuartzCore又是什么? QuartzCore几乎等于Core Animation
在CALayer类查看其定义,发现其定义在QuartzCore框架中而不是Core Animation框架,实际上QuartzCore几乎等于Core Animation,可以查看这个框架的主头文件中知道。
#ifndef QUARTZCORE_H
#define QUARTZCORE_H
#include <QuartzCore/CoreAnimation.h>
#endif /* QUARTZCORE_H */
Core Animation 以下
- Core Animation 是更底层的两个框架OpenGL ES/OpenG,Core Graphic的封装
- OpenGL ES/OpenGL : 第三方开源库
- Core Graphic: 对苹果自己实现的绘图框架Quartz 2D的封装,Quartz 2D 是一组二维绘图和渲染API,而Core Graphic则是通过这组API实现绘图功能的。需要注意的是,它可以在iOS和Mac OS通用。
总结来说,OpenGL,Core Graphic实现了单张图的绘图(渲染)功能,而Core Animation实现了多张图的连续顺序渲染(动画)功能
Core Animation 以上
Core Animation中定义了CALayer类,实现了渲染功能,但是CALayer不能响应用户的操作,UIKit的UIView封装了CALayer类,由于继承了UIResponder,实现了用户操作的响应。
我们也可以UIView中使用类似的方法
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
来实现动画,它是CoreAnimation中动画的封装,如果使用UIView可以完成需求,没有必要使用CoreAnimation的类来实现动画。
2. 参与动画的核心类
核心动画需要两种类来实现动画,一个是CAAnimation类,一个是CALayer类。
- CAAnimation类视图层次
![](https://img.haomeiwen.com/i8350700/1804a5463e8bb9f1.png)
实际上,CAAnimation,CAPropertyAnimation是抽象类,因此实际使用中,我们一般使用CABasicAnimation,CAKeyframeAnimation,CAAnimationGroup,CATransition这四个动画类。
- CALayer类
![](https://img.haomeiwen.com/i8350700/8e1d7c0e0dd5e4cf.png)
图上这些类基本继承CALayer.常用的是CALayer,CATextLayer,CAShapeLayer这三个类。
CALayer是真正实现显示功能的类。除了配合CAAnimation类来实现动画外,还有其他一些功能,并非本文研究的重点,暂时不表。
3.CABasicAnimation CALayer实现简单动画。
- CABasicAnimation重要属性
无论是CABasicAnimation还是CAKeyframeAnimation,他们都继承CAPropertyAnimation类。参照上面的图,CAPropertyAnimation中有一个重要的属性keyPath,keyPath表示对CALayer的哪个属性执行动画。所有的keyPath如下:
transform.translation.x = 平行移动
transform.scale = 比例轉換
transform.scale.x = 闊的比例轉換
transform.scale.y = 高的比例轉換
transform.rotation.z = 平面圖的旋轉
opacity = 透明度
margin = 布局
zPosition = 翻转
backgroundColor = 背景颜色
cornerRadius = 圆角
borderWidth = 边框宽
bounds = 大小
contents = 内容
contentsRect = 内容大小
cornerRadius = 圆角
hidden = 显示隐藏
mask
masksToBounds
opacity
position
shadowColor
shadowOffset
shadowOpacity
shadowRadius
CABasicAnimation 还需要指定keyPath初始化状态和结束状态,自己有两个属性fromValue,toValue(也可以设置fromValue,byValue,这时候toValue = fromValue+byValue)。一个动画必然有执行时间。在CAMediaTiming协议中定义了该属性duration.有时候我们需要动画有一个翻转回放效果,比如说先放大后按照回放缩小,autoreverse属性实现了回放效果。动画执行的时候可以是重复多次的,使用repeatCount来指定重复次数。动画有速度,还可以匀速,先快后慢或者先慢后快,timeFunction属性实现了动画的节奏。
- CABasicAnimation执行动画的套路
- 创建一个CABasicAnimation对象同时指定其keyPath
- 设定CABasicAnimation其他属性。
- 使用layer的
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key
。
UIView *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor redColor];
button.frame = CGRectMake(40, 40, 200, 200);
[self.view addSubview:button];
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
basicAnimation.fromValue = @0;
basicAnimation.toValue = @1;
basicAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
basicAnimation.autoreverses = YES;
basicAnimation.repeatCount = 1;
basicAnimation.duration = 2;
basicAnimation.fillMode = kCAFillModeForwards;
[button.layer addAnimation:basicAnimation forKey:nil];
4.CAKeyFrameAnimation CALayer实现简单动画
CABasicAnimation 通过fromValue和toValue指定动画初始化状态和结束状态,框架自动根据这两个状态执行过渡的动画。CAKeyFrameAnimation可以指定多个状态,每个状态称之为关键帧。它有一个Values属性(数组)来保存多个状态。记录的是Keypath的不同时期的值。CABasicAnimation只有两个状态,这两个状态之间的速率使用一个timingFunction就可以了。而对于多个状态的动画,自然需要多个timingFunction来实现不同桢动画,因此定义了timingFunctions数组来完成两个桢之间的时间节奏。当我们再定义好duration后,似乎整个动画都完全被定义好了。但是我们也许还希望定义每个桢到达点的时间,这样就可以决定好各个动画段的时间了。
keyTimes
定义了每个桢到达的时间比例。取值是@0到@1,@0.5表示duration * 0.5.
UIView *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor redColor];
button.frame = CGRectMake(40, 40, 200, 200);
[self.view addSubview:button];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
keyframeAnimation.values = @[@1, @0.5, @1];
CAMediaTimingFunction *mediaTimingFunction1 = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
CAMediaTimingFunction *mediaTimingFunction3 = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
keyframeAnimation.timingFunctions = @[mediaTimingFunction1, mediaTimingFunction3];
keyframeAnimation.repeatCount = 1;
keyframeAnimation.duration = 3;
keyframeAnimation.keyTimes = @[@0 ,@0.2, @1.0];
keyframeAnimation.fillMode = kCAFillModeForwards;
keyframeAnimation.autoreverses = NO;
keyframeAnimation.repeatCount = 1;
[button.layer addAnimation:keyframeAnimation forKey:nil];
5. CAKeyFrameAnimation的Paths属性实现动画
上一个例子中,CAKeyFrameAnimation是一个更精细的CABasicAnimation动画,可以定义多个时间段。CABasicAnimation也可以理解为values只有两个值的CAKeyFrameAnimation的版本。但是实际上CAKeyFrameAnimation能做的事情更多。再次观察一下上面CAAnimation视图层次图。发现CAKeyframeAnimation比CABasicAnimation多一个path属性(CGPathRef类型)。path规定了动画执行路径。path称为路径,指定path后,动画就沿着path执行,path是连续的而不是指定values离散的方式。path是一条线路,因为不是所有的keyPath属性都可以使用path.
当我们使用path属性后,values属性将不起作用
path的构造是一个比较复杂的过程,我们可以直接用更底层的CoreGraphic层的API来构造它,也可以使用UIkit层的UIBezierPath 构造UIkit的path后再通过[bezierPath CGPath]转换而来。前置是面向过程的API,后者是面向对象的API.
5.1构建CoreGraphic 的CGPathRef
相关构造方法都在CoreGraphics/CGPath.h文件中定义
- 创建一个path引用
CGMutablePathRef path = CGPathCreateMutable();
- 设定动画的起始位置
CGPathMoveToPoint(path, nil, 200, 200);
- 添加直线或者曲线或者弧线
- 直线
CGPathAddLineToPoint(path, nil, 300, 300);
-
二阶贝塞尔曲线(抛物线)
贝塞尔曲线 需要三个点(两条直线)来确定。分别是起点 ,终点和控制点。
0_1282984428GQ1X.gif
实际上,代码编写过程中,起点和终点是确定的,控制点比抛物线最高的还高一点的位置就可以了
CGPathAddQuadCurveToPoint(path, nil, 250, 50, 300, 300);
P0 是上一个API结束后的点,P1是控制点,对应(250,50),P2是终点,即是(300,300)
-
三阶贝塞尔曲线(用的少)
0_1282984443K2nb (1).gif
CGPathAddCurveToPoint(path, nil, 180, 100, 270, 100, 300, 200);
- 圆弧(Arc)
圆弧和曲线只是定义方式不同,实际上从数学角度来说都是曲线。在iOS中Curve指的是用贝塞尔方式定义(几条线段)的曲线。Arc指的是用中心点和旋转角度定义的圆弧。
CGPathAddArc(path, nil, 100, 100, 100, 0, 350 * M_PI/ 180,NO);
- 第三个第四个参数是圆弧的起点(在圆的最右边)
- 第五个参数是圆的半径
- 第六个参数是起始弧度(不是角度,开发者对角度认知比较直观,因此还需要使用
- 第七个参数是结束弧度(不是角度,开发者对角度认知比较直观,一因此还需要角度转弧度的公式)
- 0角度 == 0 弧度
- x角度 == x * π/180 弧度 (π在oc中有一个宏M_PI)
4.释放资源
由于使用C的面向过程的API,使用结束后要自己回收资源
CGPathRelease(path);
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.autoreverses = NO;
keyframeAnimation.duration = 3;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
keyframeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
keyframeAnimation.repeatCount = 1;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 200, 100);
CGPathAddArc(path, nil, 100, 100, 100, 40, 350 * M_PI/ 180,NO);
keyframeAnimation.path = path;
CGPathRelease(path);
[button.layer addAnimation:keyframeAnimation forKey:nil];
注意: 面向过程的API都是有状态的,一个方法的终点是下个方法的起点。
5.2构建UIKit 的UIBezierPath 后使用CGPath
// Path construction
- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);
- (void)closePath;
- (void)removeAllPoints;
实际上,这些API和CGMutablePathRef相关的API类似,只不过结束的时候需要将UIBezierPath对象转化为CGMutablePathRef 即可
keyframeAnimation.path = [path CGPath];
Demo如下
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(195, 95, 10, 10)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.autoreverses = NO;
keyframeAnimation.duration = 1;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
keyframeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
keyframeAnimation.repeatCount = 1;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(200, 100)];
[path addArcWithCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:350 * M_PI/180 clockwise:YES];
keyframeAnimation.path = [path CGPath];
[button.layer addAnimation:keyframeAnimation forKey:nil];
6.动画组
一个CAAnimation只能对CALayer的一个属性起作用,如果需要同时对一个CALayer多个属性进行动画操作,那么就需要使用动画组了。
demo 放大某个视图同时逐渐隐藏它
CABasicAnimation *basicAnimation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
basicAnimation1.removedOnCompletion = NO;
basicAnimation1.fillMode = kCAFillModeForwards;
basicAnimation1.duration = 2;
basicAnimation1.repeatCount = 1;
basicAnimation1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
basicAnimation1.autoreverses = NO;
basicAnimation1.fromValue = @0;
basicAnimation1.toValue = @1;
CABasicAnimation *basicAnimation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
basicAnimation2.removedOnCompletion = NO;
basicAnimation2.fillMode = kCAFillModeForwards;
basicAnimation2.duration = 2;
basicAnimation2.repeatCount = 1;
basicAnimation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
basicAnimation2.autoreverses = NO;
basicAnimation2.fromValue = @1;
basicAnimation2.toValue = @0;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 2;
group.repeatCount = 1;
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.animations = @[basicAnimation1, basicAnimation2];
[button.layer addAnimation:group forKey:nil];
网友评论