feat(录音): RecordingOverlay 录音浮层(波形/上滑取消/太短,触摸穿透)

新增 entry/src/main/ets/components/RecordingOverlay.ets:
- @Prop state: 'recording'|'cancel'|'tooShort' 三态展示
- recording 状态:7根波形柱 setInterval(120ms) 随机高度跳动动画
- cancel 状态:红底(#C0392B) + '✕'
- tooShort 状态:暗底 + '!'
- hitTestBehavior(HitTestMode.None) 触摸穿透到下方 Web,由容器 Web.onTouch 感知手势
- aboutToAppear/aboutToDisappear 正确管理 timer 生命周期

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-27 17:06:21 +08:00
co-authored by Claude Sonnet 4.6
parent 0a9ba6c8be
commit 481afc803d
@@ -0,0 +1,75 @@
/**
* 录音浮层(设计 §10,纯展示,仿微信"按住说话")。状态由容器手势驱动,自身不处理触摸
* HitTestMode.None 让触摸穿透到下方 Web,由 Web.onTouch 观察手势)。
* - recording:跳动波形 + "手指上滑,取消发送"
* - cancel:红底 + "✕" + "松开手指,取消发送"
* - tooShort:黑底 + "!" + "说话时间太短"
*/
@Component
export struct RecordingOverlay {
/** 'recording' | 'cancel' | 'tooShort' */
@Prop state: string;
@State private bars: number[] = [0.4, 0.7, 0.5, 0.9, 0.6, 0.8, 0.45];
private timer: number = -1;
aboutToAppear(): void {
this.timer = setInterval(() => {
this.bars = this.bars.map(() => 0.25 + 0.75 * Math.random());
}, 120);
}
aboutToDisappear(): void {
if (this.timer >= 0) {
clearInterval(this.timer);
this.timer = -1;
}
}
private hintText(): string {
if (this.state === 'cancel') {
return '松开手指,取消发送';
}
if (this.state === 'tooShort') {
return '说话时间太短';
}
return '手指上滑,取消发送';
}
build() {
Column() {
Column({ space: 14 }) {
if (this.state === 'recording') {
Row({ space: 4 }) {
ForEach(this.bars, (h: number, i: number) => {
Column()
.width(5)
.height(48 * h)
.borderRadius(3)
.backgroundColor('#FFFFFF')
}, (h: number, i: number) => i.toString())
}
.height(52)
.alignItems(VerticalAlign.Center)
} else {
Text(this.state === 'cancel' ? '✕' : '!')
.fontSize(42)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
Text(this.hintText())
.fontSize(14)
.fontColor('#FFFFFF')
}
.width(168)
.height(168)
.justifyContent(FlexAlign.Center)
.borderRadius(18)
.backgroundColor(this.state === 'cancel' ? '#C0392B' : 'rgba(0, 0, 0, 0.78)')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.hitTestBehavior(HitTestMode.None)
}
}