越上层,封装程度越高,动画实现越简洁越简单,但是自由度越低;反之亦然。本文着重介绍Core Animation层的基本动画实现方案。
在iOS中,展示动画可以类比于显示生活中的“拍电影”。拍电影有三大要素:演员+剧本+开拍,概念类比如下:
演员--->CALayer,规定电影的主角是谁
剧本--->CAAnimation,规定电影该怎么演,怎么走,怎么变换
开拍--->AddAnimation,开始执行
CALayer代表一个层,它提供了一个+layer类方法来创建CALayer层。
提示:所有的UIView都有一个默认的CALayer,通过UIView的layer属性即可访问UIView上的CALayer层。
使用CALayer的步骤非常简单,具体如下:
与CALayer显示相关的还有如下几个常用属性:
具体详情请查看CALayer层
使用Core Animation创建动画不仅简单,而且具有更好地性能,原因有如下两个:
除此之外,Core Animation动画还涉及如下API:
实际上,控制UIView内子控件的过渡还有另一种方式,通过UIView的+ (void)beginAnimations:(NSString )animationID context:(void )context;与 + (void)commitAnimations;方法控制,——如果子组件的过渡动画不是特别复杂,只需要实现一些简单的动画,即可通过这种方式控制。步骤如下:
上面的+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;方法用于控制UIView的过渡动画的动画方式,它支持如下动画方式:
另:+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;方法用于控制动画的变化曲线,也就是控制动画的变化速度,该方法支持如下几种变化速度:
例如:
- (void)curl:(UIButton *)sender{
//开始执行动画
[UIView beginAnimations:@"animation" context:nil];
[UIView setAnimationDuration:1.0f];
//控制UIView内过渡动画的类型
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];
//设置动画的变化曲线
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
//交换视图控制器所显示的UIView中两个子控件的位置
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[UIView commitAnimations];
}
CAAnimation提供了如下属性和方法:
(1)、 @property CFTimeInterval beginTime;
②、如果一个animation是直接添加再layer上,beginTime同样是其parent object——layer开始的一个偏移,但是一个layer的beginning是一个过去的时间,因此不能简单的设置beginTime为5去延迟动画5s之后开始,因为可能layer的beginning加上5s之后也是过去的时间,因此,当要延迟一个添加layer上的动画的时候,需要定义一个addTime,先获取addTime如下:
CFTimeInterval addTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
然后延迟delay秒:
animation.beginTime = addTime + delay;
(2)、 @property CFTimeInterval duration;设置时间;
注意,我们设置的duration可能和动画进行的真是duration不一样,这个依赖于superLayer的time space或者就是speed;
(3)、 @property float speed;动画速度;
注意:如果一个动画A:duration为1秒,speed为1;而另一个动画B:duration为2秒,speed为2。则这两个动画的效果是相同的;前提它们的super layer相同;
(4)、 @property CFTimeInterval timeOffset;
假设一个3s的动画,它的状态为t0,t1,t2,t3,当没有timeOffset的时候,正常的状态序列应该为:t0->t1->t2->t3;当设置timeOffset为1的时候状态序列就变为:t1->t2->t3->t0;同理当timeOffset为2的时候状态序列就变味t2->t3->t0->t1;
(5)、 @property float repeatCount;动画重复次数;
(6)、 @property CFTimeInterval repeatDuration;动画重复时间;
(7)、 @property BOOL autoreverses;动画结束后是否返回到原来的位置;
CATransition通常用于通过CALayer控制UIView内子控件的过度画面,比如,删除子控件、添加子控件、切换两个子控件。继承CAAnimation
使用CATransition控制UIView内子控件的过度画面的步骤如下:
提示:CATransition继承了CAAnimation,因此也支持指定CAAnimation的removedOnCompletion等属性;
CATransition属性如下:
(1)、 @property(copy) NSString *type;
CATransition的type属性用于控制动画类型,它支持如下值(每个值代表一种类型的动画)。
(2)、 @property(nullable, copy) NSString *subtype;
CATransition的subtype属性用于控制动画方向,它支持如下值:
例如:
- (void)oglFlip:(UIButton *)sender{
//开始执行动画
CATransition* transition = [CATransition animation];
transition.duration = 2.0f;
//通过翻转动画控制子组件的过渡;
transition.type = @"oglFlip";
//指定动画方向,从下向上
transition.subtype = kCATransitionFromBottom;
[self.view.layer addAnimation:transition forKey:@"animation"];
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
属性动画由CAPropertyAnimation代表,该对象用于控制CALayer的动画属性(所有支持数值型属性值的属性几乎都可作为动画属性)持续改变,当CALayer的动画属性持续改变时,CALayer的外观就会持续改变——用户看上去就变成了动画。
CAPropertyAnimation提供了如下类方法来创建属性动画:
方法+ (id)animationWithKeyPath:(NSString *)path;仅需要一个参数,该参数只是一个字符串类型的值,指定CALayer的动画属性名,设置该属性动画控制CALayer的哪个动画属性持续改变。
除此之外,CAPropertyAnimation还支持如下属性:
如果要控制CALayer的位移动画,直接使用属性动画控制CALayer的postion持续改变即可。如果要控制该CALayer的缩放、旋转、斜切等效果,则需要控制如下属性。
一般来说,可使用Core Animation提供了如下属性来创建三维变换矩阵:
使用属性动画控制CALayer的执行动画创建属性动画:
③、如果使用CAKeyframeAnimation属性动画,则指定values属性值,该属性值是一个NSArray属性,其中第一个元素指定动画属性开始时的属性值,toValue指定动画属性结束时的属性值,其他数组元素指定动画变化过程中的属性值。
提示:CABasicAnimation,CAKeyframeAnimation都继承了CAPropertyAnimation,他们都是
属性动画,只是CABasicAnimation只能指定动画属性的开始值和结束值,该CALayer的动画属性
就由开始值变化到结束值;而CAKeyframeAnimation则可为动画属性指定多个值,该CALayer的
动画属性就从values的第一个属性值开始,依次经历每个属性值,知道变成最后一个属性值。
④、调用CALayer的addAnimation:forKey:添加动画即可;
CALayer为动画支持提供了如下方法:
- (void)move:(UIButton *)sender{
//获取animationLayer的位置
CGPoint fromPoint = animationLayer.position;
CGPoint toPoint = CGPointMake(fromPoint.x + 80, fromPoint.y);
//创建不断改变CALayer的position属性的属性动画
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"position"];
//设置动画开始的属性值
anim.fromValue = [NSValue valueWithCGPoint:fromPoint];
//设置动画结束的属性值
anim.toValue = [NSValue valueWithCGPoint:toPoint];
anim.duration = 2;
animationLayer.position = toPoint;
anim.removedOnCompletion = YES;
//添加动画到图层,注意key相当于给动画进行命名,以后获得该图层时可以使用此名称获取
[animationLayer addAnimation:anim forKey:@"KCBasicAnimation_Translation"];
}
- (void)rotationAnimation:(UIButton *)sender{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
//2.设置动画属性初始值、结束值
// basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
//设置其他动画属性
basicAnimation.duration=6.0;
basicAnimation.autoreverses=true;//旋转后再旋转到原来的位置
//4.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[animationLayer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}
- (void)scaleAnimation:(UIButton *)sender{
CABasicAnimation *pulse = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
pulse.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
pulse.duration = 0.5 + (rand() % 10) * 0.05;
pulse.repeatCount = 1;
pulse.autoreverses = YES;
pulse.fromValue = [NSNumber numberWithFloat:.8];
pulse.toValue = [NSNumber numberWithFloat:1.2];
[animationLayer addAnimation:pulse forKey:nil];
}
- (void)scale:(UIButton *)sender{
//创建不断改变CALayer的transform属性的属性动画
CAKeyframeAnimation* anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
//设置CAKeyframeAnimation控制transform属性依次经过的属性值
anim.values = [NSArray arrayWithObjects:[NSValue valueWithCATransform3D:imageLayer.transform],[NSValue valueWithCATransform3D:CATransform3DScale(imageLayer.transform, 0.2, 0.2, 1)],[NSValue valueWithCATransform3D:CATransform3DScale(imageLayer.transform, 2, 2, 1)],[NSValue valueWithCATransform3D:imageLayer.transform], nil];
anim.duration = 5;
anim.removedOnCompletion = YES;
[imageLayer addAnimation:anim forKey:nil];
}
注意:path其他还有transform.scale = 比例转换、transform.scale.x = 宽的比例转换、transform.scale.y = 高的比例转换、transform.rotation.z = 平面圆的旋转、opacity = 透明度、backgroundColor=背景颜色、cornerRadius=圆角。。。
- (void)group:(UIButton *)sender{
CGPoint fromPoint = imageLayer.position;
CGPoint toPoint = CGPointMake(fromPoint.x + 80, fromPoint.y);
//创建不断改变CALayer的position属性的属性动画
CABasicAnimation* moveAnim = [CABasicAnimation animationWithKeyPath:@"positon"];
//设置动画开始的属性值
moveAnim.fromValue = [NSValue valueWithCGPoint:fromPoint];
//设置动画结束的属性值
moveAnim.toValue = [NSValue valueWithCGPoint:toPoint];
moveAnim.duration = 6;
imageLayer.position = toPoint;
moveAnim.removedOnCompletion = YES;
//--------------------
//创建不断改变CALayer的transform属性的属性动画
CABasicAnimation* transformAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = imageLayer.transform;
//设置动画开始的属性值
transformAnim.fromValue = [NSValue valueWithCATransform3D:fromValue];
//创建在X、Y两个方向上缩放为0.5的变换矩阵
CATransform3D scaleValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
//绕Z轴旋转180度的变换矩阵
CATransform3D rotateValue = CATransform3DRotate(fromValue, M_PI, 0, 0, 1);
//计算两个变换矩阵的和
CATransform3D toValue = CATransform3DConcat(scaleValue, rotateValue);
//设置动画结束的属性值
transformAnim.toValue = [NSValue valueWithCATransform3D:toValue];
//动画效果累加
transformAnim.cumulative = YES;
//动画重复执行次数,旋转360度
transformAnim.repeatCount = 2;
transformAnim.duration = 6;
//位移、缩放、旋转组合起来执行
CAAnimationGroup* animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:moveAnim,transformAnim, nil];
animGroup.duration = 6;
[imageLayer addAnimation:animGroup forKey:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat pathBorder = WIN_WIDTH/2;
UIView* bgView = [[UIView alloc]initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)];
bgView.backgroundColor = [UIColor redColor];
[self.view addSubview:bgView];
//画出一个完成的进度的背景轨道
//创建一个路径图层
trackLayer = [CAShapeLayer layer];
trackLayer.frame = CGRectMake(0, 100, pathBorder, pathBorder);
trackLayer.fillColor = [[UIColor clearColor] CGColor];
//指定path的渲染颜色
trackLayer.strokeColor = [[UIColor redColor] CGColor];
//背景同学你就甘心做背景吧,不要太明显了,透明度小一点
trackLayer.opacity = 1;
//指定线的边缘是圆的
trackLayer.lineCap = kCALineCapRound;
//线的宽度
trackLayer.lineWidth = 4;
//上面说明过了用来构建圆形
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(pathBorder/2, pathBorder/2) radius:(pathBorder-4)/2 startAngle:degreesToRadians(-360) endAngle:degreesToRadians(0) clockwise:YES];
//把path传递給layer,然后layer会处理相应的渲染,整个逻辑和CoreGraph是一致的。
trackLayer.path =[path CGPath];
[bgView.layer addSublayer:trackLayer];
//创建背景图层
gradientLayer = [CAGradientLayer layer];
[gradientLayer setColors:[NSArray arrayWithObjects:
(id)[[[UIColor blackColor] colorWithAlphaComponent:1] CGColor],
(id)[[[UIColor yellowColor] colorWithAlphaComponent:1] CGColor],
(id)[[[UIColor purpleColor] colorWithAlphaComponent:1] CGColor],
(id)[[UIColor whiteColor] CGColor],
nil]];
gradientLayer.frame = bgView.bounds;
[gradientLayer setLocations:[NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.3],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0],
nil]];
[gradientLayer setStartPoint:CGPointMake(0, 0.5)];
[gradientLayer setEndPoint:CGPointMake(1, 0.5)];
[bgView.layer addSublayer:gradientLayer];
[gradientLayer setMask:trackLayer]; //用progressLayer来截取渐变层
UIButton* btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(0, 20, self.view.frame.size.width, 40);
[btn addTarget:self action:@selector(startGreenHeadAnimation) forControlEvents:UIControlEventTouchUpInside];
[btn setTitle:@"动画跳转" forState:UIControlStateNormal];
[self.view addSubview:btn];
}
- (void)startGreenHeadAnimation{
//动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.duration = 2;
//获取animationLayer的位置
CGPoint fromPoint = trackLayer.position;
CGPoint toPoint = CGPointMake(fromPoint.x + WIN_WIDTH/2, fromPoint.y);
//设置动画开始的属性值
animation.fromValue = [NSValue valueWithCGPoint:fromPoint];
//设置动画结束的属性值
animation.toValue = [NSValue valueWithCGPoint:toPoint];
trackLayer.position = toPoint;
animation.removedOnCompletion = YES;
[trackLayer addAnimation:animation forKey:@"position"];
}
总结:移动路径图层,背景图层不会跟着移动;
- (void)startGreenHeadAnimation{
//动画
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"position"];
animation1.duration = 2;
//获取animationLayer的位置
CGPoint fromPoint1 = gradientLayer.position;
CGPoint toPoint1 = CGPointMake(fromPoint1.x + WIN_WIDTH/2, fromPoint1.y);
//设置动画开始的属性值
animation1.fromValue = [NSValue valueWithCGPoint:fromPoint1];
//设置动画结束的属性值
animation1.toValue = [NSValue valueWithCGPoint:toPoint1];
gradientLayer.position = toPoint1;
animation1.removedOnCompletion = YES;
[gradientLayer addAnimation:animation1 forKey:@"position"];
}
总结:移动背景图层,路径图层跟着移动;
- (void)startGreenHeadAnimation{
//动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.duration = 2;
//获取animationLayer的位置
CGPoint fromPoint = trackLayer.position;
CGPoint toPoint = CGPointMake(fromPoint.x + WIN_WIDTH/2, fromPoint.y);
//设置动画开始的属性值
animation.fromValue = [NSValue valueWithCGPoint:fromPoint];
//设置动画结束的属性值
animation.toValue = [NSValue valueWithCGPoint:toPoint];
trackLayer.position = toPoint;
animation.removedOnCompletion = YES;
[trackLayer addAnimation:animation forKey:@"position"];
//动画
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"position"];
animation1.duration = 2;
//获取animationLayer的位置
CGPoint fromPoint1 = gradientLayer.position;
CGPoint toPoint1 = CGPointMake(fromPoint1.x - WIN_WIDTH/2, fromPoint1.y);
//设置动画开始的属性值
animation1.fromValue = [NSValue valueWithCGPoint:fromPoint1];
//设置动画结束的属性值
animation1.toValue = [NSValue valueWithCGPoint:toPoint1];
gradientLayer.position = toPoint1;
animation1.removedOnCompletion = YES;
[gradientLayer addAnimation:animation1 forKey:@"position"];
}