本篇的Demo地址:
github.com/tanghaitao/…
假如想修改frame,angle等,即简单的动画的话,直接修改相关属性就行。
但是想做复杂的动画的,就需要使用核心动画CoreAnimation.
本篇将以一种由浅入深的方式带领大家一起学习CoreAnimaton(同时也希望大佬能提出宝贵的意见)
CoreAnimation中核心的类是CALayer,
先引入一个知识点: CALayer和UIView的区别:
Layer的隐式动画/显示动画
- 隐式动画:默认时间0.25s(位置,颜色,大小),必须是独立的layer(不是uiview.layer)才有隐式动画,uiview(根layer)是没有隐式动画的
- 显示动画:下图中的动画,需要实例化具体的类才能显示对应效果的画。
下面的CoreAnimaton的结构图:
Layer的一些概念:
KeyPath属性设置:
动画三步骤:
1.初始化动画对象
2.设置需要修改的动画属性的值
3.把动画添加到layer(所有的动画都是添加在layer上,不是view)
`你看到的都是假象,真正的view是没有发生变化的`
postition:(layer里面设置中心点) center:uiview
//redview宽高100,layer.position.y移动到400后,uiview.frame.y= 400-100/2 = 350
presentationLayer和modelLayer(呈现层和模型层),属性设置先修改模型层的数据,Vsync来的时候呈现层再从模型层获取数据,重绘一次。
锚点:1.概念;2.单位坐标0-1;3.和position的关系:就是锚点在父视图的位置
隐式动画:默认时间0.25s(位置,颜色,大小),必须是独立的layer(不是uiview.layer)才有隐式动画,uiview(根layer)
显示动画:
复制代码
1 基础动画CABasicAnimation
Demo地址:github.com/tanghaitao/…
下面从例子进行讲解:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"position.y";
anim.toValue = @400;
[_redView.layer addAnimation:anim forKey:nil];
}
复制代码
上面演示效果,动画结束后没有停在结束位置,而是回到了原来的位置了。
anim.removedOnCompletion = NO;//默认会回到最初的位置
anim.fillMode = kCAFillModeForwards;//默认会回到最初的位置
复制代码
添加上面两行代码后的效果, 就会保持动画结束时的状态
如果想在动画开始之前或者结束的时候做一些额外的操作,就需要使用代理:
@interface ViewController ()<CAAnimationDelegate>
anim.delegate = self;//动画开始和结束时
- (void)animationDidStart:(CAAnimation *)anim{
NSLog(@"start-%@",NSStringFromCGRect(_redView.frame));
}
//hittest
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"stop-%@",NSStringFromCGRect(_redView.frame));
// NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.presentationLayer.frame));
// NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.modelLayer.frame));
}
start-{{0, 0}, {100, 100}}
stop-{{0, 0}, {100, 100}}
复制代码
为什么动画结束的时候打印的是 stop-{{0, 0}, {100, 100}}
。
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
[_redView addGestureRecognizer:tap];
- (void)tapAction:(UITapGestureRecognizer *)ges{
NSLog(@"redview被点击了");
}
复制代码
程序运行点击redview,打印redview被点击了
动画结束点击redview,不打印
redview被点击了
//NSLog(@"stop-%@",NSStringFromCGRect(_redView.frame));// stop-{{0, 0}, {100, 100}}
//NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.frame));// stop-{{0, 0}, {100, 100}}
NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.presentationLayer.frame));// stop-{{0, 349.99860227108002}, {100, 100}}
//NSLog(@"stop-%@",NSStringFromCGRect(_redView.layer.modelLayer.frame));// stop-{{0, 0}, {100, 100}}
复制代码
_redView.layer.presentationLayer.frame
呈现的位置
2 关键帧动画CAKeyframeAnimation
Demo地址:
github.com/tanghaitao/…
#define angleToRadians(angle) ((angle) / 180.0 *M_PI)//角度转弧度
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"transform.rotation";
anim.values = @[@angleToRadians(-6), @angleToRadians(6)];
anim.repeatCount = MAXFLOAT;
anim.duration = 2;//默认是0.5秒
复制代码
效果如下:
可以看出, 从开始到结束
是正常的,但是 从结束到开始
不正常,马上回去了0.5,duration不是持续时间。
解决办法:
anim.values = @[@angleToRadians(-6),@angleToRadians(6),@angleToRadians(-6)];
anim.autoreverses = YES;//反转,
同时设置anim.speed = 2;
, 反转后速度会变慢 (推荐使用这种方式
)
效果如下:
2.1 案例讲解:过山车动画
UIBezierPath贝塞尔曲线:利用公式把数据绘制成曲线
绘制有三个关键信息: 落笔point 连线 addline 抬笔point
复制代码
最终效果:
开始讲解思路:
2.1 画贝塞尔曲线
/// 画贝塞尔曲线
-(void)test21{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
// [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(112.5, 100) controlPoint2:CGPointMake(237.5, 300)];
//(50,200)最左边的点
// (300,200) 终点
// (180, 100) 顶部 (200, 300) 底部
// width: 300-50 = 250,长度分别为1/2= 125,1/4= 62.5,中点位置是50+125 = 175 .
// 左边中点 175-1/4 = 175-62.5 = 112.5
// 右边中点 175+1/4 = 175+62.5 = 237.5
// height: 小于200,顶部, 大于200,底部
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;// ---------------- 这里
[self.view.layer addSublayer:shapeLayer];
}
复制代码
效果展示:
2.2 去掉填充颜色,设置画笔颜色
/// 去掉填充颜色,以及设置画笔颜色
-(void)test22{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
[self.view.layer addSublayer:shapeLayer];
}
复制代码
效果展示:
2.3 绘制过山车图片
/// 绘制过山车图片
-(void)test23 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
//这里 begin
CALayer *carLayer = [CALayer layer];
carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
[self.view.layer addSublayer:carLayer];
//这里 end
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
[self.view.layer addSublayer:shapeLayer];
}
复制代码
效果展示:
2.4 添加过山车动画
/// 添加过山车动画 动起来,添加动画属性
-(void)test24 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
CALayer *carLayer = [CALayer layer];
carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
[self.view.layer addSublayer:carLayer];
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
[self.view.layer addSublayer:shapeLayer];
// 过山车动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.path = path.CGPath;//路径,不是values
anim.keyPath = @"position";//改变position
anim.duration = 5.0f;
[carLayer addAnimation:anim forKey:nil];
}
复制代码
效果展示:
2.5 车角度没有跟着曲线走
/// 过山车的角度问题,角度没有跟着曲线走
-(void)test25 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
CALayer *carLayer = [CALayer layer];
carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
[self.view.layer addSublayer:carLayer];
carLayer.anchorPoint = CGPointMake(0.5, .8);//改变车的角度
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
[self.view.layer addSublayer:shapeLayer];
// 过山车动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.path = path.CGPath;
anim.keyPath = @"position";
anim.duration = 5.0f;
anim.rotationMode = kCAAnimationRotateAuto; // --------2
[carLayer addAnimation:anim forKey:nil];
}
复制代码
效果展示:
2.6 停留在结束位置
/// 停留在结束位置
-(void)test26 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
CALayer *carLayer = [CALayer layer];
carLayer.frame = CGRectMake(50-30, 200-18, 36, 36);//50-36/2 = 32
carLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"car.png"].CGImage);
[self.view.layer addSublayer:carLayer];
carLayer.anchorPoint = CGPointMake(0.5, .8);//改变车的角度
//path添加到layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;//填充颜色
shapeLayer.strokeColor = [UIColor blackColor].CGColor;//画笔颜色
[self.view.layer addSublayer:shapeLayer];
// 过山车动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.path = path.CGPath;
anim.keyPath = @"position";
anim.duration = 5.0f;
anim.rotationMode = kCAAnimationRotateAuto;
//动画结束
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
[carLayer addAnimation:anim forKey:nil];
}
复制代码
效果展示:
上面是整个的分析过程,可以根据效果来分析每一步操作。
3 转场动画CATransition
补充一个知识点 frame和bounds.关于这2个属性的区分,举个简单例子,就好比一个人睡觉,笔直的睡觉时,frame和bounds是一样的,但如果这个人斜着睡觉,那么frame就会变大,bounds是自身的。类似下面的图: 旋转后frame = {-1,3,62,64},bounds={0,0,40,50};
Demo地址:
github.com/tanghaitao/…
转场类型:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
_index++;
if (_index == 3) {
_index = 0;
}
NSString *imgName = _imgs[_index];
_imgView.image = [UIImage imageNamed:imgName];
CATransition *anim = [CATransition animation];
anim.type = @"cube";
anim.duration = .5;
// anim.startProgress = .2;//转场开始时机
// anim.endProgress = .5;//转场结束时机:一半的时候就结束转场动画
[_imgView.layer addAnimation:anim forKey:nil];
}
复制代码
效果展示:
4 动画组CAAnimationGroup
顾名思义就是多个动画同时执行
Demo地址:
github.com/tanghaitao/…
ViewController.m
文件的 test1()
方法中:
- (void)test1{
// 曲线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 200)];
[path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
//需要添加在layer上
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = nil;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 60, 60);
colorLayer.position = CGPointMake(50, 200);
colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:colorLayer];
// 过山车的动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"position";
anim.path = path.CGPath;
// anim.duration = 4.0f;
// anim.removedOnCompletion = NO;
// anim.fillMode = kCAFillModeForwards;
// anim.rotationMode = kCAAnimationRotateAuto;
// [colorLayer addAnimation:anim forKey:nil];
// 改变大小
CABasicAnimation *sizeAnim = [CABasicAnimation animation];
sizeAnim.keyPath = @"transform.scale";
sizeAnim.toValue = @.5;
// sizeAnim.duration = 4.0;
// sizeAnim.fillMode = kCAFillModeForwards;
// sizeAnim.removedOnCompletion = NO;
//
// [colorLayer addAnimation:sizeAnim forKey:nil];
// 修改颜色
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1];
CABasicAnimation *colorAnim = [CABasicAnimation animation];
colorAnim.keyPath = @"backgroundColor";
colorAnim.toValue = (id)color.CGColor;
// colorAnim.duration = 4.0f;
// colorAnim.fillMode = kCAFillModeForwards;
// colorAnim.removedOnCompletion = NO;
// [colorLayer addAnimation:colorAnim forKey:nil];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[anim, sizeAnim, colorAnim];
//重复的代码可以放到动画组里面。
group.duration = 4.0f;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[colorLayer addAnimation:group forKey:nil];
}
复制代码
效果展示:
// 过山车的动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
// 改变大小
CABasicAnimation *sizeAnim = [CABasicAnimation animation];
// 修改颜色
CABasicAnimation *colorAnim = [CABasicAnimation animation];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[anim, sizeAnim, colorAnim];
//重复的代码可以放到动画组里面。
group.duration = 4.0f;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
这里添加了三个动画,同时改变,需要使用动画组CAAnimationGroup
复制代码