下载按钮|动画

前言

此demo主要注重,按钮的动画并没有实现离线下载,如果对动画不感兴趣的老铁就可以不看了。
效果来源:网络搜索微操作的时候搜到的


downloadButton.gif

分析

看到一个动画我们不要一拿到就开始做,首先多分析分析,这个怎么实现有哪些元素构成

开始状态

开始做下载操作之前会有个竖线变成小圆点,然后网上抛出去的动画

抛出.png

然后是下载中
下载中.png

还有最后的完成状态
完成.png

总的我们归整以下只有背景圆、竖线、箭头、进度圆、波浪(最后的勾也是波浪的一种状态)和显示文件大小的Label。这里对于图形的绘制我们都选择CAShapeLayer

/**
 背景圆
 */
@property(nonatomic,strong)CAShapeLayer *bgCircleShapeLayer;
/** 竖线*/
@property(nonatomic,strong)CAShapeLayer *pointShapeLayer;
/** 箭头*/
@property(nonatomic,strong)CAShapeLayer *arrowShapeLayer;
/** 进度*/
@property(nonatomic,strong)CAShapeLayer *progressShapeLayer;
/** 波浪*/
@property(nonatomic,strong)AIDownloadWaveLayer *waveLayer;
/** 文件大小*/
@property(nonatomic,weak)UILabel *progressLabel;

动画

点击了按钮到实际开始下载之间,我们是有个动画
1、竖线变成点
这种变路径代码我们选择基础动画中的path属性

//变为点
   UIBezierPath         *pointPath      = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.ai_middleX, self.ai_middleY + 1) radius:.5 startAngle:0 endAngle:2*M_PI clockwise:NO];
   CABasicAnimation    *changeToPoint   = [CABasicAnimation animationWithKeyPath:@"path"];
   changeToPoint.toValue                = (__bridge id)(pointPath.CGPath);
   changeToPoint.fillMode               = kCAFillModeForwards;
   changeToPoint.removedOnCompletion    = NO;
   changeToPoint.duration               = .2;
   [self.pointShapeLayer addAnimation:changeToPoint forKey:nil];

2、箭头变成直线
在变成直线的时候纵坐标在网上移动一点,这样会有种圆点是被线弹上去的感觉
上下的位移肯定选择position.y
箭头变为线要有种把圆点弹出去的感觉动画选择CASpringAnimation 属性还是选择path这里直线的路径要注意节点左右对称不然达不到左右一样的效果。

 //箭头变为线
    
    CABasicAnimation   *lineAniamtion  = [CABasicAnimation animationWithKeyPath:@"position.y"];
    
    lineAniamtion.duration              = .2;
    lineAniamtion.fillMode              = kCAFillModeBackwards;
    lineAniamtion.toValue               = @(self.arrowShapeLayer.y +10);
    lineAniamtion.removedOnCompletion   = NO;
    
    UIBezierPath         *linePath      = [self linePath];
    
    CASpringAnimation   *lineSpringAnimation    = [CASpringAnimation animationWithKeyPath:@"path"];
    lineSpringAnimation.toValue                 = (__bridge id _Nullable)(linePath.CGPath);
    lineSpringAnimation.duration                = lineSpringAnimation.settlingDuration;
    lineSpringAnimation.damping                 = 0;
    lineSpringAnimation.mass                    = 30;
    lineSpringAnimation.stiffness               = 5;
    lineSpringAnimation.initialVelocity         = 30;
    lineSpringAnimation.fillMode                = kCAFillModeForwards;
    lineSpringAnimation.beginTime               = .2;
    lineSpringAnimation.removedOnCompletion     = NO;
    
    
    CAAnimationGroup    *groupAnimation         = [CAAnimationGroup animation];
    groupAnimation.duration                     = 1;
    groupAnimation.fillMode                     = kCAFillModeForwards;
    groupAnimation.removedOnCompletion          = NO;
    groupAnimation.animations                   = @[lineAniamtion,lineSpringAnimation];
    [self.arrowShapeLayer addAnimation:groupAnimation forKey:nil];
    
    //圆点起跳
    CASpringAnimation   *pointSpringAnimation   = [CASpringAnimation animationWithKeyPath:@"position.y"];
    pointSpringAnimation.delegate               = self;
    [pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];
    pointSpringAnimation.toValue                = @(-self.ai_height*.5 - self.bgCircleShapeLayer.lineWidth * 0.5  + self.pointShapeLayer.lineWidth * 0.5 );
    pointSpringAnimation.duration               = pointSpringAnimation.settlingDuration;
    pointSpringAnimation.fillMode               = kCAFillModeForwards;
    pointSpringAnimation.removedOnCompletion    = NO;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(changeToPoint.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.pointShapeLayer addAnimation:pointSpringAnimation forKey:nil];
    });

3、圆点起跳到背景圆上
这里我们还是选择CASpringAnimation动画上下移动就选择position.y属性,由于这个小圆点跳出完成后才开算下载,我们要在圆点起跳动画完成后回调,我们为动画添加key/value在动画结束的时候好找到。
[pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];

