adolsai's echo

一个录音波浪动画

根据音量大小变化效果:

99

实现

  1. 使用三角函数计算每一个点(x, y)位置;
  2. 使用UIBezierPath连接这些点;
  3. 把path赋值给CAShapeLayer;

三角函数

假设 y = Asin(ωx+φ)+ C
其中:
A 表示振幅,也就是使用这个变量来调整波浪的最大的高度;
ω 与周期相关,周期 T = 2 * pi / ω ,这个变量用来调整同宽度内显示的波浪的数量;
φ 表示波浪横向的偏移,也就是使用这个变量来调整波浪的流动;
C 表示波浪纵向偏移的位置。

//waveMax 波动幅度
//xScale x轴缩放
//yScale y轴缩放
//offset x轴偏移
y = waveMax * yScale * sin(xScale * x + offset);

//两边还要控制一下收敛回到x轴, 需要乘多个sin函数
y = sin((M_PI/width) * x) * waveMax * (yScale * sin(xScale * x + offset));

代码:

AnimationView.h

@interface AnimationView : UIView

@property (nonatomic, readwrite, assign) CGFloat vol; //音量大小 0.0f ~ 1.0f

@end

AnimationView.m


@interface AnimationView ()

@property (nonatomic, readwrite, assign) CGFloat lastVol; //具体应用到view上的音量大小

@property (nonatomic, readwrite, assign) CGFloat offset; //当前曲线x偏移


@property (nonatomic, readwrite, strong) UIBezierPath *path; //主体线条路径

@property (nonatomic, readwrite, strong) UIBezierPath *backPath1; //背景线条路径1

@property (nonatomic, readwrite, strong) UIBezierPath *backPath2; //背景线条路径2


@property (nonatomic, readwrite, strong) CAShapeLayer *shapeLayer; //主体线条Layer

@property (nonatomic, readwrite, strong) CAShapeLayer *backLayer1; //背景线条Layer1

@property (nonatomic, readwrite, strong) CAShapeLayer *backLayer2; //背景线条Layer2


@property (nonatomic, readwrite, strong) CADisplayLink *link; //刷新计时器


@end

@implementation AnimationView

#pragma mark - Life Cycle

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    
    if (self){
        self.lastVol = 0.5f;
        
        self.backPath1 = [UIBezierPath bezierPath];
        self.backPath2 = [UIBezierPath bezierPath];
        self.path = [UIBezierPath bezierPath];
        
        [self.layer addSublayer:self.backLayer1];
        [self.layer addSublayer:self.backLayer2];
        [self.layer addSublayer:self.shapeLayer];
        
        self.link = [CADisplayLink displayLinkWithTarget:self
                                                selector:@selector(updateShapeLayer)];
        [self.link addToRunLoop:[NSRunLoop currentRunLoop]
                        forMode:NSRunLoopCommonModes];
    }
    
    return self;
}

- (void)dealloc{
    [self.link invalidate];
}

#pragma mark - Private Methods

- (void)updateShapeLayer{
    
    CGFloat scale = 2.3f;
    
    //防止vol值变化太快导致动画跳动
    CGFloat animatedVol = self.lastVol;
    if (self.vol > self.lastVol){
        animatedVol *= 1.04f;
    }else{
        animatedVol *= 0.95f;
    }
    
    self.lastVol = animatedVol;
    
    CGFloat height = CGRectGetHeight(self.bounds);
    CGFloat width = CGRectGetWidth(self.bounds);
    
    [self.path removeAllPoints];
    [self.backPath1 removeAllPoints];
    [self.backPath2 removeAllPoints];
    
    //起点
    [self.path moveToPoint:CGPointMake(0.0f, height / 2.0f)];
    [self.backPath1 moveToPoint:CGPointMake(0.0f, height / 2.0f)];
    [self.backPath2 moveToPoint:CGPointMake(0.0f, height / 2.0f)];
    
    CGFloat xScale = 1 / 30.0f;
    CGFloat yScale = 20;
    CGFloat offset = self.offset;
    CGFloat yPadding = height / 2.0f;
    
    //计算每一个点
    for (CGFloat x = 0.0f; x < width; x += [UIScreen mainScreen].scale){
        
        CGFloat y = (0.4 + animatedVol * scale) * sin((M_PI/width) * x) * (yScale * sin(xScale * x + offset)) + yPadding;
        
        [self.path addLineToPoint:CGPointMake(x, y)];
        
        
        CGFloat y2 = (0.4 + animatedVol * scale) * sin((M_PI/width) * x) * (10 * sin(xScale * x + offset + 1.1)) + yPadding;
        
        [self.backPath1 addLineToPoint:CGPointMake(x, y2)];
        
        
        CGFloat y3 = - (0.4 + animatedVol * scale) * sin((M_PI/width) * x) * (10 * sin(xScale * x + offset + 0.3)) + yPadding;
        
        [self.backPath2 addLineToPoint:CGPointMake(x, y3)];
    }
    
    //终点
    [self.path addLineToPoint:CGPointMake(width, height / 2.0f)];
    [self.backPath1 addLineToPoint:CGPointMake(width, height / 2.0f)];
    [self.backPath2 addLineToPoint:CGPointMake(width, height / 2.0f)];
    
    [self.shapeLayer setPath:self.path.CGPath];
    
    [self.backLayer1 setPath:self.backPath1.CGPath];
    
    [self.backLayer2 setPath:self.backPath2.CGPath];
    
    //波浪线移动
    self.offset -= 0.28;
    if (self.offset < -60 * M_PI * 2) {
        self.offset = 0;
    }
}

#pragma mark Getters & Setters

- (CAShapeLayer *)shapeLayer{
    if (!_shapeLayer){
        _shapeLayer = [CAShapeLayer layer];
        _shapeLayer.fillColor = [UIColor clearColor].CGColor;
        _shapeLayer.lineWidth = 1.5f;
        _shapeLayer.strokeColor  = [[UIColor colorWithRed:0.325 green:0.60 blue:0.969 alpha:1.0] CGColor];
    }
    
    return _shapeLayer;
}

- (CAShapeLayer *)backLayer1{
    if (!_backLayer1){
        _backLayer1 = [CAShapeLayer layer];
        _backLayer1.fillColor = [UIColor clearColor].CGColor;
        _backLayer1.lineWidth = 1.0f;
        _backLayer1.strokeColor  = [[UIColor colorWithRed:0.824 green:0.902 blue:0.992 alpha:1.0] CGColor];
    }
    
    return _backLayer1;
}

- (CAShapeLayer *)backLayer2{
    if (!_backLayer2){
        _backLayer2 = [CAShapeLayer layer];
        _backLayer2.fillColor = [UIColor clearColor].CGColor;
        _backLayer2.lineWidth = 1.0f;
        _backLayer2.strokeColor  = [[UIColor colorWithRed:0.824 green:0.902 blue:0.992 alpha:1.0] CGColor];
    }
    
    return _backLayer2;
}

@end

参考: iOS 怎么制作出动感的波浪动画

标签:#ios #wave #animtion