YQDownloadButton---带有波浪效果和震动波效果的动画

前言

最近在看《A-GUIDE-TO-iOS-ANIMATION》,这本书挺不错的,恰好旁边安卓同学过年时写了一个有点好看的按钮,自己就按照这个效果去写来玩玩~

效果图

screencast.gif

动画主要有水波动画、震动波动画组成,以下分析主要实现,具体实现可以看源码,源码地址在文章最后

波浪效果实现

参考网上示例,主要有三种方式:

  1. 切一张波浪形的图片,进行不断循环的位置变化的动画
  2. 通过CAShapeLayer绘制波浪曲线,并不断改变垂直位置,来达到水面波动并上升的动画效果
  3. 同时绘制两个波形图,让它们彼此错开,下层的波形图层设置一定的透明度,两层水波交替波动时就达到波浪的视觉效果

我这里采取了第三种方法,利用两层水波交替波动达到波浪效果,如下代码所示
实现代码

#pragma mark - drawRect
- (void)drawRect:(CGRect)rect {
    if (_borderPath) {
        if (_border_fillColor) {
            [_border_fillColor setFill];
            [_borderPath fill];
        }
        
        if (_border_strokeColor) {
            [_border_strokeColor setStroke];
            [_borderPath stroke];
        }
        
        [_borderPath addClip];
    }
    //同时绘制两个波形图
    [self drawWaveColor:_topColor offsetx:0 offsety:0];
    [self drawWaveColor:_bottomColor offsetx:_wave_h_distance offsety:_wave_v_distance];
    
}

绘制波浪方法:

#pragma mark - draw wave
- (void)drawWaveColor:(UIColor *)color offsetx:(CGFloat)offsetx offsety:(CGFloat)offsety {
    //波浪动画,所以进度的实际操作范围是,多加上两个振幅的高度,到达设置进度的位置y坐标
    CGFloat end_offY = (1-_progress) * (self.frame.size.height + 2* _wave_Amplitude);
        if (_wave_offsety != end_offY) {
            if (end_offY < _wave_offsety) {//上升
                _wave_offsety = MAX(_wave_offsety-=(_wave_offsety - end_offY)*_offsety_scale, end_offY);
            } else {
                _wave_offsety = MIN(_wave_offsety+=(end_offY-_wave_offsety)*_offsety_scale, end_offY);
            }
        }
    
    UIBezierPath *wave = [UIBezierPath bezierPath];
    for (float next_x= 0.f; next_x <= self.frame.size.width; next_x ++) {
        //正弦函数
        CGFloat next_y = _wave_Amplitude * sin(_wave_Cycle*next_x + _wave_offsetx + offsetx/self.bounds.size.width*2*M_PI) + _wave_offsety + offsety;
        if (next_x == 0) {
            [wave moveToPoint:CGPointMake(next_x, next_y - _wave_Amplitude)];
        } else {
            [wave addLineToPoint:CGPointMake(next_x, next_y - _wave_Amplitude)];
        }
    }
    [wave addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height)];
    [wave addLineToPoint:CGPointMake(0, self.bounds.size.height)];
    [color set];
    [wave fill];
}

前面if语句的代码可以使得progress为1.0后,水波上升不是立即上升,而是通过一个动画缓慢上升;后面代码通过正弦曲线公式y=Asin(ωx+φ)+k,绘制在每个时刻的波形图以达到水波动画的效果。

  • _wave_Amplitude,波纹振幅,A

  • _wave_Cycle,波纹周期,T = 2π/ω

  • offsetX,波浪x位移,φ

  • _wave_offsety,当前波浪偏移高度,k

震动波效果

震动效果先是让整体是如图变小,然后瞬间变大并带有弹簧效果,同时让震动波视图逐渐变大,并修改其alpha值,所有效果都可以通过UIView Animation来实现

实现代码

#pragma mark - 结束动画
- (void)endAnimation {
    
    self.layer.borderColor = [UIColor clearColor].CGColor;
    _vibrationWaveView.backgroundColor = [UIColor colorWithRed:79/255.0 green:240/255.0 blue:255/255.0 alpha:1];
    // 为了不影响缩小后的效果,提前将振动波视图缩小
    _vibrationWaveView.transform = CGAffineTransformMakeScale(.9, .9);
    // 视图缩小动画
    [UIView animateWithDuration: .9
                          delay: 1.2
                        options: UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         _vibrationWaveView.transform = CGAffineTransformMakeScale(.9, .9);
                         
                     }
                     completion:^(BOOL finished) {
                         // 震动波效果
                         [UIView animateWithDuration: 2.1
                                          animations:^{
                                              _vibrationWaveView.transform = CGAffineTransformMakeScale(3, 3);
                                              _vibrationWaveView.alpha = 0;
                                          }
                                          completion:^(BOOL finished) {
                                              [_vibrationWaveView removeFromSuperview];
                                          }];
                         //弹簧震动效果
                         [UIView animateWithDuration: 1.f
                                               delay: 0.2
                              usingSpringWithDamping: 0.4
                               initialSpringVelocity: 0
                                             options: UIViewAnimationOptionCurveEaseInOut
                                          animations:^{
                                              // 视图瞬间增大一倍
                                              self.transform = CGAffineTransformMakeScale(1.8, 1.8);
                                              self.transform = CGAffineTransformMakeScale(1.0, 1.0);
                                          }
                                          completion:^(BOOL finished) {
                                              
                                          }];
                     }];
    
    
}

