先看下效果
写的demo和零散和知识点过于分散开个博客的坑总结和复习下,沙漏动画主要是关于贝塞尔曲线和帧动画的一个复习与使用总结。首先我们将要实现的效果拆分分为沙漏上半部分动画,沙漏下半部分动画,及沙子的动画和最后的旋转的动画。
沙堆下半部分动画
其实我一开始的想法很简单,就是用两张图片,做位移。但是向上的动画还好说,向下的话,沙堆下降的时候超出去的部分会展示出来,至于背景色填充,遮罩这种的方案,就更不符合需求了。所以一定要用layer去做。
- (void)configUI{
UIBezierPath*path = [[UIBezierPath alloc]init];
[pathmoveToPoint:CGPointMake(0,self.frame.size.height*1/3)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width/2, 0)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height*1/3)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height)];
[pathaddLineToPoint:CGPointMake(0, self.frame.size.height)];
[pathaddLineToPoint:CGPointMake(0, self.frame.size.height * 1/3)];
[pathclosePath];
CAShapeLayer *sublayer = [[CAShapeLayer alloc]init];
sublayer.masksToBounds=YES;
sublayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
sublayer.path= path.CGPath;
sublayer.fillColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
self.masksToBounds = YES;
self.mask= sublayer;
self.mainLayer= sublayer;
[self addSublayer:self.mainLayer];
}
然后就是要让他跑起来
- (void)startAnimation{
[self.mainLayer removeAllAnimations];
CAKeyframeAnimation *animation = [[CAKeyframeAnimation alloc]init];
animation.keyPath=@"position";
NSValue *v1 = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height*1.5)];
NSValue *v2 = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
NSValue *v3 = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
animation.keyTimes = @[[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:0.9],[NSNumber numberWithFloat:1.0]];
animation.values=@[v1,v2,v3];
animation.duration=6.5;
[animationsetRemovedOnCompletion:YES];
animation.repeatCount=MAXFLOAT;
animation.timingFunction = [[CAMediaTimingFunction alloc]initWithControlPoints:0.85 :0 :1 :1];
animation.fillMode = kCAFillModeForwards;
[self.mainLayeraddAnimation:animationforKey:@""];
}
让他跑起来很简单用关键帧动画就可以了,说白了就是一个可以分时间段控制动画进程的类,讲几个关键性的属性或者说难以理解的属性。
第一个keyTimes,传入的数0-1之间的 后一个必须大于前一个 干什么用的呢,控制每一个动画的所占总时长的比例比如我传的 0,0.9,1.0 就是第一段动画占总时间的6.5秒的十分之九 然后剩下的十分之一的时间做第二段动画。
第二个timingFunction属性 这个属性可以提供一个贝塞尔曲线控制动画的随着时间完成的速度,系统有几个预设好的 ,easeinout这种的。不过我们自己自定义一个数字去理解下
CAMediaTimingFunction这个类的初始化方法是传两个点。没有这两个点的时候,动画是线性的,斜率固定。但是当我们加入两个控制点的时候。
斜率就发生变化了,根据斜率来开,动画的速度是先快后慢,在快的过程。
我传的两个点是(0.8,0),(1,1)了,所以这个贝尔赛曲线的斜率一定是刚开始很慢,后来飙升这么一个过程。那动画也会这样,一开始不动,后来越来越快。
沙漏上半部分动画
沙漏上半部分动画和下半部分动画最大的区别就是之前说的不能让他只做简单的位移否的话就效果不对了这时候我们需要用到一个很关键的属性CALayer的mask,按我的理解就是mask是layer上的一层可视范围,就比如大家经常做圆角的时候开启的属性masktobounds,CAShapeLayer是CALayer的子类,它可以填充颜色,还有path这个属性,可以用贝塞尔曲线画出一个图形,创建一个CAShapeLayer然后设置它的path等于我们画好的沙堆的图形,最后在让CALayer的mask=CASHaperLayer,这样超出的部分就不会看到了。一个透明的不规则的CALayer就ok了。然后在用刚才的path同样的方式做一个沙堆layer,放在父layer的底部。
- (void)configUI{
UIBezierPath *path = [[UIBezierPath alloc]init];
[pathmoveToPoint:CGPointMake(0, 0)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width, 0)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height*2/3)];
[pathaddLineToPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height)];
[pathaddLineToPoint:CGPointMake(0, self.frame.size.height*2/3)];
[pathaddLineToPoint:CGPointMake(0, 0)];
[pathclosePath];
CAShapeLayer *sublayer = [[CAShapeLayer alloc]init];
sublayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
sublayer.path= path.CGPath;
self.mainLayer= [[CAShapeLayeralloc]init];
self.mainLayer.path= path.CGPath;
self.mainLayer.fillColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
self.mask= sublayer;
[self addSublayer:self.mainLayer];
}
沙子动画
这个就比较简单了,就是几组沙子向下做位移就可以了,我就直接贴代码了
- (void)configUI{
self.masksToBounds = YES;
self.sand05Layer= [[CALayeralloc]init];
self.sand05Layer.frame=CGRectMake(1, -3,1,1);
self.sand05Layer.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand05Layer];
self.sand05Layer1= [[CALayeralloc]init];
self.sand05Layer1.frame=CGRectMake(1, -10,1,2);
self.sand05Layer1.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand05Layer1];
self.sand03Layer= [[CALayeralloc]init];
self.sand03Layer.frame=CGRectMake(0, -10,1,2);
self.sand03Layer.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand03Layer];
self.sand03Layer1= [[CALayeralloc]init];
self.sand03Layer1.frame=CGRectMake(1, -3,1,1);
self.sand03Layer1.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand03Layer1];
self.sand01Layer= [[CALayeralloc]init];
self.sand01Layer.frame=CGRectMake(0, -3,1,1);
self.sand01Layer.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand01Layer];
self.sand01Layer1= [[CALayeralloc]init];
self.sand01Layer1.frame=CGRectMake(1, -10,1,2);
self.sand01Layer1.backgroundColor = [UIColor colorWithRed:206/255.0 green:206/255.0 blue:206/255.0 alpha:206/255.0].CGColor;
[self addSublayer:self.sand01Layer1];
}
- (void)startAnimation{
[self addAnimation:0.5 WithLayer:self.sand05Layer];
[self addAnimation:0.5 WithLayer:self.sand05Layer1];
[self addAnimation:0.3 WithLayer:self.sand03Layer];
[self addAnimation:0.3 WithLayer:self.sand03Layer1];
[self addAnimation:0.1 WithLayer:self.sand01Layer];
[self addAnimation:0.1 WithLayer:self.sand01Layer1];
}
- (void)addAnimation:(CFTimeInterval)duration WithLayer:(CALayer*)layer{
[layerremoveAllAnimations];
CABasicAnimation *animation = [[CABasicAnimation alloc]init];
animation.keyPath=@"position";
animation.fromValue= [NSValuevalueWithCGPoint:layer.position];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(layer.position.x, self.frame.size.height)];
animation.duration= duration;
[animationsetRemovedOnCompletion:YES];
animation.repeatCount=MAXFLOAT;
animation.valueFunction = [CAValueFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fillMode = kCAFillModeForwards;
[layeraddAnimation:animationforKey:@""];
}
组合+旋转动画
最后就是给上述的三个组合起来
- (void)SubView{
self.clipsToBounds = YES;
self.topLayer = [[HourglassNewTopLayer alloc]initWithFrame:CGRectMake(7, 3, boxWidth, boxHeight)];
self.bottomLayer = [[HourglassBottomLayer alloc]initWithFrame:CGRectMake(7, 28, boxWidth, boxHeight)];
self.sandLayer= [[SandLayeralloc]initWithFrame:CGRectMake(24,30,2,25)];
[self.layer addSublayer:self.topLayer];
[self.layer addSublayer:self.bottomLayer];
[self.layer addSublayer:self.sandLayer];
[self startAnimation];
}
然后进行旋转动画
- (void)startAnimation{
[self.topLayer startAnimation];
[self.bottomLayer startAnimation];
[self.sandLayer startAnimation];
[self.layer removeAllAnimations];
CAKeyframeAnimation *animation = [[CAKeyframeAnimation alloc]init];
animation.keyPath = @"transform.rotation.z";
animation.keyTimes = @[[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:0.97],[NSNumber numberWithFloat:1.0]];
animation.values = @[[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:M_PI]];
animation.duration=6.5;
[animationsetRemovedOnCompletion:YES];
animation.repeatCount=MAXFLOAT;
CAMediaTimingFunction *EaseOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAMediaTimingFunction *EaseIn = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animation.timingFunctions=@[EaseOut,EaseOut,EaseOut,EaseIn];
animation.fillMode = kCAFillModeForwards;
[self.layeraddAnimation:animationforKey:@""];
}
最后的沙漏背景图用贝塞尔直接画或者切图都可以都不是很麻烦。
最后总结一下iOS大部分的动画都可以用贝塞尔曲线和帧动画来一起合作完成而且提供的效果之繁多几乎每种酷炫的动画都可以拆分来做所以大家了解基本的以后以后的动画需求完全不需要有所惧怕。
参考资料:
iOS核心动画