import { CommonConstants } from '../common/constants/CommonConstants' import { normalizeHexColor, rgbaFromHex } from './spectrum/SpectrumRenderer' //正在播放的动画指示器 @Component export struct PlayingIndicator { @Prop @Watch('onActiveChange') isActive: boolean = false @Prop indicatorSize: number = 18 @Prop indicatorColor: string = '' @Prop marginRight: number = 0 @Prop marginTop: number = 0 @Prop marginBottom: number = 0 @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR private readonly renderSettings: RenderingContextSettings = new RenderingContextSettings(true) private readonly canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderSettings) private timerId: number = -1 private phase: number = 0 private canvasWidth: number = 0 private canvasHeight: number = 0 aboutToAppear(): void { this.updateAnimationState() this.drawFrame() } aboutToDisappear(): void { this.stopTimer() } onActiveChange(): void { this.updateAnimationState() this.drawFrame() } private updateAnimationState(): void { if (this.isActive) { this.startTimer() return } this.stopTimer() this.phase = 0 } private startTimer(): void { if (this.timerId !== -1) { return } this.timerId = setInterval(() => { this.phase += 0.58 if (this.phase > Math.PI * 2) { this.phase -= Math.PI * 2 } this.drawFrame() }, 88) } private stopTimer(): void { if (this.timerId === -1) { return } clearInterval(this.timerId) this.timerId = -1 } private drawFrame(): void { if (this.canvasWidth <= 0 || this.canvasHeight <= 0) { return } const ctx: CanvasRenderingContext2D = this.canvasContext ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) if (!this.isActive) { return } const colorSource: string = this.indicatorColor.length > 0 ? this.indicatorColor : this.themeColor const brandColor: string = normalizeHexColor(colorSource, CommonConstants.DEFAULT_THEME_COLOR) const barCount: number = 5 const barWidth: number = Math.max(1.0, Math.min(1.8, this.canvasWidth * 0.075)) const barGap: number = Math.max(1.0, Math.min(1.9, this.canvasWidth * 0.092)) const totalWidth: number = barWidth * barCount + barGap * (barCount - 1) const startX: number = (this.canvasWidth - totalWidth) / 2 const baseY: number = this.canvasHeight const minBarHeight: number = this.canvasHeight * 0.28 const maxBarHeight: number = this.canvasHeight * 0.84 for (let i = 0; i < barCount; i++) { const primaryWave: number = 0.5 + 0.5 * Math.sin(this.phase + i * 0.82) const secondaryWave: number = 0.5 + 0.5 * Math.sin(this.phase * 0.65 + i * 1.24 + 1.1) const level: number = 0.18 + 0.82 * (primaryWave * 0.72 + secondaryWave * 0.28) const barHeight: number = minBarHeight + (maxBarHeight - minBarHeight) * level const x: number = startX + i * (barWidth + barGap) const y: number = baseY - barHeight const topColor: string = rgbaFromHex(brandColor, 0.96) const bottomColor: string = rgbaFromHex(brandColor, 0.62) const glowColor: string = rgbaFromHex(brandColor, 0.12) const barGradient: CanvasGradient = ctx.createLinearGradient(x, y, x, baseY) barGradient.addColorStop(0, topColor) barGradient.addColorStop(1, bottomColor) ctx.fillStyle = glowColor this.fillRoundedRect(ctx, x - 0.35, y - 0.35, barWidth + 0.7, barHeight + 0.7, barWidth * 0.50) ctx.fillStyle = barGradient this.fillRoundedRect(ctx, x, y, barWidth, barHeight, barWidth * 0.50) } } private fillRoundedRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number): void { const safeRadius: number = Math.min(radius, width * 0.5, height * 0.5) ctx.beginPath() ctx.moveTo(x + safeRadius, y) ctx.lineTo(x + width - safeRadius, y) ctx.quadraticCurveTo(x + width, y, x + width, y + safeRadius) ctx.lineTo(x + width, y + height - safeRadius) ctx.quadraticCurveTo(x + width, y + height, x + width - safeRadius, y + height) ctx.lineTo(x + safeRadius, y + height) ctx.quadraticCurveTo(x, y + height, x, y + height - safeRadius) ctx.lineTo(x, y + safeRadius) ctx.quadraticCurveTo(x, y, x + safeRadius, y) ctx.closePath() ctx.fill() } build() { Row() { Canvas(this.canvasContext) .width(this.indicatorSize) .height(this.indicatorSize) .onReady(() => { this.canvasContext.imageSmoothingEnabled = true this.canvasContext.imageSmoothingQuality = 'medium' this.drawFrame() }) .onAreaChange((_oldVal: Area, newVal: Area) => { this.canvasWidth = newVal.width as number this.canvasHeight = newVal.height as number this.drawFrame() }) } .width(this.indicatorSize) .height(this.indicatorSize) .margin({ right: this.marginRight, top: this.marginTop, bottom: this.marginBottom }) .visibility(this.isActive ? Visibility.Visible : Visibility.None) .onAppear(() => { this.updateAnimationState() this.drawFrame() }) .onDisAppear(() => { this.stopTimer() }) } }