//圆点起跳
    CASpringAnimation   *pointSpringAnimation   = [CASpringAnimation animationWithKeyPath:@"position.y"];
    pointSpringAnimation.delegate               = self;
    [pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];
    pointSpringAnimation.toValue                = @(-self.ai_height*.5 - self.bgCircleShapeLayer.lineWidth * 0.5  + self.pointShapeLayer.lineWidth * 0.5 );
    pointSpringAnimation.duration               = pointSpringAnimation.settlingDuration;
    pointSpringAnimation.fillMode               = kCAFillModeForwards;
    pointSpringAnimation.removedOnCompletion    = NO;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(changeToPoint.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.pointShapeLayer addAnimation:pointSpringAnimation forKey:nil];
    });

开始下载

下载的时候要做几件事
0、状态变更,然后回调
1、隐藏变为直线的箭头
[self opacityAnimationWithLayer:self.arrowShapeLayer fromValue:1. toValue:0.];
2、文件大小label显示并变大

//文件大小
      
        [self scaleAnimationWithLayer:self.progressLabel.layer fromValue:.1 toValue:1.];
        
        [self opacityAnimationWithLayer:self.progressLabel.layer fromValue:0. toValue:1.];

1和2的步骤都有不透明度的变化,所以这里直接把透明度提取出一个方法,我这里是使用pop动画,想多熟悉下当让用CABaseAnimation也可以

/**
不透明度动画

@param layer 要执行动画的layer
@param from 从多少开始
@param to 到多少
*/
- (void)opacityAnimationWithLayer:(CALayer*)layer fromValue:(CGFloat)from toValue:(CGFloat)to {
   POPBasicAnimation *opacityAnimation          = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
   opacityAnimation.toValue                     = @(to);
   opacityAnimation.fromValue                   = @(from);
   opacityAnimation.duration                    = .3;
   [layer pop_addAnimation:opacityAnimation forKey:nil];
}

然后待会下载完成后文件大小的label会有变小的动画,所以这里把,大小的变化也提取出一个方法

/**
 缩放动画

 @param layer 所要缩放的layer
 @param from 从多少比例开始
 @param to 到多少比例
 */
- (void)scaleAnimationWithLayer:(CALayer*)layer fromValue:(CGFloat)from toValue:(CGFloat)to {
    //文件大小
    POPBasicAnimation   *scaleAnimation          = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
    scaleAnimation.fromValue                     = [NSValue valueWithCGPoint:CGPointMake(from, from)];
    scaleAnimation.toValue                       = [NSValue valueWithCGPoint:CGPointMake(to, to)];
    scaleAnimation.duration                      = .3;
    [layer pop_addAnimation:scaleAnimation forKey:nil];
}

提取这些方法的好处不止于这里减少重复代码,而且以后有类似的动画也可以直接复制过去使用
3、添加波浪动画
这里我简单说下波浪动画原理

  • 正弦函数: y =Asin(ωx+φ)+C
  • A 表示振幅,也就是使用这个变量来调整波浪的高度
  • ω表示周期,也就是使用这个变量来调整在屏幕内显示的波浪的数量
  • φ表示波浪横向的偏移,也就是使用这个变量来调整波浪的流动
  • C表示波浪纵向的位置,也就是使用这个变量来调整波浪在屏幕中竖直的位置。

在自己找到合适的A和ω后我们只需要改变φ我这里只做了四个波形,所以每个加π/2四个后刚好回到原点,波浪就是这这四个波形的切换然后就形成了一次波浪,在一次波浪完成后继续调用下次波浪。
下面是一次波浪动画的代码:

