我在互联网上搜索是否有在 Java Swing 中制作钢琴的正确方法。但是要么他们在黑键之间有间隙,要么他们没有解释他们是如何做到的。
我尝试使用带有空布局的 JPanel 并首先添加带有 MouseListener 的白键(Jpanels 或 Jbuttons),然后添加黑键,使它们应位于白键上方。问题是它不是很优雅的代码,除此之外,它不起作用。
有谁知道如何用Java制作钢琴?
这是我的代码:
package me.Trainer.Piano;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JPanel;
import me.Trainer.Enums.Note;
public class PianoGraphics {
static volatile Note result = null;
public static JPanel getDrawnKeyboard() {
JPanel panel = new JPanel() {
private static final long serialVersionUID = 502433120279478947L;
Dimension lastFrame;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = this.getWidth();
int height = this.getHeight();
if (lastFrame != this.getSize()) {
this.removeAll();
JPanel white = new JPanel() {
private static final long serialVersionUID = 2350489085544800839L;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.LIGHT_GRAY);
g.drawRect(0, 0, this.getWidth(), this.getHeight());
};
};
white.setBackground(Color.WHITE);
white.setSize(width / 52, height);
for (int i = 0; i < 52; i++) {
Note note;
int oct = (int) i / 7;
switch(i % 7) {
case 0:
note = Note.values()[0 + (oct * 12)];
break;
case 1:
note = Note.values()[2 + (oct * 12)];
break;
case 2:
note = Note.values()[3 + (oct * 12)];
break;
case 3:
note = Note.values()[5 + (oct * 12)];
break;
case 4:
note = Note.values()[7 + (oct * 12)];
break;
case 5:
note = Note.values()[8 + (oct * 12)];
break;
case 6:
note = Note.values()[10 + (oct * 12)];
break;
default:
note = Note.C4;
}
white.setLocation(i * (width / 52), 0);
white.addMouseListener(new KeyboardMouseListener() {
Note n = note;
@Override
public void mouseReleased(MouseEvent e) {
white.setBackground(Color.WHITE);
result = null;
}
@Override
public void mouseClicked(MouseEvent e) {
white.setBackground(Color.LIGHT_GRAY);
result = n;
}
});
this.add(white);
}
JPanel black = new JPanel() {
private static final long serialVersionUID = 8445848892107864631L;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.DARK_GRAY);
g.drawRect(0, 0, this.getWidth(), this.getHeight());
};
};
black.setBackground(Color.BLACK);
black.setSize(width / 108, height / 3 * 2);
for (int i = 0; i < 7; i++) {
Note note = Note.values()[1 + (i*12)];
JPanel b = black;
b.setLocation(i*12*8 + 7, 0);
b.addMouseListener(new KeyboardMouseListener() {
public void mouseClicked(MouseEvent e) {
b.setBackground(Color.DARK_GRAY);
result = note;
};
public void mouseReleased(MouseEvent e) {
b.setBackground(Color.BLACK);
result = null;
System.out.println(note.name());
};
});
this.add(b);
JPanel b1 = black;
Note note1 = Note.values()[1 + (i*12)];
b1.setLocation(i*12*8 + 21, 0);
b1.addMouseListener(new KeyboardMouseListener() {
public void mouseClicked(MouseEvent e) {
b1.setBackground(Color.DARK_GRAY);
result = note1;
System.out.println(note1.name());
};
public void mouseReleased(MouseEvent e) {
b1.setBackground(Color.BLACK);
result = null;
};
});
this.add(b1);
JPanel b2 = black;
Note note2 = Note.values()[1 + (i*12)];
b2.setLocation(i*12*8 + 30, 0);
b2.addMouseListener(new KeyboardMouseListener() {
public void mouseClicked(MouseEvent e) {
b2.setBackground(Color.DARK_GRAY);
result = note2;
};
public void mouseReleased(MouseEvent e) {
b2.setBackground(Color.BLACK);
result = null;
};
});
this.add(b2);
JPanel b3 = black;
Note note3 = Note.values()[1 + (i*12)];
b3.setLocation(i*12*8 + 45, 0);
b3.addMouseListener(new KeyboardMouseListener() {
public void mouseClicked(MouseEvent e) {
b3.setBackground(Color.DARK_GRAY);
result = note3;
};
public void mouseReleased(MouseEvent e) {
b3.setBackground(Color.BLACK);
result = null;
};
});
this.add(b3);
JPanel b4 = black;
Note note4 = Note.values()[1 + (i*12)];
b4.setLocation(i*12*8 + 53, 0);
b4.addMouseListener(new KeyboardMouseListener() {
public void mouseClicked(MouseEvent e) {
b4.setBackground(Color.DARK_GRAY);
result = note4;
};
public void mouseReleased(MouseEvent e) {
b4.setBackground(Color.BLACK);
result = null;
};
});
this.add(b4);
}
}
lastFrame = this.getSize();
}
};
panel.setLayout(null);
return panel;
}
public static Note waitForNote() {
while (result == null) {}
Note note = result;
result = null;
return note;
}
}
class KeyboardMouseListener implements MouseListener {
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
}
Run Code Online (Sandbox Code Playgroud)
这就是我得到的: 没有什么是可点击的
您可以使用 SwingShape界面,特别java.awt.geom.Path2D是绘制任意形状和进行点击测试。我曾经用这个写过一个 Swing MIDI 钢琴:

