动画| 金币抛入红包动画详解

前言

这个动画效果很早就出来了,也是一个比较经典的关键帧动画和组合动画的运用,通过剖析源码,可以发现实际上这个酷炫的动画实现起来很简单。

金币.gif

实现过程

  • 在当前页面加载一个福袋的图片和再来一次的按钮。
  • 在for 循环中使用延迟调用函数。每个函数的调用时间越来越靠后,达到依次出现的效果。
  • 在每个延迟调用函数中创建一个金币的图片,并记录它的tag和最终的位置。
  • 为这个金币图片随机生成开始位置,并根据开始位置和结束位置计算出控制点,利用这三点绘制二次贝塞尔曲线。
  • 每个金币图层都执行一个动画组,一边沿轨迹做抛物线动画一遍做从大到小的3D缩放动画。
  • 每个金币执行完动画后,从图层中移除。
  • 所有金币都执行完动画后钱袋图层执行摇晃动画。

立即打开

//统计金币数量的变量
static int coinCount = 0;
- (void)getCoinAction:(UIButton *)btn
{
    //"立即打开"按钮从视图上移除
    [btn removeFromSuperview];
    
    //初始化金币生成的数量
    coinCount = 0;
    for (int i = 0; i<kCoinCountKey; i++) {
        //延迟调用函数
        [self performSelector:@selector(initCoinViewWithInt:) withObject:[NSNumber numberWithInt:i] afterDelay:i * 0.01];
    }
}

延迟调用函数

- (void)initCoinViewWithInt:(NSNumber *)i
{
    UIImageView *coin = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"icon_coin_%d.png",[i intValue] % 2 + 1]]];
    
    //初始化金币的最终位置
    coin.center = CGPointMake(CGRectGetMidX(self.view.frame) + arc4random()%40 * (arc4random() %3 - 1) - 20, CGRectGetMidY(self.view.frame) - 20);
    coin.tag = [i intValue] + 1;
    NSLog(@"KKK: %lf",CGRectGetMidX(self.view.frame));
    //每生产一个金币,就把该金币对应的tag加入到数组中,用于判断当金币结束动画时和福袋交换层次关系,并从视图上移除
    [_coinTagsArr addObject:[NSNumber numberWithInteger:coin.tag]];
    
    [self.view addSubview:coin];

    [self setAnimationWithLayer:coin];
}

每个金币图层都需要做的动画组

- (void)setAnimationWithLayer:(UIView *)coin
{
    CGFloat duration = 1.6f;
    
    ////////////////////////////////////////////////////////////////////////////////////////////
    //绘制从底部到福袋口之间的抛物线
    CGFloat positionX   = coin.layer.position.x;    //终点x
    CGFloat positionY   = coin.layer.position.y;    //终点y
    
    int fromX       = arc4random() % 320;     //起始位置:x轴上随机生成一个位置
    int height      = [UIScreen mainScreen].bounds.size.height + coin.frame.size.height; //y轴以屏幕高度为准
    int fromY       = arc4random() % (int)positionY; //起始位置:生成位于福袋上方的随机一个y坐标
    
    CGFloat cpx = positionX + (fromX - positionX)/2;    //x控制点
    CGFloat cpy = fromY / 2 - positionY;                //y控制点,确保抛向的最大高度在屏幕内,并且在福袋上方(负数)

    CGMutablePathRef path = CGPathCreateMutable();
    //动画的起始位置
    CGPathMoveToPoint(path, NULL, fromX, height);
    CGPathAddQuadCurveToPoint(path, NULL, cpx, cpy, positionX, positionY);

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    [animation setPath:path];
    CFRelease(path);
    path = nil;
    
    ////////////////////////////////////////////////////////////////////////////////////////////
    //图像由大到小的变化动画
    CGFloat from3DScale = 1 + arc4random() % 10 *0.1;
    CGFloat to3DScale = from3DScale * 0.5;
    CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    scaleAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(from3DScale, from3DScale, from3DScale)], [NSValue valueWithCATransform3D:CATransform3DMakeScale(to3DScale, to3DScale, to3DScale)]];
    scaleAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];

    
    ////////////////////////////////////////////////////////////////////////////////////////////
    //动画组合
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.delegate = self;
    group.duration = duration;
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    group.animations = @[scaleAnimation, animation];
    [coin.layer addAnimation:group forKey:@"position and transform"];
}