打钩动画

打勾动画的思路是给一个CAShapeLayer指定一个勾形的path,然后进行strokeEnd的动画,strokeEnd不是CALayer的属性,而是其子类CAShapeLayer的一个特有属性。所以我们先要创建一个CAShapeLayer,还有一个必须赋值的path,然后再进行绘制

实现代码

#pragma mark - 打钩动画
-(void)checkAnimation{
    
    CAShapeLayer *checkLayer = [CAShapeLayer layer];
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    CGRect rectInCircle = CGRectInset(self.bounds, self.bounds.size.width*(1-1/sqrt(2.0))/2, self.bounds.size.width*(1-1/sqrt(2.0))/2);
    [path moveToPoint:CGPointMake(rectInCircle.origin.x + rectInCircle.size.width/9, rectInCircle.origin.y + rectInCircle.size.height*2/3)];
    [path addLineToPoint:CGPointMake(rectInCircle.origin.x + rectInCircle.size.width/3,rectInCircle.origin.y + rectInCircle.size.height*9/10)];
    [path addLineToPoint:CGPointMake(rectInCircle.origin.x + rectInCircle.size.width*8/10, rectInCircle.origin.y + rectInCircle.size.height*2/10)];
    
    checkLayer.path = path.CGPath;
    checkLayer.fillColor = [UIColor clearColor].CGColor;
    checkLayer.strokeColor = [UIColor whiteColor].CGColor;
    checkLayer.lineWidth = 10.0;
    checkLayer.lineCap = kCALineCapRound; //线条拐角
    checkLayer.lineJoin = kCALineJoinRound; //终点处理
    [self.layer addSublayer:checkLayer];
    
    CABasicAnimation *checkAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    checkAnimation.duration = 0.3f;
    checkAnimation.fromValue = @(0.0f);
    checkAnimation.toValue = @(1.0f);
    checkAnimation.delegate = self;
    //这个可以起到判断不同anim的方法:KVO
    [checkAnimation setValue:@"checkAnimation" forKey:@"animationName"];
    [checkLayer addAnimation:checkAnimation forKey:nil];
    
}

触发方法

#pragma mark - 代理方法,开始波浪动画
- (void)startDownload{
    [self setProgress:1.0];
}

这是个代理方法,刚开始点击箭头后就被调用,水波上升的动画执行时间,就是_progress被设置为1的过程,水波上升的最终高度可以通过_progress来控制

水波动画控制

实现代码

#pragma mark - animation
- (void)changeoff {
    _wave_offsetx += _wave_move_width*_wave_scale;
    [self setNeedsDisplay];
    
    //偏移较小的时候加速,节省时间
    if (_wave_offsety < 5.0) {
        _offsety_scale += 1.0;
    }
    
    //水满了,做打钩动画和震荡扩散动画并停止波浪动画
    if (_wave_offsety <= 0.01) {
        [self checkAnimation];
        [self endAnimation];
        [self stopWave];
        NSLog(@"finish");
    }
}

#pragma mark - start
- (void)startWave {
    if (!_waveDisplaylink) {
        //启动同步渲染绘制波纹
        _waveDisplaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeoff)];
        [_waveDisplaylink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
}

#pragma mark - stop
- (void)stopWave {
    [_waveDisplaylink invalidate];
    _waveDisplaylink = nil;
}

diaplayLink在屏幕刷新过程中不断调用changeoff方法,若水波上升到一定高度,也就是y轴偏移较小的时候,加快上升速率,待到水填满圆形容器的时候,执行打钩动画、震动波动画并停止波浪动画

小结

完成这个动画后,收获还是蛮大的,自己对于动画的理解更加深刻了,争取以后学习更多的动画知识。

具体实现看源码:Github地址,喜欢的给个star~

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,611评论 4 59
  • 广州是一个美丽的城市,有苦,有甜,也有故事,虽然我们每天都很累,但我们为了梦想仍不顾一切的阻挡,今年是我来广州的第...
    小刀霸阅读 273评论 0 0
  • 水母(英文:Jellyfish)是一种低等的海产无脊椎浮游动物,也是一种神奇的动物。 水母的出现比恐龙还早...
    TaylorLoverrr阅读 2,104评论 1 4