我认为发布完整程序会非常困难,因为它与我的一些实用程序类纠缠在一起,而且您大概有自己想要构建的设计。但这里是图形“键盘”组件的来源,它没有依赖关系:
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public final class Keyboard extends JComponent {
public static final float WHITE_KEY_ASPECT = (7f / 8f) / (5.7f);
public static final float BLACK_KEY_HEIGHT = 3.5f / 6f;
private char firstNote;
private int whiteKeyCount;
private int whiteKeyWidth;
private int whiteKeyHeight;
private List<KeyShape> keyShapes;
private final Set<Integer> litKeys = new HashSet<>();
public Keyboard() {
setFirstNote('C');
setWhiteKeyCount(7 * 7 + 1);
setWhiteKeySize(Math.round(220 * WHITE_KEY_ASPECT), 220);
}
public void setFirstNote(char n) {
if (n < 'A' || n > 'G') throw new IllegalArgumentException();
this.firstNote = n;
revalidate();
}
public void setWhiteKeyCount(int c) {
if (c < 0) throw new IllegalArgumentException();
this.whiteKeyCount = c;
revalidate();
}
public void setWhiteKeySize(int width, int height) {
if (width < 0) throw new IllegalArgumentException();
if (height < 0) throw new IllegalArgumentException();
this.whiteKeyWidth = width;
this.whiteKeyHeight = height;
revalidate();
}
private static class KeyShape {
final Shape shape;
final char color; // 'W' or 'B'
KeyShape(Shape shape, char color) {
this.shape = shape;
this.color = color;
}
}
@Override
public void invalidate() {
super.invalidate();
keyShapes = null;
}
private List<KeyShape> getKeyShapes() {
if (keyShapes == null) {
keyShapes = generateKeyShapes();
}
return keyShapes;
}
private List<KeyShape> generateKeyShapes() {
List<KeyShape> shapes = new ArrayList<>();
int x = 0;
char note = firstNote;
for (int w = 0; w < whiteKeyCount; w++) {
float cutLeft = 0, cutRight = 0;
switch (note) {
case 'C':
cutLeft = 0 / 24f;
cutRight = 9 / 24f;
break;
case 'D':
cutLeft = 5 / 24f;
cutRight = 5 / 24f;
break;
case 'E':
cutLeft = 9 / 24f;
break;
case 'F':
cutRight = 11 / 24f;
break;
case 'G':
cutLeft = 3 / 24f;
cutRight = 7 / 24f;
break;
case 'A':
cutLeft = 7 / 24f;
cutRight = 3 / 24f;
break;
case 'B':
cutLeft = 11 / 24f;
cutRight = 0 / 24f;
break;
}
if (w == 0)
cutLeft = 0;
if (w == whiteKeyCount - 1)
cutRight = 0;
shapes.add(new KeyShape(createWhiteKey(x, cutLeft, cutRight), 'W'));
if (cutRight != 0) {
shapes.add(new KeyShape(createBlackKey(x + whiteKeyWidth - (whiteKeyWidth * cutRight)), 'B'));
}
x += whiteKeyWidth;
if (++note == 'H') note = 'A';
}
return Collections.unmodifiableList(shapes);
}
private Shape createWhiteKey(float x, float cutLeft, float cutRight) {
float width = whiteKeyWidth, height = whiteKeyHeight;
Path2D.Float path = new Path2D.Float();
path.moveTo(x + cutLeft * width, 0);
path.lineTo(x + width - (width * cutRight), 0);
if (cutRight != 0) {
path.lineTo(x + width - (width * cutRight), height * BLACK_KEY_HEIGHT);
path.lineTo(x + width, height * BLACK_KEY_HEIGHT);
}
final float bevel = 0.15f;
path.lineTo(x + width, height - (width * bevel) - 1);
if (bevel != 0) {
path.quadTo(x + width, height, x + width * (1 - bevel), height - 1);
}
path.lineTo(x + width * bevel, height - 1);
if (bevel != 0) {
path.quadTo(x, height, x, height - (width * bevel) - 1);
}
if (cutLeft != 0) {
path.lineTo(x, height * BLACK_KEY_HEIGHT);
path.lineTo(x + width * cutLeft, height * BLACK_KEY_HEIGHT);
}
path.closePath();
return path;
}
private Shape createBlackKey(float x) {
return new Rectangle2D.Float(
x, 0,
whiteKeyWidth * 14f / 24,
whiteKeyHeight * BLACK_KEY_HEIGHT
);
}
@Override
public void paintComponent(Graphics g1) {
Graphics2D g = (Graphics2D)g1;
Rectangle clipRect = g.getClipBounds();
g.setColor(Color.BLACK);
g.fill(clipRect);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(1f));
List<KeyShape> keyShapes = getKeyShapes();
for (int i = 0; i < keyShapes.size(); i++) {
KeyShape ks = keyShapes.get(i);
Rectangle bounds = ks.shape.getBounds();
if (!bounds.intersects(clipRect)) continue;
g.setColor(isKeyLit(i)
? (ks.color == 'W' ? new Color(0xFF5050) : new Color(0xDF3030))
: (ks.color == 'W' ? Color.WHITE : Color.BLACK)
);
g.fill(ks.shape);
if (true) { // gradient
if (ks.color == 'W') {
g.setPaint(new LinearGradientPaint(
bounds.x, bounds.y, bounds.x, bounds.y + bounds.height,
new float[] { 0, 0.02f, 0.125f, 0.975f, 1 },
new Color[] {
new Color(0xA0000000, true),
new Color(0x30000000, true),
new Color(0x00000000, true),
new Color(0x00000000, true),
new Color(0x30000000, true),
}
));
g.fill(ks.shape);
} else {
bounds.setRect(
bounds.getX() + bounds.getWidth() * 0.15f,
bounds.getY() + bounds.getHeight() * 0.03f,
bounds.getWidth() * 0.7f,
bounds.getHeight() * 0.97f
);
g.setPaint(new GradientPaint(
bounds.x, bounds.y, new Color(0x60FFFFFF, true),
bounds.x, bounds.y + bounds.height * 0.5f, new Color(0x00FFFFFF, true)
));
g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
g.setPaint(new LinearGradientPaint(
bounds.x, bounds.y, bounds.x + bounds.width, bounds.y,
new float[] { 0, 0.2f, 0.8f, 1 },
new Color[] {
new Color(0x60FFFFFF, true),
new Color(0x00FFFFFF, true),
new Color(0x00FFFFFF, true),
new Color(0x60FFFFFF, true),
}
));
g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
}
}
g.setColor(Color.BLACK);
g.draw(ks.shape);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(
whiteKeyCount * whiteKeyWidth,
whiteKeyHeight
);
}
public int getKeyAtPoint(Point2D p) {
List<KeyShape> keyShapes = getKeyShapes();
for (int i = 0; i < keyShapes.size(); i++) {
if (keyShapes.get(i).shape.contains(p)) return i;
}
return -1;
}
public void setKeyLit(int index, boolean b) {
if (index < 0 || index > getKeyShapes().size()) return;
if (b) {
litKeys.add(index);
} else {
litKeys.remove(index);
}
repaint(getKeyShapes().get(index).shape.getBounds());
}
public boolean isKeyLit(int index) {
return litKeys.contains(index);
}
public void clearLitKeys() {
litKeys.clear();
repaint();
}
}
Run Code Online (Sandbox Code Playgroud)
我已经很多年没有看过这段代码了,但这里有一个基本的想法:整个键盘是一个组件。它Shape为键生成一个对象列表,并使用这些形状来绘制键和单击测试(添加您的MouseListener和MouseMotionListener哪个调用getKeyAtPoint)。将键盘作为一个组件而不是单独的按钮有两个优点。一是你可以做完全任意的形状边界,而不仅仅是矩形。另一个是您可以沿着键盘直接拖动/滑动鼠标(这不适用于单独的按钮)。