前言:
一个人有多不正经,就有多深情。一个程序员有多闷,就代表ta有多骚。
我不等山无棱,不等夏雨雪,不等天地合。因为即使江水为竭,即使冬雷震震,我都在你一个转身就能触碰的距离。
(纯小白教程。七夕福利哦~)
一、绘制蓝色海洋背景
从动图中可以看出这片背景呈渐变色,自然我们可以想到用CAGradientLayer类来创建渐变图层。这块代码值得一提的地方在于设置颜色,CAGradientLayer的colors属性是一个数组,里面可以用于存放不同的颜色。在本案例中,采用的是C语言库的颜色,因此需要用到桥接的方式将C对象转换成OC的对象。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//创建背景
CAGradientLayer *gradientLayer = [[CAGradientLayer alloc] init];
CGFloat gradientX = 0;
CGFloat gradientY = self.view.bounds.size.height/2;
CGFloat gradientW = self.view.bounds.size.width;
CGFloat graidientH = self.view.bounds.size.height/2;
//设置大小
gradientLayer.frame = CGRectMake(gradientX, gradientY, gradientW, graidientH);
//设置颜色
gradientLayer.colors = @[(__bridge id)[UIColor whiteColor].CGColor,(__bridge id)[UIColor colorWithRed:0 green:0.5 blue:1 alpha:0.5].CGColor,(__bridge id)[UIColor blueColor].CGColor];
//将图层添加到self
[self.view.layer addSublayer:gradientLayer];
}
二、新建爱心的类,并做初始化
爱心部分将采用贝塞尔曲线来绘制爱心的路径,并用动画的形式让爱心在贝塞尔曲线上波动。
新创建一个代表爱心的类,并且加入爱心的图片文件拖入到文件中。
在类中定义一个初始化方法,该方法用于设置爱心能到达的最大高度和宽度,以及初始的位置和它的父视图。
@interface HeartImageView : UIImageView
- (instancetype)initWithMaxHeight:(CGFloat)maxHeight maxWidth:(CGFloat)maxWidth maxFrame:(CGRect)frame andSuperView:(UIView*)superView;
@end
实现这个方法
直接创建这个类的一个实例。那么它是用来存放图片的。所以用图片初始化。
-(instancetype)initWithMaxHeight:(CGFloat)maxHeight maxWidth:(CGFloat)maxWidth maxFrame:(CGRect)frame andSuperView:(UIView *)superView {
self = [[HeartImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"heart" ofType:@"jpg" ]]];
return self;
}
三、处理爱心的大小和位置
设置一个全局变量,用它来存储图片原本的大小。而这个尺寸其实就是外界传入的。
@interface HeartImageView()
@property (nonatomic, assign) CGRect originFrame; //图片原始的尺寸
@end
每个爱心的大小都是不一样的。所以这里我们封装一个方法,让爱心随机放大。
当然,这要传入一个坐标。用来确定从哪个位置产生爱心。而这个爱心的宽度是随机的,所以也要创建一个随机的函数。
//随机数
- (CGFloat)makeRandomNumberFromMin:(CGFloat)min toMax:(CGFloat)max {
NSInteger precision = 100;
CGFloat subtraction = ABS(max - min);
subtraction *= precision;
CGFloat randomNumber = arc4random()%((int)subtraction+1);
randomNumber /= precision;
randomNumber += min;
return randomNumber;
}
//处理图片的大小 让它随机
- (CGRect)getRandomFrameWithFrame:(CGRect)frame {
CGFloat width = [self makeRandomNumberFromMin:15 toMax:self.originFrame.size.width];
CGRect randomFrame = CGRectMake(frame.origin.x, frame.origin.y, width, width);
return randomFrame;
}
此时,随机大小的方法已经处理完毕,先调用已经完成的方法,看爱心的大小和位置是否是随机的。
//在初始化方法中添加
if(self) {
//爱心最初的大小设置成传入的frame
self.originFrame = frame;
//大小调用随机的方法
self.frame = [self getRandomFrameWithFrame:frame];
[superView addSubview:self];
}
到原始类中调用爱心类。本案例中是ViewController类。
先导入爱心类。而该案例的标语是神秘,浪漫。所以这里用触摸屏幕的方式来触发动画。并且,过盈则亏,我们总不可能让爱心充满整个屏幕吧。
所以设置一个这样的变量,规定大于30,我们就不产生爱心了。
#import "ViewController.h"
#import "HeartImageView.h"
@interface ViewController ()
@property (nonatomic,assign)NSInteger heartNumber; //爱心的最大数量
@end
//随机数
- (CGFloat)makeRandomNumberFromMin:(CGFloat)min toMax:(CGFloat)max {
NSInteger precision = 100;
CGFloat subtraction = ABS(max - min);
subtraction *= precision;
CGFloat randomNumber = arc4random()%((int)subtraction+1);
randomNumber /= precision;
randomNumber += min;
return randomNumber;
}
//设置随机的尺寸传入到HeartImageView类中,从而使每个爱心的位置不一样。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if(self.heartNumber <= 30) {
HeartImageView *heartImageView = [[HeartImageView alloc] initWithMaxHeight:self.view.bounds.size.height/2.5 maxWidth:self.view.bounds.size.width/1.5 maxFrame:CGRectMake([self makeRandomNumberFromMin:0 toMax:self.view.bounds.size.width], self.view.center.y, 50, 50) andSuperView:self.view];
self.heartNumber++;
}
}
现在可以产生随机大小和随机位置的图片了
8F57B75C-9E31-46DF-A6B1-5312CA08734D.png四、设置动画的方向
图片的大小处理好了之后呢,我们就要处理爱心的动画了。
第一步就是要觉得这个爱心到底是往左飘还是往右飘呢。
所以这里用一个枚举。列举爱心的方向。
(枚举值都是一个整形,并且不参加内存的占用和释放。枚举定义的变量可以直接使用,不要初始化)
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger,HeartPathType) {
heartPathTypeLeft = 0,
heartPathTypeRight
};
设置一个全局变量,来表示动画的路径方向
@property (nonatomic, assign) CGRect originFrame; //图片原始的尺寸
爱心上升的方向是随机分配的,所以创建一个随机方法来决定是向左还是向右
- (void)getRandomBubbleType {
if (arc4random()%2 == 1) {
self.pathType = heartPathTypeRight;
}else {
self.pathType = heartPathTypeLeft;
}
}
五、设置动画的路径
先设置全局变量记录动画的最大高度和宽度
@property (nonatomic, assign) CGFloat maxHeight; //爱心到底的最大高度
@property (nonatomic, assign) CGFloat maxWidth ;
@property (nonatomic, assign) CGFloat nowMaxHeight;
@property (nonatomic, assign) CGFloat nowMaxWidth; //目前最大宽度
@property (nonatomic, assign) CGPoint originPoint; //爱心的原点
设定一个方法来处理动画的宽和高
动画的宽和高,使爱心目前的高度和宽度随机分布。
//动画随机的分部
- (void)getRandomPathWidthAndHeight {
self.nowMaxHeight = [self makeRandomNumberFromMin:self.maxHeight/2 toMax:self.maxHeight];
self.nowMaxWidth = [self makeRandomNumberFromMin:0 toMax:self.maxWidth];
}
六、绘制贝塞尔曲线(动画路径)
@property (nonatomic, assign) CGPoint originPoint; //爱心的原点
分别对向左还是向右的爱心做一下操作。这里设三阶曲线。也就是要设置两个控制点。这里涉及到贝塞尔曲线的一些原理。也就是说贝塞尔曲线之所以是曲线都是因为它有很多的点控制。而这条曲线无限接近于这些点所连接的折线。 公式则是n阶曲线的控制点为n-1。
- (void)setupBezierPath {
//创建贝塞尔曲线
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:self.originPoint];
if(self.pathType == heartPathTypeLeft) {
//三阶曲线 2个控制点 n n-1
CGPoint leftControllPoint = CGPointMake(self.originPoint.x - self.nowMaxWidth/2, self.originPoint.y-self.nowMaxHeight /4);
CGPoint rightControllPoint = CGPointMake(self.originPoint.x + self.nowMaxWidth/2, self.originPoint.y - self.nowMaxHeight * 3/4);
//终点
CGPoint termalPoint = CGPointMake(self.originPoint.x, self.originPoint.y-self.nowMaxHeight);
[bezierPath addCurveToPoint:termalPoint controlPoint1:leftControllPoint controlPoint2:rightControllPoint];
}else {
//三阶曲线 2个控制点 n n-1
CGPoint leftControllPoint = CGPointMake(self.originPoint.x - self.nowMaxWidth/2, self.originPoint.y-self.nowMaxHeight *3/4);
CGPoint rightControllPoint = CGPointMake(self.originPoint.x + self.nowMaxWidth/2, self.originPoint.y - self.nowMaxHeight /4);
//终点
CGPoint termalPoint = CGPointMake(self.originPoint.x, self.originPoint.y-self.nowMaxHeight);
[bezierPath addCurveToPoint:termalPoint controlPoint1:leftControllPoint controlPoint2:rightControllPoint];
}
//移动的动画 关键帧动画
//创建一个以position为关键字的实例
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[keyFrameAnimation setDuration:2.0];
//动画的路径是贝塞尔曲线的路径
keyFrameAnimation.path = bezierPath.CGPath;
keyFrameAnimation.fillMode = kCAFillModeForwards; //当动画结束,layer会保存动画最后的状态
//运行一次是否移除动画
keyFrameAnimation.removedOnCompletion = NO;
keyFrameAnimation.delegate = self;
//给图层加动画 以关键字为movingAnimation
[self.layer addAnimation:keyFrameAnimation forKey:@"movingAnimation"];
}
刚刚讲的关键帧动画只是路径和第一个爱心的操作,但是我们要的效果是在这个动画结束了之后还会源源不断的冒出新的爱心。那么我们就要在这个爱心动画结束之后再对之后冒出的爱心进行处理。
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
//核心动画事物,必须要在事物中进行
[CATransaction begin];
[CATransaction setCompletionBlock:^{
//UIViewAnimationOptionTransitionCrossDissolve是指旧视图溶解消失显示下一个新视图的效果。
[UIView transitionWithView:self duration:0.1f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
//爱心往上飘的时候放大1.3倍
self.transform = CGAffineTransformMakeScale(1.3, 1.3);
} completion:^(BOOL finished) {
if(finished == YES) {
[self.layer removeAllAnimations];
HeartImageView *heartView = [[HeartImageView alloc] initWithMaxHeight:self.maxHeight maxWidth:self.maxWidth maxFrame:self.originFrame andSuperView:self.superview];
//展示完之后要移除,不然爱心会一直停留在屏幕上
[self removeFromSuperview];
}
}];
}];
[CATransaction commit]; //提交事务 动画结束
}
七、调用自定义的方法
if(self) {
//爱心最初的大小设置成传入的frame
self.originFrame = frame;
//大小调用随机的方法
self.frame = [self getRandomFrameWithFrame:frame];
[superView addSubview:self];
//======下面的方法是新加入的===============
self.originPoint = self.frame.origin;
self.maxHeight = maxHeight;
self.maxWidth = maxWidth;
self.alpha = [self makeRandomNumberFromMin:0.5 toMax:1];
[self getRandomBubbleType];
[self getRandomPathWidthAndHeight];
[self setupBezierPath];
}
网友评论