Files
keyboard/keyBoard/Class/AiTalk/V/KBAiWaveformView.m
2026-02-02 19:49:56 +08:00

213 lines
6.4 KiB
Objective-C

//
// KBAiWaveformView.m
// keyBoard
//
// Created by Mac on 2026/1/15.
//
#import "KBAiWaveformView.h"
@interface KBAiWaveformView ()
@property(nonatomic, strong) NSMutableArray<CAShapeLayer *> *barLayers;
@property(nonatomic, strong) NSMutableArray<NSNumber *> *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