添加音频动画
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
@property(nonatomic, assign) float currentRMS;
|
@property(nonatomic, assign) float currentRMS;
|
||||||
@property(nonatomic, assign) float targetRMS;
|
@property(nonatomic, assign) float targetRMS;
|
||||||
@property(nonatomic, assign) BOOL isAnimating;
|
@property(nonatomic, assign) BOOL isAnimating;
|
||||||
|
@property(nonatomic, assign) NSInteger debugFrameCount;
|
||||||
|
@property(nonatomic, assign) CGSize lastLayoutSize;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation KBAiWaveformView
|
@implementation KBAiWaveformView
|
||||||
@@ -49,6 +51,11 @@
|
|||||||
|
|
||||||
- (void)layoutSubviews {
|
- (void)layoutSubviews {
|
||||||
[super layoutSubviews];
|
[super layoutSubviews];
|
||||||
|
if (CGSizeEqualToSize(self.lastLayoutSize, self.bounds.size) &&
|
||||||
|
self.barLayers.count == self.barCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.lastLayoutSize = self.bounds.size;
|
||||||
[self setupBars];
|
[self setupBars];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +80,7 @@
|
|||||||
barLayer.cornerRadius = self.barWidth / 2;
|
barLayer.cornerRadius = self.barWidth / 2;
|
||||||
|
|
||||||
CGFloat x = startX + i * (self.barWidth + self.barSpacing);
|
CGFloat x = startX + i * (self.barWidth + self.barSpacing);
|
||||||
CGFloat height = minHeight;
|
CGFloat height = maxHeight; // 统一用满高,后续用 transform.scale.y 控制高度
|
||||||
if (self.barHeightPattern.count > i) {
|
if (self.barHeightPattern.count > i) {
|
||||||
CGFloat base = [self.barHeightPattern[i] floatValue];
|
CGFloat base = [self.barHeightPattern[i] floatValue];
|
||||||
base = MIN(MAX(base, 0.15), 0.9);
|
base = MIN(MAX(base, 0.15), 0.9);
|
||||||
@@ -81,7 +88,11 @@
|
|||||||
}
|
}
|
||||||
CGFloat y = (maxHeight - height) / 2;
|
CGFloat y = (maxHeight - height) / 2;
|
||||||
|
|
||||||
barLayer.frame = CGRectMake(x, y, self.barWidth, height);
|
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;
|
barLayer.backgroundColor = self.waveColor.CGColor;
|
||||||
|
|
||||||
[self.layer addSublayer:barLayer];
|
[self.layer addSublayer:barLayer];
|
||||||
@@ -94,24 +105,42 @@
|
|||||||
|
|
||||||
- (void)updateWithRMS:(float)rms {
|
- (void)updateWithRMS:(float)rms {
|
||||||
self.targetRMS = MIN(MAX(rms, 0), 1);
|
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 {
|
- (void)startIdleAnimation {
|
||||||
if (self.isAnimating)
|
NSLog(@"[KBAiWaveformView] startIdleAnimation (animating=%d, bars=%ld, size=%@)",
|
||||||
|
self.isAnimating, (long)self.barLayers.count, NSStringFromCGRect(self.bounds));
|
||||||
|
if (self.isAnimating && self.displayLink) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.isAnimating = YES;
|
self.isAnimating = YES;
|
||||||
self.displayLink =
|
self.debugFrameCount = 0;
|
||||||
[CADisplayLink displayLinkWithTarget:self
|
[self.displayLink invalidate];
|
||||||
selector:@selector(updateAnimation)];
|
self.displayLink = [CADisplayLink displayLinkWithTarget:self
|
||||||
|
selector:@selector(updateAnimation)];
|
||||||
|
if (@available(iOS 10.0, *)) {
|
||||||
|
self.displayLink.preferredFramesPerSecond = 60;
|
||||||
|
}
|
||||||
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
|
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
|
||||||
forMode:NSRunLoopCommonModes];
|
forMode:NSRunLoopCommonModes];
|
||||||
|
NSLog(@"[KBAiWaveformView] displayLink started");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stopAnimation {
|
- (void)stopAnimation {
|
||||||
|
NSLog(@"[KBAiWaveformView] stopAnimation (bars=%ld)", (long)self.barLayers.count);
|
||||||
self.isAnimating = NO;
|
self.isAnimating = NO;
|
||||||
[self.displayLink invalidate];
|
[self.displayLink invalidate];
|
||||||
self.displayLink = nil;
|
self.displayLink = nil;
|
||||||
|
for (CAShapeLayer *layer in self.barLayers) {
|
||||||
|
[layer removeAnimationForKey:@"kb_idle_scale"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reset {
|
- (void)reset {
|
||||||
@@ -123,8 +152,16 @@
|
|||||||
#pragma mark - Animation
|
#pragma mark - Animation
|
||||||
|
|
||||||
- (void)updateAnimation {
|
- (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
|
// 平滑过渡到目标 RMS
|
||||||
CGFloat smoothing = 0.3;
|
CGFloat smoothing = 0.65;
|
||||||
self.currentRMS =
|
self.currentRMS =
|
||||||
self.currentRMS + (self.targetRMS - self.currentRMS) * smoothing;
|
self.currentRMS + (self.targetRMS - self.currentRMS) * smoothing;
|
||||||
|
|
||||||
@@ -144,25 +181,26 @@
|
|||||||
|
|
||||||
// 添加基于时间的波动效果
|
// 添加基于时间的波动效果
|
||||||
CGFloat phase = (CGFloat)i / self.barLayers.count * M_PI * 2;
|
CGFloat phase = (CGFloat)i / self.barLayers.count * M_PI * 2;
|
||||||
CGFloat wave = sin(time * 3 + phase) * 0.3 + 0.7; // 0.4 - 1.0
|
CGFloat wave = sin(time * 8 + phase) * 0.3 + 0.7; // 0.4 - 1.0
|
||||||
|
|
||||||
CGFloat baseFactor = 0.2;
|
CGFloat baseFactor = 0.2;
|
||||||
if (self.barHeightPattern.count > i) {
|
if (self.barHeightPattern.count > i) {
|
||||||
baseFactor = [self.barHeightPattern[i] floatValue];
|
baseFactor = [self.barHeightPattern[i] floatValue];
|
||||||
baseFactor = MIN(MAX(baseFactor, 0.15), 0.9);
|
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 波动
|
// 计算高度:基准高度 + 随 RMS 波动
|
||||||
CGFloat dynamicFactor = rms * (0.35 + 0.15 * wave); // 0.35~0.5
|
CGFloat dynamicFactor = rms * (0.45 + 0.20 * wave); // 0.45~0.65
|
||||||
CGFloat heightFactor = MIN(1.0, baseFactor + dynamicFactor * (1.0 - baseFactor));
|
CGFloat heightFactor = MIN(1.0, baseWave + dynamicFactor * (1.0 - baseWave));
|
||||||
CGFloat height = maxHeight * heightFactor;
|
CGFloat height = maxHeight * heightFactor;
|
||||||
height = MAX(minHeight, MIN(maxHeight, height));
|
height = MAX(minHeight, MIN(maxHeight, height));
|
||||||
|
CGFloat scale = height / maxHeight;
|
||||||
// 更新位置
|
|
||||||
CGFloat y = (maxHeight - height) / 2;
|
|
||||||
|
|
||||||
[CATransaction begin];
|
[CATransaction begin];
|
||||||
[CATransaction setDisableActions:YES];
|
[CATransaction setDisableActions:YES];
|
||||||
layer.frame = CGRectMake(layer.frame.origin.x, y, self.barWidth, height);
|
layer.transform = CATransform3DMakeScale(1, scale, 1);
|
||||||
[CATransaction commit];
|
[CATransaction commit];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,7 @@
|
|||||||
|
|
||||||
- (void)setInputState:(KBVoiceInputBarState)inputState {
|
- (void)setInputState:(KBVoiceInputBarState)inputState {
|
||||||
_inputState = inputState;
|
_inputState = inputState;
|
||||||
|
NSLog(@"[KBVoiceInputBar] setInputState: %ld", (long)inputState);
|
||||||
self.textInputView.hidden = (inputState != KBVoiceInputBarStateText);
|
self.textInputView.hidden = (inputState != KBVoiceInputBarStateText);
|
||||||
self.voiceInputView.hidden = (inputState != KBVoiceInputBarStateVoice);
|
self.voiceInputView.hidden = (inputState != KBVoiceInputBarStateVoice);
|
||||||
self.recordingView.hidden = (inputState != KBVoiceInputBarStateRecording);
|
self.recordingView.hidden = (inputState != KBVoiceInputBarStateRecording);
|
||||||
@@ -256,6 +257,7 @@
|
|||||||
[self.recordButton updateVolumeRMS:rms];
|
[self.recordButton updateVolumeRMS:rms];
|
||||||
if (self.inputState == KBVoiceInputBarStateRecording) {
|
if (self.inputState == KBVoiceInputBarStateRecording) {
|
||||||
CGFloat safeRMS = MAX(rms, 0.6f);
|
CGFloat safeRMS = MAX(rms, 0.6f);
|
||||||
|
NSLog(@"[KBVoiceInputBar] updateVolumeRMS: %.3f (safe=%.3f)", rms, safeRMS);
|
||||||
[self.leftWaveformView updateWithRMS:safeRMS];
|
[self.leftWaveformView updateWithRMS:safeRMS];
|
||||||
[self.rightWaveformView updateWithRMS:safeRMS];
|
[self.rightWaveformView updateWithRMS:safeRMS];
|
||||||
}
|
}
|
||||||
@@ -533,6 +535,20 @@
|
|||||||
#pragma mark - Recording Wave
|
#pragma mark - Recording Wave
|
||||||
|
|
||||||
- (void)startRecordingWaveAnimationIfNeeded {
|
- (void)startRecordingWaveAnimationIfNeeded {
|
||||||
|
NSLog(@"[KBVoiceInputBar] startRecordingWaveAnimationIfNeeded");
|
||||||
|
self.leftWaveformView.hidden = NO;
|
||||||
|
self.rightWaveformView.hidden = NO;
|
||||||
|
[self.inputContainer setNeedsLayout];
|
||||||
|
[self.recordingView setNeedsLayout];
|
||||||
|
[self.inputContainer layoutIfNeeded];
|
||||||
|
[self.recordingView layoutIfNeeded];
|
||||||
|
[self.leftWaveformView setNeedsLayout];
|
||||||
|
[self.rightWaveformView setNeedsLayout];
|
||||||
|
[self.leftWaveformView layoutIfNeeded];
|
||||||
|
[self.rightWaveformView layoutIfNeeded];
|
||||||
|
NSLog(@"[KBVoiceInputBar] waveform frames L=%@ R=%@",
|
||||||
|
NSStringFromCGRect(self.leftWaveformView.frame),
|
||||||
|
NSStringFromCGRect(self.rightWaveformView.frame));
|
||||||
[self.leftWaveformView startIdleAnimation];
|
[self.leftWaveformView startIdleAnimation];
|
||||||
[self.rightWaveformView startIdleAnimation];
|
[self.rightWaveformView startIdleAnimation];
|
||||||
[self.leftWaveformView updateWithRMS:0.7f];
|
[self.leftWaveformView updateWithRMS:0.7f];
|
||||||
@@ -540,10 +556,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)stopRecordingWaveAnimation {
|
- (void)stopRecordingWaveAnimation {
|
||||||
|
NSLog(@"[KBVoiceInputBar] stopRecordingWaveAnimation");
|
||||||
[self.leftWaveformView stopAnimation];
|
[self.leftWaveformView stopAnimation];
|
||||||
[self.rightWaveformView stopAnimation];
|
[self.rightWaveformView stopAnimation];
|
||||||
[self.leftWaveformView reset];
|
[self.leftWaveformView reset];
|
||||||
[self.rightWaveformView reset];
|
[self.rightWaveformView reset];
|
||||||
|
self.leftWaveformView.hidden = YES;
|
||||||
|
self.rightWaveformView.hidden = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user