整體架構
Custom View (PianoKeyboardView)
├── onMeasure() 計算白鍵/黑鍵尺寸、總寬高
├── onDraw(Canvas) 繪製鍵盤背景、白鍵、黑鍵、按下高亮
├── onTouchEvent() 多點觸控識別、按下/抬起狀態更新、觸發發聲
└── SoundPool 低延遲音頻播放引擎
關鍵技術點
1. 佈局幾何計算
- 一個八度 7 白鍵 + 5 黑鍵 = 12 鍵。
- 白鍵寬度 =
viewWidth / (7 * octaves)。
- 黑鍵寬度 = 白鍵寬度 * 0.6,高度 = 白鍵高度 * 0.6,位於白鍵間隙上方。
- 預計算每個鍵的
RectF 區域,觸摸判斷用 rect.contains(x, y)。
2. 多點觸控處理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
handleKeyDown(pointerId, x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
handleKeyUp(pointerId, x, y);
break;
case MotionEvent.ACTION_MOVE:
// 滑動跨鍵可選擇觸發 legato 效果
break;
}
invalidate(); // 重繪高亮
return true;
}
3. 低延遲發聲
- 使用 SoundPool (API 1-20) 或 AudioTrack / AAudio (API 26+)。
- 預加載所有鍵音頻 (建議 OGG/MP3 短采樣),
soundPool.load(context, R.raw.note_c4, 1)。
- 播放:
soundPool.play(soundId, 1f, 1f, 1, 0, 1f)。
- 釋放:
soundPool.release() 在 onDetachedFromWindow。
4. 視覺反饋
- 白鍵按下:淺灰色覆蓋 + 輕微下沉位移 (Canvas translate)。
- 黑鍵按下:深灰色覆蓋。
- 使用
Paint.setColorFilter 或疊加半透明矩形實現。
XML 佈局引用
<com.example.PianoKeyboardView
android:id="@+id/piano"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#222" />
擴展建議
- 支持滾動鍵盤:外層包
HorizontalScrollView,內部 View 寬度超過屏幕。
- MIDI 輸出:集成
MidiManager 連接外部設備。
- 錄音回放:記錄
(note, timestamp, velocity) 序列,回放時調度 SoundPool。