在这里插入图片描述
在这里插入图片描述

实例:简易钢琴(Piano)|技术:画布布局、音符提示、静音开关、弹奏体验

一、页面架构总览

Column
├── 标题栏              // 标题 + 音符提示 + 静音开关 + 刷新
├── 钢琴画布            // Canvas:96% 宽 × 210 高,白键+黑键
└── 音阶说明            // C3 D3 E3 F3 G3 A3 B3 提示

页面结构简洁:键盘画布是绝对主体,标题栏承载"当前音符"与"静音开关"。

二、画布与键盘适配

Canvas(this.ctx)
  .width('96%')
  .height(210)
  .borderRadius(12)
  .border({ width: 1, color: '#E2E8F0' })
  .onReady(() => {
    this.canvasW = this.ctx.width;
    this.canvasH = this.ctx.height;
    this.draw();
  })

96% 宽 × 210 高:白键 7 个 × 44px + 边距 ≈ 318px,画布 96% 宽(约 350px)足够容纳。210 高容纳白键 200 + 边距。

注意:白键宽 44px 是逻辑值,画布实际宽度可能更大——键盘左对齐(startX=10)绘制,右侧留白。若要键盘居中/铺满,可动态计算 whiteW = (canvasW - 20) / 7按画布反推键宽,同 34 的反推 CELL 思路)。

三、音符提示

@State pressedKey: string = '';
@State noteText: string = '触摸琴键弹奏';
// 命中时:
this.pressedKey = WHITE_KEYS[i];   // 或 BLACK_NAMES[i]
this.noteText = `${WHITE_KEYS[i]}`;

双状态量pressedKey 驱动画布高亮(绘制判断),noteText 驱动标题栏提示(UI 文本)。一个命中事件更新两个状态——一个管绘制、一个管文案。

抬起复位

} else if (e.type === TouchType.Up) {
  this.pressedKey = '';
  this.noteText = '触摸琴键弹奏';
  this.draw();
}

Up 时清空 pressedKey(高亮消失)+ 提示复位 + 重绘。

四、静音开关

Text(this.mutedText === '' ? '🔊' : '🔇').fontSize(20).margin({ right: 12 })
  .onClick(() => {
    this.muted = !this.muted;
    this.mutedText = this.muted ? 'muted' : '';
  })

🔊/🔇 切换:用 mutedText 状态驱动 emoji 显示(''→🔊,'muted’→🔇)。playNote 内部 if (this.muted) return——静音时只高亮不发音。

为什么用 mutedText 而非直接 muted:@State 才能驱动 UI 刷新,muted 是普通变量不触发重渲染。用状态量包装展示值是 ArkTS 的常见技巧(也可以用 @State muted 直接驱动,此处教学展示"包装"手法)。

五、音频播放器生命周期

private players: Array<audio.AudioPlayer | null> = [];

aboutToDisappear(): void {
  for (const p of this.players) {
    if (p !== null) {
      p.release();   // 释放播放器资源
    }
  }
  this.players = [];
}

播放器数组 + 统一释放:多音符并发需要多个播放器,players 数组管理;页面销毁时全部 release()——资源清理纪律(与 34 停定时器同思想)。

播放器创建时机(注释说明):

// 首次播放某音符时创建:
const player = audio.createAudioPlayer();
player.src = `rawfile:///notes/${name}.wav`;
player.play();
this.players.push(player);

懒创建 + 复用:播放过的播放器存数组,同音符再按需 player.stop() 重置后重播。

六、弹奏体验流程

触摸 Down ──► hitKey
         │    ├─ 黑键区域?→ 命中黑键
         │    ├─ 白键区域?→ 命中白键
         │    ├─ pressedKey + noteText 更新
         │    ├─ playNote(发音)
         │    └─ draw(高亮)
         │
触摸 Move ──► hitKey(滑音,连续触发)
         │
触摸 Up ──► pressedKey='' + noteText 复位 + draw

C 大调音阶弹奏:依次按 C3→B3 白键,noteText 逐个显示 ♩ C3 ♩ D3…,画布高亮逐键移动——视觉与提示同步

七、UI 技术要点对照表

技术点 实现方式 生产价值
键盘画布 96% × 210 主体突出
双状态量 pressedKey + noteText 绘制/文案分离
抬起复位 Up 清空 + 重绘 状态归零
静音开关 mutedText 驱动 emoji 演示控制
播放器数组 players + release 资源管理
懒创建 首次播放建播放器 按需资源

八、本篇小结

本篇完成了简易钢琴的界面与生命周期:键盘画布主体化双状态量(高亮 + 提示)同步更新静音开关播放器数组的懒创建与统一释放。UI 层的核心是"一个命中事件驱动两个 UI 通道"——画布高亮(绘制)与音符提示(文案)由同一个 pressedKey 事件触发,互不干扰。

下一篇《完整代码与运行效果》将给出 PianoPage 全量源码解读、操作剧本演示,以及"乐器类应用"通用模板抽象——适用于鼓垫、竖琴、MIDI 键盘等一切"触摸发音"应用。


九、UI 深度扩展

1. 键盘铺满画布

动态键宽:whiteW = (canvasW - 2*startX) / 7——画布不同大小键盘自动铺满。反推几何参数让布局自适应(34/36 都提过)。

2. 双指和弦

e.touches.length === 2 时两个命中点分别处理——pressedKeys 数组 + 多高亮。多点触控是乐器进阶。

3. 键盘滚动(多八度)

画布显示部分八度,左右滑动切换(视口偏移)。视口滚动扩展键盘范围。

4. 录制回放

录音:notes[] = [{name, time}];回放:定时器按 time 依次 playNote。弹奏记录是教学功能。

5. FAQ

Q1:为什么按下用浅蓝/深蓝而非红/绿?
A:钢琴是乐器非警示,蓝系"按下"语义(非红绿的情绪色)。颜色语义随应用类型变化——乐器用中性色。

Q2:静音后点琴键还有反馈吗?
A:有——高亮与音符提示仍在,只是不发音。视觉反馈与声音解耦,静音仅关声音通道。

Q3:播放器 release 后能复用吗?
A:不能,release 销毁资源。下次播放需重新 createAudioPlayer。release 是终态操作,只在页面销毁时调用。

Q4:滑动弹奏时 Up 会误触发吗?
A:Up 只是清空高亮,不影响弹奏序列。滑动过程中 Move 持续触发命中,Up 结束。状态机简单清晰(Down/Move 弹奏,Up 复位)。

Q5:这个键盘能弹完整曲子吗?
A:一个八度能弹简单旋律(小星星、两只老虎)。复杂曲子需多八度(深度扩展 3)。教学聚焦机制,规模可扩展


十、下篇预告

最后一篇《完整代码与运行效果》将:给出 PianoPage.ets 完整源码逐段解读;演示 C 大调音阶弹奏、黑键、静音开关的完整操作;抽象"触摸发音乐器"通用模板,并给出鼓垫、MIDI 键盘两个改写场景。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