每个金币图层动画组执行完后执行

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (flag) {
        //动画完成后把金币和数组对应位置上的tag移除
        UIView *coinView = (UIView *)[self.view viewWithTag:[[_coinTagsArr firstObject] intValue]];
        
        [coinView removeFromSuperview];
        [_coinTagsArr removeObjectAtIndex:0];
        
        //全部金币完成动画后执行的动作
        if (++coinCount == kCoinCountKey) {
            [self bagShakeAnimation];
            if (_getBtn) {
                [self.view addSubview:_getBtn];
                [_getBtn setTitle:@"再来一次" forState:UIControlStateNormal];
            }
        }
    }
}

福袋晃动动画

- (void)bagShakeAnimation
{
    CABasicAnimation* shake = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    shake.fromValue = [NSNumber numberWithFloat:- 0.2];
    shake.toValue   = [NSNumber numberWithFloat:+ 0.2];
    shake.duration = 0.1;
    shake.autoreverses = YES;
    shake.repeatCount = 4;
    
    [_bagView.layer addAnimation:shake forKey:@"bagShakeAnimation"];
}

PS:如果我们把每一个金币图层的路径都画出来的话,会更明白图层的轨迹了:

金币轨迹.gif

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法+

图片文档倾斜矫正算法 附完整c代码

 2年前在学习图像算法的时候看到一个文档倾斜矫正的算法。 也就是说能将一些文档图像进行旋转矫正, 当然这个算法一般用于一些文档扫描软件做后处理 或者用于ocr ...

8665
来自专栏陈满iOS

iOS动画专题·UIView二维形变动画与CAAnimation核心动画(transform动画,基础,关键帧,组动画,路径动画,贝塞尔曲线)

总的来说,从涉及类的形式来看,iOS动画有:基于UIView的仿射形变动画,基于CAAnimation及其子类的动画,基于CG的动画。这篇文章着重总结前两种动画...

4711
来自专栏pangguoming

HTML特殊符号对照表

HTML特殊符号对照表 特殊符号 命名实体 十进制编码 特殊符号 命名实体 十进制编码 Α &Alpha...

5366
来自专栏老司机的简书

老司机带你走进Core Animation 之图层的透视、渐变及复制

老司机的想法就是要把CoreAnimation头文件中的类大概都说一遍,毕竟一开始把系列名定成了《老司机带你走进CoreAnimation》(深切的觉得自己给自...

1704
来自专栏前端儿

Windows下编程--模拟时钟的实现

(4)   编写显示数字时钟函数。注意要自己用矩形填充(FillRect)擦除背景。

1341
来自专栏非典型技术宅

iOS动画系列之八:使用CAShapeLayer绘画动态流量图1. CAShapeLayer2. 实战:绘制一个镂空图层动画3. 使用CAShapeLayer绘画动态流量图

2303
来自专栏進无尽的文章

绘图-几种基本统计图的实现分析

在开发中我们会遇到各种统计图,或者各种绘图,本文通过对基本三大统计图:折线图、柱状图、扇形图的实现来掌握基本统计图的绘制,在下一篇文中会带来复杂一些的绘图案例分...

1961
来自专栏向治洪

React Native仿美团下拉菜单

在很多产品中都会涉及到下拉菜单选择功能,用的最好的当属美团了,其效果如下: ? 要实现上面的效果,在原生中比较好做,直接使用PopWindow组件即可。如果使...

7845
来自专栏叁金大数据

WPF Canvas 画区域

有时候需要实现类似于QQ截图那样的选择区域功能,这里的区域可以是一条线,圆,矩形等等

2103
来自专栏算法channel

Python-GUI|Tk类,属性文档使用指南

这是一篇tkinter相关API的介绍性地帮助文档,包括常用的包,类结构图,属性取值等,可以作为一个工具文档,供大家查阅。 01Tk中的包 __main...

3417

扫码关注云+社区

领取腾讯云代金券