教你做泡泡动画
2016-03-26 19:37
316 查看
以下文章版权归叶景天博客所有`
老规矩,效果图如下所示:
刚开始我看这个动画的时候真的被震撼到了,无论用什么来实现都觉得非常非常得难,用粒子发射器,动力框架,感觉都做不出来.只觉得其中的算法肯定及其复杂又复杂, 然而,越是高级的程序员越是能用"懒惰的方法"实现复杂的功能,没错,这个动画只是封装了一个imageView的子类而已,并没有想象当中的那么复杂,我把动画实现的渲染图放到下面,你看了也绝对会恍然大悟:
是否瞬间觉得这个动画简单了许多,我以前好像提到过,看似高级的动画,其实基本都是由几个CoreAnimation的叠加形成的,接下来我们便分析一下这个动画是怎么叠加做成的?
分解:
先不要看那么多的气泡,但从一个气泡上面进行分析:
1.从渲染图上可以看出,一个气泡上面用了大量的随机函数,气泡的大小,气泡的透明度,贝塞尔曲线的变化,所以封装的过程当中必然要先写一个随机的方法,方便调用;
2.贝塞尔曲线变化的规律,从渲染图可以看出,贝塞尔曲线的起点是不变的,变的只是有三个点,左边的控制点,和有右边的控制点,以及贝塞尔曲线的终点;
3.让气泡沿着贝塞尔曲线行走,用关键帧动画是最理想不过的了,关键帧动画不仅能够制定它的key数组,还能给他制定一条路径,让气泡沿着这条路径行走
4.气泡走到贝塞尔曲线终点的时候,我们也可以看到它有一个爆破的效果,因此关键帧动画必然有一个delegate方便在动画结束的时候执行爆破动画
5.动画爆破的效果主要有两个动作 放大->消失 放大之后还要消失,所以用UIView封装的transitionWithView来做这个效果在得知放大之后(动画结束之后),将泡泡imageView从父视图中移除掉;
单个泡泡的分析完毕,那么一群泡泡的分析就简单多了,就只有一个特点,随机在屏幕的中间X轴的位置上产生一个新的泡泡!
分析完毕之后就可以写代码了,整体的代码封装在一个集成了UIImageView的BubbleImageView上面
首先是随机取一个从 min 到 max的一个浮点值函数的写法
- (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;
}
以下是BubbleImageView的属性列表:
@interface BubbleImageView ()
@property (nonatomic, assign)CGFloat maxHeight; //贝塞尔曲线的最大高度
@property (nonatomic, assign)CGFloat maxWidth; //贝塞尔曲线的最大宽度
@property (nonatomic, assign)BubblePathType pathType;
//每一次动画执行的最大高度和最大宽度
@property (nonatomic, assign)CGFloat nowMaxHeight;
@property (nonatomic, assign)CGFloat nowMaxWidth;
@property (nonatomic, assign)CGPoint originPoint;
@property (nonatomic, assign)CGRect originFrame;
//贝塞尔曲线的渲染layer;
//@property (nonatomic, strong)CAShapeLayer *shapeLayer;
@end
接着是BubbleImageView的初始化方法
- (instancetype)initWithMaxHeight:(CGFloat) maxHeight maxWidth: (CGFloat)maxWidth maxFrame:(CGRect)frame andSuperView: (UIView *)superView
{
self = [[BubbleImageView alloc]initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"bubble" ofType:@"png"]]];
if (self) {
self.originFrame = frame;
self.frame = [self getRandomFrameWithFrame:frame];
self.originPoint = self.frame.origin;
[superView addSubview:self];
self.maxHeight = maxHeight;
self.maxWidth = maxWidth;
self.alpha = [self makeRandomNumberFromMin:0.5 toMax:1];
[self getRandomBubbleType];
[self getRandomPathWidthAndHeight];
[self setUpBezierPath];
}
return self;
}
//三个随机函数的方法如下面所示
//这个随机函数是为了获得,泡泡是先往哪个方向走,总共有两种方向,第一种是先左后右,第二种是先右后左
//如手稿所示
- (void)getRandomBubbleType
{
if (arc4random() %2 ==1) {
self.pathType = BubblePathTypeLeft;
}else{
self.pathType = BubblePathTypeRight;
}
}
- (void)getRandomPathWidthAndHeight {
self.nowMaxHeight = [self makeRandomNumberFromMin:self.maxHeight / 2 toMax:self.maxHeight];
self.nowMaxWidth = [self makeRandomNumberFromMin:0 toMax:self.maxWidth];
}
- (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;
}
//绘制贝塞尔曲线方法
//这里绘制贝塞尔曲线的方法需要重点讲一下:
大致原理图如下手稿所示
- (void)setUpBezierPath
{
//开始绘制泡泡的贝塞尔曲线
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:self.originPoint];
if (self.pathType == BubblePathTypeLeft) {
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{
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:rightControllPoint controlPoint2:leftControllPoint];
}
// self.shapeLayer = [CAShapeLayer layer];
// self.shapeLayer .path = bezierPath.CGPath;
// [self.shapeLayer setStrokeColor:[UIColor redColor].CGColor];
// [self.superview.layer addSublayer:self.shapeLayer ];
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[keyFrameAnimation setDuration:2.0];
keyFrameAnimation.path = bezierPath.CGPath;
keyFrameAnimation.fillMode = kCAFillModeForwards;
keyFrameAnimation.removedOnCompletion = NO;
keyFrameAnimation.delegate = self;
[self.layer addAnimation:keyFrameAnimation forKey:@"movingAnimation"];
}
//绘制完贝塞尔曲线和添加玩动画之后,我们在动画停止之后的那个方法进行爆破动画
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// transform the image to be 1.3 sizes larger to
// give the impression that it is popping
[UIView transitionWithView:self
duration:0.1f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.transform = CGAffineTransformMakeScale(1.3, 1.3);
} completion:^(BOOL finished) {
if (finished == YES) {
[self.layer removeAllAnimations];
BubbleImageView *bubbleImageView = [[BubbleImageView alloc]initWithMaxHeight:self.maxHeight maxWidth:self.maxWidth maxFrame:self.originFrame andSuperView:self.superview];
// [self.shapeLayer removeFromSuperlayer];
[self removeFromSuperview];
}
}];
}];
[CATransaction commit];
}
//完成一个泡泡的封装之后,你若将它直接添加到view上面去将会看到这样的效果
//接下来我们来将一下蓝色的渐变色是怎么制作的,需要用到一个CALayer的子类CAGradientLayer,只要将它的colors数组赋值,便能够实现渐变的效果,当然你也可以指定它的startPoint和endPoint默认是渐变方向从上往下,所以这里并没有指定
CAGradientLayer *gradientLayer = [[CAGradientLayer alloc]init];
[gradientLayer setFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height / 2, self.view.bounds.size.width, self.view.bounds.size.height / 2)];
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.view.layer addSublayer:gradientLayer];
//好了,我们的泡泡动画到这里就结束了,代码的设计上并不是很难,关键在相关参数的调试上,如果你需要Demo的话可以到我的GitHub上面下载
//下载地址是:
https://github.com/bnb173yjx/BubbleAnimationDemo
老规矩,效果图如下所示:
刚开始我看这个动画的时候真的被震撼到了,无论用什么来实现都觉得非常非常得难,用粒子发射器,动力框架,感觉都做不出来.只觉得其中的算法肯定及其复杂又复杂, 然而,越是高级的程序员越是能用"懒惰的方法"实现复杂的功能,没错,这个动画只是封装了一个imageView的子类而已,并没有想象当中的那么复杂,我把动画实现的渲染图放到下面,你看了也绝对会恍然大悟:
是否瞬间觉得这个动画简单了许多,我以前好像提到过,看似高级的动画,其实基本都是由几个CoreAnimation的叠加形成的,接下来我们便分析一下这个动画是怎么叠加做成的?
分解:
先不要看那么多的气泡,但从一个气泡上面进行分析:
1.从渲染图上可以看出,一个气泡上面用了大量的随机函数,气泡的大小,气泡的透明度,贝塞尔曲线的变化,所以封装的过程当中必然要先写一个随机的方法,方便调用;
2.贝塞尔曲线变化的规律,从渲染图可以看出,贝塞尔曲线的起点是不变的,变的只是有三个点,左边的控制点,和有右边的控制点,以及贝塞尔曲线的终点;
3.让气泡沿着贝塞尔曲线行走,用关键帧动画是最理想不过的了,关键帧动画不仅能够制定它的key数组,还能给他制定一条路径,让气泡沿着这条路径行走
4.气泡走到贝塞尔曲线终点的时候,我们也可以看到它有一个爆破的效果,因此关键帧动画必然有一个delegate方便在动画结束的时候执行爆破动画
5.动画爆破的效果主要有两个动作 放大->消失 放大之后还要消失,所以用UIView封装的transitionWithView来做这个效果在得知放大之后(动画结束之后),将泡泡imageView从父视图中移除掉;
单个泡泡的分析完毕,那么一群泡泡的分析就简单多了,就只有一个特点,随机在屏幕的中间X轴的位置上产生一个新的泡泡!
分析完毕之后就可以写代码了,整体的代码封装在一个集成了UIImageView的BubbleImageView上面
首先是随机取一个从 min 到 max的一个浮点值函数的写法
- (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;
}
以下是BubbleImageView的属性列表:
@interface BubbleImageView ()
@property (nonatomic, assign)CGFloat maxHeight; //贝塞尔曲线的最大高度
@property (nonatomic, assign)CGFloat maxWidth; //贝塞尔曲线的最大宽度
@property (nonatomic, assign)BubblePathType pathType;
//每一次动画执行的最大高度和最大宽度
@property (nonatomic, assign)CGFloat nowMaxHeight;
@property (nonatomic, assign)CGFloat nowMaxWidth;
@property (nonatomic, assign)CGPoint originPoint;
@property (nonatomic, assign)CGRect originFrame;
//贝塞尔曲线的渲染layer;
//@property (nonatomic, strong)CAShapeLayer *shapeLayer;
@end
接着是BubbleImageView的初始化方法
- (instancetype)initWithMaxHeight:(CGFloat) maxHeight maxWidth: (CGFloat)maxWidth maxFrame:(CGRect)frame andSuperView: (UIView *)superView
{
self = [[BubbleImageView alloc]initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"bubble" ofType:@"png"]]];
if (self) {
self.originFrame = frame;
self.frame = [self getRandomFrameWithFrame:frame];
self.originPoint = self.frame.origin;
[superView addSubview:self];
self.maxHeight = maxHeight;
self.maxWidth = maxWidth;
self.alpha = [self makeRandomNumberFromMin:0.5 toMax:1];
[self getRandomBubbleType];
[self getRandomPathWidthAndHeight];
[self setUpBezierPath];
}
return self;
}
//三个随机函数的方法如下面所示
//这个随机函数是为了获得,泡泡是先往哪个方向走,总共有两种方向,第一种是先左后右,第二种是先右后左
//如手稿所示
- (void)getRandomBubbleType
{
if (arc4random() %2 ==1) {
self.pathType = BubblePathTypeLeft;
}else{
self.pathType = BubblePathTypeRight;
}
}
- (void)getRandomPathWidthAndHeight {
self.nowMaxHeight = [self makeRandomNumberFromMin:self.maxHeight / 2 toMax:self.maxHeight];
self.nowMaxWidth = [self makeRandomNumberFromMin:0 toMax:self.maxWidth];
}
- (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;
}
//绘制贝塞尔曲线方法
//这里绘制贝塞尔曲线的方法需要重点讲一下:
大致原理图如下手稿所示
- (void)setUpBezierPath
{
//开始绘制泡泡的贝塞尔曲线
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:self.originPoint];
if (self.pathType == BubblePathTypeLeft) {
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{
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:rightControllPoint controlPoint2:leftControllPoint];
}
// self.shapeLayer = [CAShapeLayer layer];
// self.shapeLayer .path = bezierPath.CGPath;
// [self.shapeLayer setStrokeColor:[UIColor redColor].CGColor];
// [self.superview.layer addSublayer:self.shapeLayer ];
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[keyFrameAnimation setDuration:2.0];
keyFrameAnimation.path = bezierPath.CGPath;
keyFrameAnimation.fillMode = kCAFillModeForwards;
keyFrameAnimation.removedOnCompletion = NO;
keyFrameAnimation.delegate = self;
[self.layer addAnimation:keyFrameAnimation forKey:@"movingAnimation"];
}
//绘制完贝塞尔曲线和添加玩动画之后,我们在动画停止之后的那个方法进行爆破动画
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// transform the image to be 1.3 sizes larger to
// give the impression that it is popping
[UIView transitionWithView:self
duration:0.1f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.transform = CGAffineTransformMakeScale(1.3, 1.3);
} completion:^(BOOL finished) {
if (finished == YES) {
[self.layer removeAllAnimations];
BubbleImageView *bubbleImageView = [[BubbleImageView alloc]initWithMaxHeight:self.maxHeight maxWidth:self.maxWidth maxFrame:self.originFrame andSuperView:self.superview];
// [self.shapeLayer removeFromSuperlayer];
[self removeFromSuperview];
}
}];
}];
[CATransaction commit];
}
//完成一个泡泡的封装之后,你若将它直接添加到view上面去将会看到这样的效果
//接下来我们来将一下蓝色的渐变色是怎么制作的,需要用到一个CALayer的子类CAGradientLayer,只要将它的colors数组赋值,便能够实现渐变的效果,当然你也可以指定它的startPoint和endPoint默认是渐变方向从上往下,所以这里并没有指定
CAGradientLayer *gradientLayer = [[CAGradientLayer alloc]init];
[gradientLayer setFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height / 2, self.view.bounds.size.width, self.view.bounds.size.height / 2)];
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.view.layer addSublayer:gradientLayer];
//好了,我们的泡泡动画到这里就结束了,代码的设计上并不是很难,关键在相关参数的调试上,如果你需要Demo的话可以到我的GitHub上面下载
//下载地址是:
https://github.com/bnb173yjx/BubbleAnimationDemo
相关文章推荐
- ThinkPHP框架中遇到的若干问题手札1
- 自定义View里面的自定义属性的时候报错:Attribute "color" has already been defined
- leetcode 338:Counting Bits 数1,C++
- 【杭电-oj】-1008-Elevator(电梯)
- 单元测试
- 第五周上机实践项目——项目3-时间类-(1)
- 第一个不重复的字符---剑指Offer
- UVA 1343(p210)----The Rotation Game
- 学习笔记-第五周-学习笔记
- S3C2451_uart_ARM串口操作
- json基础教程
- hdu1249三角形
- 查看Android版本的源代码
- 《高可用MySQL》2 – 单机版MySQL主从配置
- java事务学习笔记(三)--丑陋的案例
- 二十、XML的SAX解析
- 有道词典单词本如何由默认的英音切换到美音
- CSU1659: Graph Center(最短路)
- Shell代码规范
- XGBoost-Python完全调参指南-介绍篇