- (void)waveAnimateWithLayer:(CALayer*)layer {
    //    1
    CABasicAnimation *waveAnimationStart = [CABasicAnimation animationWithKeyPath:@"path"];
    waveAnimationStart.fromValue = (__bridge id _Nullable)(self.wavePathStarting.CGPath);
    waveAnimationStart.toValue = (__bridge id _Nullable)(self.wavePath1.CGPath);
    waveAnimationStart.beginTime = 0.0;
    waveAnimationStart.duration = KAnimationDuration;
    //    2
    CABasicAnimation *waveAnimation1 = [CABasicAnimation animationWithKeyPath:@"path"];
    waveAnimation1.fromValue = (__bridge id _Nullable)(self.wavePath1.CGPath);
    waveAnimation1.toValue = (__bridge id _Nullable)(self.wavePath2.CGPath);
    waveAnimation1.beginTime = waveAnimationStart.beginTime + waveAnimationStart.duration;
    waveAnimation1.duration = KAnimationDuration;
    //    3
    CABasicAnimation *waveAnimation2 = [CABasicAnimation animationWithKeyPath:@"path"];
    waveAnimation2.fromValue = (__bridge id _Nullable)(self.wavePath2.CGPath);
    waveAnimation2.toValue = (__bridge id _Nullable)(self.wavePath3.CGPath);
    waveAnimation2.duration = KAnimationDuration;
    waveAnimation2.beginTime = waveAnimation1.beginTime + waveAnimation1.duration;
    //    4
    CABasicAnimation *waveAnimationLow = [CABasicAnimation animationWithKeyPath:@"path"];
    waveAnimationLow.fromValue = (__bridge id _Nullable)(self.wavePath3.CGPath);
    waveAnimationLow.toValue = (__bridge id _Nullable)(self.wavePath4.CGPath);
    waveAnimationLow.duration = KAnimationDuration;
    waveAnimationLow.beginTime = waveAnimation2.beginTime + waveAnimation2.duration;
    //    5
    CABasicAnimation *waveAnimationCompelted = [CABasicAnimation animationWithKeyPath:@"path"];
    waveAnimationCompelted.fromValue = (__bridge id _Nullable)(self.wavePath4.CGPath);
    waveAnimationCompelted.toValue = (__bridge id _Nullable)(self.wavePathStarting.CGPath);
    waveAnimationCompelted.duration = KAnimationDuration;
    waveAnimationCompelted.beginTime = waveAnimationLow.beginTime + waveAnimationLow.duration;
    //    6
    CAAnimationGroup *animationGroup      = [[CAAnimationGroup alloc] init];
    animationGroup.delegate               = self;
    [animationGroup setValue:@"group" forKey:@"name"];
    animationGroup.animations             = \
  @[waveAnimationStart,waveAnimation1,waveAnimation2,waveAnimationLow];
    animationGroup.duration               = waveAnimationLow.beginTime + waveAnimationLow.duration;
    animationGroup.fillMode               = kCAFillModeForwards;
    animationGroup.removedOnCompletion    = NO;
    [layer addAnimation:animationGroup forKey:nil];
    
    self.allAnimationDuration             = animationGroup.duration;
    
}

接下来就是最实际的效果,让进度圆圈同步下载的进度,由于是逆时针所以设置strokeStart

-(void)setProgress:(CGFloat)progress {
   _progress = progress;
   self.progressShapeLayer.strokeStart   = 1-progress;
   if (progress >= 1) {
       [self end];
   }
}

复位

最后全部事情完成,如果想恢复到一开始的状态
1、进度条消失
进度条我这里并不是让他真的消失,而是让他宽度变为0
2、圆点变为竖线
3、箭头出现
4、移除波浪

/**
 复位
 */
-(void)reset {
    //变更状态
    self.state  = AIDownloadButtonNone;
    
    [self.pointShapeLayer removeAllAnimations];
    [self scaleAnimationWithLayer:self.progressLabel.layer fromValue:1. toValue:.1];
    [self opacityAnimationWithLayer:self.progressLabel.layer fromValue:1. toValue:0];
    self.progressShapeLayer.strokeStart     = 1;
    self.progress                           = 0.;
    self.state                              = AIDownloadButtonNone;
    //进度消失
    POPBasicAnimation   *progressAnimation  = [POPBasicAnimation animationWithPropertyNamed:kPOPShapeLayerLineWidth];
    progressAnimation.toValue               = @0.;
    progressAnimation.duration              = .3;
    [self.progressShapeLayer pop_addAnimation:progressAnimation forKey:nil];
    //点变成竖线
    UIBezierPath    *pointPath      = [UIBezierPath bezierPath];
    [pointPath moveToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.25)];
    [pointPath addLineToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.75 - self.arrowShapeLayer.lineWidth)];
    CABasicAnimation    *pointToLineAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pointToLineAnimation.toValue              = (__bridge id _Nullable)(pointPath.CGPath);
    pointToLineAnimation.duration             = .3;
    pointToLineAnimation.removedOnCompletion  = NO;
    pointToLineAnimation.fillMode             = kCAFillModeForwards;
    [self.pointShapeLayer addAnimation:pointToLineAnimation forKey:nil];
    //移除波浪
    [self.waveLayer removeFromSuperlayer];
    //箭头
    self.arrowShapeLayer.opacity    = 1.;
    UIBezierPath    *arrowPath      = [UIBezierPath bezierPath];
    [arrowPath moveToPoint: CGPointMake(self.ai_middleX * .75, self.ai_height *(0.25 + .5 * 0.6))];
    [arrowPath addLineToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.75)];
    [arrowPath addLineToPoint: CGPointMake(self.ai_middleX * 1.25, self.ai_height *(0.25 + .5 * 0.6))];
    CABasicAnimation    *arrowAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    arrowAnimation.toValue              = (__bridge id _Nullable)(arrowPath.CGPath);
    arrowAnimation.duration             = .3;
    arrowAnimation.removedOnCompletion  = NO;
    arrowAnimation.fillMode             = kCAFillModeForwards;
    [self.arrowShapeLayer addAnimation:arrowAnimation forKey:nil];
    
}

如果对基础动画使用有不太明白的可以看下我另一篇文章登录动画也可以在GitHub上查看源码,你的star是我最大的支持

源码位置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容