From 481afc803d4d1579d104b246bc70b6082290501d Mon Sep 17 00:00:00 2001 From: lanterngamescn Date: Sat, 27 Jun 2026 17:06:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=BD=95=E9=9F=B3):=20RecordingOverlay=20?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E6=B5=AE=E5=B1=82(=E6=B3=A2=E5=BD=A2/?= =?UTF-8?q?=E4=B8=8A=E6=BB=91=E5=8F=96=E6=B6=88/=E5=A4=AA=E7=9F=AD,?= =?UTF-8?q?=E8=A7=A6=E6=91=B8=E7=A9=BF=E9=80=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 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 --- .../main/ets/components/RecordingOverlay.ets | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 entry/src/main/ets/components/RecordingOverlay.ets diff --git a/entry/src/main/ets/components/RecordingOverlay.ets b/entry/src/main/ets/components/RecordingOverlay.ets new file mode 100644 index 0000000..38ba130 --- /dev/null +++ b/entry/src/main/ets/components/RecordingOverlay.ets @@ -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) + } +}