我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛
前言
正值中秋来临之际,突然发现掘金小盆友又在偷(da)偷(zhang)摸(qi)摸(gu)地搞活动。那这个热闹我必须要凑一波啊~在此先提前祝各位老铁新年快乐!!!
思路
无意间看到了进军的王小二的作品:嫦娥飞天动画简易版。这不就是我脑海中想象的嫦娥奔月的大致场景吗!perfect~ 既然他用Web端的技术实现,那我用iOS原生也来实现一个。我可真是个小机灵鬼~
夜色背景
嫦娥奔月,顾名思义,有月亮肯定是夜间。所以首先要添加一个静谧祥和的夜色背景。有需要的小伙伴,资源图可从我的项目中获取。
月亮
月亮的绘制
- 中秋节月亮必须是满月,一个完美的圆形。
- 添加一个由内而外的颜色渐变,这样更能凸显出月亮的3d效果。
- 并且在此基础上,可以添加阴影,达到类似月晕的效果。
核心代码:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
// 1.获取CGContextRef
UIGraphicsBeginImageContext(self.bounds.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.绘制path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, CGRectGetMidX(rect), CGRectGetMidY(rect), rect.size.width * 0.5, 0, M_PI * 2, false);
CGPathCloseSubpath(path);
// 3.绘制渐变
[self drawRadialGradient:ctx path:path];
// 4.绘制阴影
self.layer.shadowColor = RGBA(247, 247, 9, 255).CGColor;
self.layer.shadowOffset = CGSizeMake(20, 20);
self.layer.shadowRadius = self.bounds.size.width * 0.5;
self.layer.shadowOpacity = 0.8;
self.layer.shadowPath = path;
// 5.释放path 有create就对应有release
CGPathRelease(path);
// 6.从上下文中获取图像,显示在界面上
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imageView];
}
- (void)drawRadialGradient:(CGContextRef)context path:(CGPathRef)path {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = @[( __bridge id) RGBA(196, 196, 60, 255).CGColor, ( __bridge id) RGBA(240, 240, 9, 255).CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, ( __bridge CFArrayRef) colors, locations);
CGRect pathRect = CGPathGetBoundingBox(path);
CGPoint center = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMidY(pathRect));
CGFloat radius = MAX(pathRect.size.width * 0.5, pathRect.size.height * 0.5) * sqrt(2);
CGContextSaveGState(context);
CGContextAddPath(context, path);
CGContextEOClip(context);
CGContextDrawRadialGradient(context, gradient, center, 0, center, radius, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
复制代码
给月亮添加动画
因为嫦娥是奔向月亮的,所以我们给月亮添加一个由远及近的缩放动画
CABasicAnimation *moonScaleAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
moonScaleAnim.fromValue = [NSNumber numberWithFloat:0.8];
moonScaleAnim.toValue = [NSNumber numberWithFloat:1.5];
moonScaleAnim.duration = 4;
moonScaleAnim.autoreverses = NO;
moonScaleAnim.removedOnCompletion = NO;
moonScaleAnim.fillMode = kCAFillModeForwards;
moonScaleAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.moonView.layer addAnimation:moonScaleAnim forKey:nil];
复制代码
嫦娥
嫦娥的绘制
嫦娥使用了进军的王小二文章里提供的gif动图。有需要的小伙伴也可以到我的项目中获取。采用SDWebImage三方库进行加载
核心代码:
- (UIImageView *)changeImgView {
if (_changeImgView == nil) {
_changeImgView = [[UIImageView alloc] init];
NSData *changeData = [[NSDataAsset alloc] initWithName:@"change" bundle:[NSBundle mainBundle]].data;
UIImage *changeImage = [UIImage sd_animatedGIFWithData:changeData];
_changeImgView.image = changeImage;
_changeImgView.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height, CHANGE_WIDTH, CHANGE_WIDTH * changeImage.size.width / changeImage.size.height);
}
return _changeImgView;
}
复制代码
给嫦娥添加螺旋飞天动画
- 嫦娥是飞向远处的月亮,所以其必然会有一个位移动画
- 由近及远,其身影会越来越小。所以还需要一个缩放动画
- 当小到一定程度,其背影会渐渐模糊。故还添加了一个延迟的透明度渐变
所以嫦娥这里存在3个动画:位移、缩放、透明度。其中缩放和位移动画我要的精确度比较高,所以采取了CAKeyframeAnimation帧动画
// 缩放
CAKeyframeAnimation *changeScaleAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
changeScaleAnim.values = @[[NSNumber numberWithFloat:1.2],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:0.5]];
changeScaleAnim.keyTimes = @[[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0]];
changeScaleAnim.beginTime = 0;
// 透明度
CABasicAnimation *changeOpacityAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
changeOpacityAnim.fromValue = @(1.0);
changeOpacityAnim.toValue = @(0.0);
// 延迟模糊
changeOpacityAnim.beginTime = 2.5;
// 位移
CAKeyframeAnimation *changePositionAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
// 来个螺旋升天(贝塞尔曲线)
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
[path addQuadCurveToPoint:self.moonView.center controlPoint:CGPointMake(CGRectGetMinX(self.changeImgView.frame) - 100, CGRectGetMinY(**self**.moonView.frame))];
changePositionAnim.path = path.CGPath;
changePositionAnim.beginTime = 0;
// 动画组
CAAnimationGroup *changeAnim = [CAAnimationGroup animation];
changeAnim.duration = 4;
changeAnim.repeatCount = 1;
changeAnim.removedOnCompletion = NO;
changeAnim.fillMode = kCAFillModeForwards;
changeAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
changeAnim.animations = @[changeScaleAnim, changePositionAnim, changeOpacityAnim];
[self.changeImgView.layer addAnimation:changeAnim forKey:nil];
复制代码
点点繁星
繁星的绘制
思路:一个个带阴影的小白点,随机分布在屏幕之间。并且其大小也进行了随机范围内的设置,让每一个星星个体都互相有差异
核心代码:
for (int i = 0; i < 100; i ++) {
// 创建小星星
UIView *starView = [[UIView alloc] init];
CGFloat starViewX = random() % ((int)[UIScreen mainScreen].bounds.size.width);
CGFloat starViewY = random() % ((int)([UIScreen mainScreen].bounds.size.height));
CGFloat starViewWH = random() % 3 + 2;
starView.frame = CGRectMake(starViewX, starViewY, starViewWH, starViewWH);
starView.backgroundColor = [UIColor whiteColor];
starView.layer.shadowColor = [UIColor whiteColor].CGColor;
starView.layer.shadowOffset = CGSizeMake(0, 0);
starView.layer.shadowRadius = starViewWH * 0.5;
starView.layer.shadowOpacity = 0.8;
starView.layer.cornerRadius = starViewWH * 0.5;
[self.view insertSubview:starView belowSubview:self.moonView];
...
}
复制代码
让繁星闪烁起来
使用CoreAnimation核心动画让其透明度变化起来,并且每个小白点的动画开始时间、动画时长、透明度变化值都进行随机处理。这样使得繁星不仅在位置尺寸上有静态性差异,并且在效果的动态性上也存在差异,就惟妙惟肖地完成了点点繁星的效果
核心代码:
// 小星星闪烁动画
CGFloat delayTime = (random() % 10) / 10.f + 0.5;
CGFloat duration = (random() % 2) + 1;
CGFloat toValue = (random() % 6) / 10.f;
CABasicAnimation *starOpacityAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
starOpacityAnim.fromValue = @(1.0);
starOpacityAnim.toValue = @(toValue);
starOpacityAnim.duration = duration;
starOpacityAnim.beginTime = delayTime;
starOpacityAnim.autoreverses = YES;
starOpacityAnim.repeatCount = MAXFLOAT;
starOpacityAnim.removedOnCompletion = NO;
starOpacityAnim.fillMode = kCAFillModeForwards;
starOpacityAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[starView.layer addAnimation:starOpacityAnim forKey:**nil**];
复制代码
流星
流星的绘制
思路:流星的绘制思路我是采取线性渐变 + mask遮罩进行实现。将绘制的流星转为UIImage备孕,以便于后面的粒子动画使用
核心代码:
#pragma mark - lazy loading
- (CAShapeLayer *)shaperLayer {
if (_shaperLayer == nil) {
_shaperLayer = [CAShapeLayer layer];
_shaperLayer.lineCap = kCALineCapButt;
_shaperLayer.lineJoin = kCALineJoinRound;
_shaperLayer.strokeColor = [UIColor redColor].CGColor;
_shaperLayer.fillColor = [[UIColor redColor] CGColor];
_shaperLayer.lineWidth = 3;
_shaperLayer.backgroundColor = [UIColor clearColor].CGColor;
}
return _shaperLayer;
}
- (CAGradientLayer *)gradientLayer {
if (_gradientLayer == nil) {
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.colors = @[
( __bridge id)[UIColor whiteColor].CGColor,
( __bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0.6].CGColor,
( __bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0.3].CGColor,
( __bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0].CGColor
];
_gradientLayer.startPoint = CGPointMake(0, 1);
_gradientLayer.endPoint = CGPointMake(1, 0);
_gradientLayer.locations = @[@0, @0.3, @0.6, @1];
}
return _gradientLayer;
}
#pragma mark - private methods
- (void)makeUI {
[self.layer addSublayer:self.gradientLayer];
// 设置遮罩路径
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, CGRectGetMinX(self.bounds) + 5, CGRectGetMaxY(self.bounds) - 5);
CGPathAddLineToPoint(path, NULL, CGRectGetMaxX(self.bounds) - 5, CGRectGetMinY(self.bounds) + 5);
CGPathCloseSubpath(path);
self.shaperLayer.path = path;
CGPathRelease(path);
self.layer.mask = **self**.shaperLayer;
}
#pragma mark - public methods
// 转为屏幕快照
- (UIImage *)convertViewToImage {
UIImage *starRainImage = [[UIImage alloc] init];
// 设置背景透明背景,并且不失真
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
starRainImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return starRainImage;
}
复制代码
粒子动画实现流星雨效果
上面也提到了,把绘制的流星转化为UIImage备孕,原因就是流星雨的效果采用了粒子动画进行实现。而粒子动画的contents一般都是CGImage的集合
核心代码:
- (void)startStarRainAnimation {
CAEmitterLayer *meteor = [CAEmitterLayer layer];
// 发射位置
meteor.emitterPosition = CGPointMake(-300, 0);
// 粒子产生系数,默认为1
meteor.birthRate = 1;
// 发射器的尺寸
meteor.emitterSize = CGSizeMake(4000, 0);
// 发射的形状
meteor.emitterShape = kCAEmitterLayerCuboid;
// 发射的模式
meteor.emitterMode = kCAEmitterLayerLine;
// 渲染模式
meteor.renderMode = kCAEmitterLayerAdditive;
CAEmitterCell *cell = [CAEmitterCell emitterCell];
// 设置粒子每秒的生成数量
cell.birthRate = 15;
// 设置生成时的初始速度的变化范围
cell.velocity = 500.0f;
cell.velocityRange = 300.0f;
// 设置粒子的Y轴加速度
cell.yAcceleration = 0.0f;
// 缩放比例
cell.scale= 0.5f;
// 缩放速度
cell.scaleSpeed = 0.1;
// 粒子存活时长
cell.lifetime = 2.5f/*1.8f*/;
// 粒子的初始发射方向
cell.emissionLongitude = 0.75 * M_PI;
// 展示流星快照
cell.contents = ( __bridge id _Nullable)(self.starRainImage.CGImage);
// 设置粒子颜色
cell.color = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.6].CGColor;
meteor.emitterDepth = 10.0f;
meteor.shadowOpacity = 0.0f;
meteor.shadowRadius = 0.0f;
meteor.emitterCells = [NSArray arrayWithObject:cell];
[self.view.layer insertSublayer:meteor below:self.moonView.layer];
}
复制代码
奔月开始
做完上面的一切,点击运行,嫦娥奔月的整体效果就出来了。
推荐使用iPad Pro真机运行,效果最佳
小结
麻雀虽小,五脏俱全。通过这个小小的实践,里面涉及的知识点还是有一些的
并且发现掘金的活动iOSer好像不怎么活跃,相关活动的iOS文章少之又少。希望本篇文章能抛砖引玉,把更多的iOS大佬们吸引过来,让其他端的小伙伴可以见识到我们移动端的魅力~
最后,再次祝各位老铁中秋愉快,单身的小伙伴们早日收获爱情
多多点赞 + 评论哦~