// // KBAiWaveformView.m // keyBoard // // Created by Mac on 2026/1/15. // #import "KBAiWaveformView.h" @interface KBAiWaveformView () @property(nonatomic, strong) NSMutableArray *barLayers; @property(nonatomic, strong) NSMutableArray *barHeights; @property(nonatomic, strong) CADisplayLink *displayLink; @property(nonatomic, assign) float currentRMS; @property(nonatomic, assign) float targetRMS; @property(nonatomic, assign) BOOL isAnimating; @property(nonatomic, assign) NSInteger debugFrameCount; @property(nonatomic, assign) CGSize lastLayoutSize; @end @implementation KBAiWaveformView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { [self setup]; } return self; } - (void)setup { _waveColor = [UIColor systemBlueColor]; _barCount = 5; _barWidth = 4; _barSpacing = 3; _barLayers = [[NSMutableArray alloc] init]; _barHeights = [[NSMutableArray alloc] init]; _currentRMS = 0; _targetRMS = 0; self.backgroundColor = [UIColor clearColor]; } - (void)layoutSubviews { [super layoutSubviews]; if (CGSizeEqualToSize(self.lastLayoutSize, self.bounds.size) && self.barLayers.count == self.barCount) { return; } self.lastLayoutSize = self.bounds.size; [self setupBars]; } - (void)setupBars { // 移除旧的图层 for (CAShapeLayer *layer in self.barLayers) { [layer removeFromSuperlayer]; } [self.barLayers removeAllObjects]; [self.barHeights removeAllObjects]; // 计算总宽度 CGFloat totalWidth = self.barCount * self.barWidth + (self.barCount - 1) * self.barSpacing; CGFloat startX = (self.bounds.size.width - totalWidth) / 2; CGFloat maxHeight = self.bounds.size.height; CGFloat minHeight = maxHeight * 0.2; for (NSInteger i = 0; i < self.barCount; i++) { CAShapeLayer *barLayer = [CAShapeLayer layer]; barLayer.fillColor = self.waveColor.CGColor; barLayer.cornerRadius = self.barWidth / 2; CGFloat x = startX + i * (self.barWidth + self.barSpacing); CGFloat height = maxHeight; // 统一用满高,后续用 transform.scale.y 控制高度 if (self.barHeightPattern.count > i) { CGFloat base = [self.barHeightPattern[i] floatValue]; base = MIN(MAX(base, 0.15), 0.9); height = MAX(minHeight, maxHeight * base); } CGFloat y = (maxHeight - height) / 2; barLayer.frame = CGRectMake(x, 0, self.barWidth, maxHeight); barLayer.anchorPoint = CGPointMake(0.5, 0.5); barLayer.position = CGPointMake(x + self.barWidth / 2, maxHeight / 2); CGFloat scale = height / maxHeight; barLayer.transform = CATransform3DMakeScale(1, scale, 1); barLayer.backgroundColor = self.waveColor.CGColor; [self.layer addSublayer:barLayer]; [self.barLayers addObject:barLayer]; [self.barHeights addObject:@(height)]; } } #pragma mark - Public Methods - (void)updateWithRMS:(float)rms { self.targetRMS = MIN(MAX(rms, 0), 1); NSLog(@"[KBAiWaveformView] updateWithRMS: %.3f, targetRMS=%.3f, barCount=%ld, size=%@", rms, self.targetRMS, (long)self.barLayers.count, NSStringFromCGRect(self.bounds)); if (!self.displayLink) { self.currentRMS = self.targetRMS; [self updateBarsWithRMS:self.currentRMS]; } } - (void)startIdleAnimation { NSLog(@"[KBAiWaveformView] startIdleAnimation (animating=%d, bars=%ld, size=%@)", self.isAnimating, (long)self.barLayers.count, NSStringFromCGRect(self.bounds)); if (self.isAnimating && self.displayLink) { return; } self.isAnimating = YES; self.debugFrameCount = 0; [self.displayLink invalidate]; self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimation)]; if (@available(iOS 10.0, *)) { self.displayLink.preferredFramesPerSecond = 60; } [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; NSLog(@"[KBAiWaveformView] displayLink started"); } - (void)stopAnimation { NSLog(@"[KBAiWaveformView] stopAnimation (bars=%ld)", (long)self.barLayers.count); self.isAnimating = NO; [self.displayLink invalidate]; self.displayLink = nil; for (CAShapeLayer *layer in self.barLayers) { [layer removeAnimationForKey:@"kb_idle_scale"]; } } - (void)reset { self.currentRMS = 0; self.targetRMS = 0; [self updateBarsWithRMS:0]; } #pragma mark - Animation - (void)updateAnimation { if (!self.isAnimating) { return; } self.debugFrameCount += 1; if (self.debugFrameCount % 30 == 0) { NSLog(@"[KBAiWaveformView] tick (target=%.3f, current=%.3f, bars=%ld)", self.targetRMS, self.currentRMS, (long)self.barLayers.count); } // 平滑过渡到目标 RMS CGFloat smoothing = 0.65; self.currentRMS = self.currentRMS + (self.targetRMS - self.currentRMS) * smoothing; [self updateBarsWithRMS:self.currentRMS]; } - (void)updateBarsWithRMS:(float)rms { CGFloat maxHeight = self.bounds.size.height; CGFloat minHeight = maxHeight * 0.2; CGFloat range = maxHeight - minHeight; // 为每个条添加略微不同的高度和相位 NSTimeInterval time = CACurrentMediaTime(); for (NSInteger i = 0; i < self.barLayers.count; i++) { CAShapeLayer *layer = self.barLayers[i]; // 添加基于时间的波动效果 CGFloat phase = (CGFloat)i / self.barLayers.count * M_PI * 2; CGFloat wave = sin(time * 8 + phase) * 0.3 + 0.7; // 0.4 - 1.0 CGFloat baseFactor = 0.2; if (self.barHeightPattern.count > i) { baseFactor = [self.barHeightPattern[i] floatValue]; baseFactor = MIN(MAX(baseFactor, 0.15), 0.9); } // 让基准高度也随时间轻微摆动(长按时更明显) CGFloat idleWave = sin(time * 10 + phase) * 0.25 + 0.85; // 0.6 - 1.1 CGFloat baseWave = MIN(MAX(baseFactor * idleWave, 0.15), 0.95); // 计算高度:基准高度 + 随 RMS 波动 CGFloat dynamicFactor = rms * (0.45 + 0.20 * wave); // 0.45~0.65 CGFloat heightFactor = MIN(1.0, baseWave + dynamicFactor * (1.0 - baseWave)); CGFloat height = maxHeight * heightFactor; height = MAX(minHeight, MIN(maxHeight, height)); CGFloat scale = height / maxHeight; [CATransaction begin]; [CATransaction setDisableActions:YES]; layer.transform = CATransform3DMakeScale(1, scale, 1); [CATransaction commit]; } } - (void)dealloc { [self stopAnimation]; } @end