教你做泡泡动画

 

  老规矩,效果图如下所示:

  

  刚开始我看这个动画的时候真的被震撼到了,无论用什么来实现都觉得非常非常得难,用粒子发射器,动力框架,感觉都做不出来.只觉得其中的算法肯定及其复杂又复杂, 然而,越是高级的程序员越是能用"懒惰的方法"实现复杂的功能,没错,对于这个动画处理方式,我只是封装了一个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

 

 

  

posted @ 2016-03-26 19:37  6Xa天  阅读(582)  评论(0编辑  收藏  举报