在Java中有没有制作钢琴图形的好方法?
Is there a good way to make Piano Graphics in Java?
我在网上查了下JavaSwing有没有合适的钢琴制作方法。但是要么他们在黑键之间有间隙,要么他们没有解释他们是如何做到的。
我尝试使用具有空布局的 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) {}
}
这是我得到的:
Nothing is clickable
您可以使用 Swing Shape
界面,特别是 java.awt.geom.Path2D
来绘制任意形状并进行点击测试。我曾经用这个写过 Swing MIDI 钢琴:
我认为 post 完整的程序会非常困难,因为它与我的一些实用程序纠缠在一起 类,并且您大概有自己的设计想要构建。但这里是图形“键盘”组件的源代码,它没有依赖关系:
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();
}
}
我已经很多年没看过这段代码了,但基本思想如下:整个键盘是一个组件。它为键生成一个 Shape
对象列表,并使用形状绘制键 和 点击测试(添加你的 MouseListener
和 MouseMotionListener
调用 getKeyAtPoint
)。将键盘作为一个组件而不是单独的按钮有两个优点。一是你可以做完全任意的形状边界,而不仅仅是矩形。另一个是您可以 drag/glide 鼠标直接沿着键盘移动(这不适用于单独的按钮)。
adding the white keys ... and then adding the black keys so they should be above the whites.
实际上,Swing 绘制逻辑首先绘制最后添加的组件。所以你的黑键将首先被绘制,白色被绘制在最上面。通常这不是问题,因为在使用布局管理器时组件不会重叠。
因此,您需要先将黑键添加到面板,然后再添加白键。
但是,这并不能解决所有问题。
Swing 绘画在假设组件不重叠的情况下进行了优化。因为您的组件确实重叠,您还需要将 isOptimizedDrawingEnable()
方法覆盖为 return false
.
这是一个基本的例子(我很久以前在网上找到的):
import java.awt.*;
import java.awt.event.*;
import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.*;
public class MidiPiano implements MouseListener {
final int OCTAVES = 4; // change as desired
private WhiteKey[] whites = new WhiteKey [7 * OCTAVES + 1];
private BlackKey[] blacks = new BlackKey [5 * OCTAVES];
MidiChannel channel;
public MidiPiano () {
try {
Synthesizer synth = MidiSystem.getSynthesizer ();
synth.open ();
synth.loadAllInstruments (synth.getDefaultSoundbank ());
Instrument [] insts = synth.getLoadedInstruments ();
MidiChannel channels[] = synth.getChannels ();
for (int i = 0; i < channels.length; i++) {
if (channels [i] != null) {
channel = channels [i];
break;
}
}
for (int i = 0; i < insts.length; i++) {
if (insts [i].toString ()
.startsWith ("Instrument MidiPiano")) {
channel.programChange (i);
break;
}
}
} catch (MidiUnavailableException ex) {
ex.printStackTrace ();
}
}
public void mousePressed (MouseEvent e) {
Key key = (Key) e.getSource ();
channel.noteOn (key.getNote (), 127);
}
public void mouseReleased (MouseEvent e) {
Key key = (Key) e.getSource ();
channel.noteOff (key.getNote ());
}
public void mouseClicked (MouseEvent e) { }
public void mouseEntered (MouseEvent e) { }
public void mouseExited (MouseEvent e) { }
private void createAndShowGUI () {
JPanel contentPane = new JPanel(null)
{
@Override
public Dimension getPreferredSize()
{
int count = getComponentCount();
Component last = getComponent(count - 1);
Rectangle bounds = last.getBounds();
int width = 10 + bounds.x + bounds.width;
int height = 10 + bounds.y + bounds.height;
return new Dimension(width, height);
}
@Override
public boolean isOptimizedDrawingEnabled()
{
return false;
}
};
for (int i = 0; i < blacks.length; i++) {
blacks [i] = new BlackKey (i);
contentPane.add (blacks [i]);
blacks [i].addMouseListener (this);
}
for (int i = 0; i < whites.length; i++) {
whites [i] = new WhiteKey (i);
contentPane.add (whites [i]);
whites [i].addMouseListener (this);
}
JFrame frame = new JFrame("Midi Piano");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
//frame.add( contentPane );
frame.add( new JScrollPane(contentPane) );
frame.pack();
frame.setLocationRelativeTo (null);
frame.setVisible(true);
}
public static void main (String[] args) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
new MidiPiano ().createAndShowGUI ();
}
});
}
}
interface Key {
// change WD to suit your screen
int WD = 16;
int HT = (WD * 9) / 2;
// change baseNote for starting octave
// multiples of 16 only
int baseNote = 48;
int getNote ();
}
class BlackKey extends JButton implements Key {
final int note;
public BlackKey (int pos) {
note = baseNote + 1 + 2 * pos + (pos + 3) / 5 + pos / 5;
int left = 10 + WD
+ ((WD * 3) / 2) * (pos + (pos / 5)
+ ((pos + 3) / 5));
setBackground (Color.BLACK);
setBounds (left, 10, WD, HT);
}
public int getNote () {
return note;
}
}
class WhiteKey extends JButton implements Key {
static int WWD = (WD * 3) / 2;
static int WHT = (HT * 3) / 2;
final int note;
public WhiteKey (int pos) {
note = baseNote + 2 * pos
- (pos + 4) / 7
- pos / 7;
int left = 10 + WWD * pos;
// I think metal looks better!
//setBackground (Color.WHITE);
setBounds (left, 10, WWD, WHT);
}
public int getNote () {
return note;
}
}
我在网上查了下JavaSwing有没有合适的钢琴制作方法。但是要么他们在黑键之间有间隙,要么他们没有解释他们是如何做到的。
我尝试使用具有空布局的 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) {}
}
这是我得到的: Nothing is clickable
您可以使用 Swing Shape
界面,特别是 java.awt.geom.Path2D
来绘制任意形状并进行点击测试。我曾经用这个写过 Swing MIDI 钢琴:
我认为 post 完整的程序会非常困难,因为它与我的一些实用程序纠缠在一起 类,并且您大概有自己的设计想要构建。但这里是图形“键盘”组件的源代码,它没有依赖关系:
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();
}
}
我已经很多年没看过这段代码了,但基本思想如下:整个键盘是一个组件。它为键生成一个 Shape
对象列表,并使用形状绘制键 和 点击测试(添加你的 MouseListener
和 MouseMotionListener
调用 getKeyAtPoint
)。将键盘作为一个组件而不是单独的按钮有两个优点。一是你可以做完全任意的形状边界,而不仅仅是矩形。另一个是您可以 drag/glide 鼠标直接沿着键盘移动(这不适用于单独的按钮)。
adding the white keys ... and then adding the black keys so they should be above the whites.
实际上,Swing 绘制逻辑首先绘制最后添加的组件。所以你的黑键将首先被绘制,白色被绘制在最上面。通常这不是问题,因为在使用布局管理器时组件不会重叠。
因此,您需要先将黑键添加到面板,然后再添加白键。
但是,这并不能解决所有问题。
Swing 绘画在假设组件不重叠的情况下进行了优化。因为您的组件确实重叠,您还需要将 isOptimizedDrawingEnable()
方法覆盖为 return false
.
这是一个基本的例子(我很久以前在网上找到的):
import java.awt.*;
import java.awt.event.*;
import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.*;
public class MidiPiano implements MouseListener {
final int OCTAVES = 4; // change as desired
private WhiteKey[] whites = new WhiteKey [7 * OCTAVES + 1];
private BlackKey[] blacks = new BlackKey [5 * OCTAVES];
MidiChannel channel;
public MidiPiano () {
try {
Synthesizer synth = MidiSystem.getSynthesizer ();
synth.open ();
synth.loadAllInstruments (synth.getDefaultSoundbank ());
Instrument [] insts = synth.getLoadedInstruments ();
MidiChannel channels[] = synth.getChannels ();
for (int i = 0; i < channels.length; i++) {
if (channels [i] != null) {
channel = channels [i];
break;
}
}
for (int i = 0; i < insts.length; i++) {
if (insts [i].toString ()
.startsWith ("Instrument MidiPiano")) {
channel.programChange (i);
break;
}
}
} catch (MidiUnavailableException ex) {
ex.printStackTrace ();
}
}
public void mousePressed (MouseEvent e) {
Key key = (Key) e.getSource ();
channel.noteOn (key.getNote (), 127);
}
public void mouseReleased (MouseEvent e) {
Key key = (Key) e.getSource ();
channel.noteOff (key.getNote ());
}
public void mouseClicked (MouseEvent e) { }
public void mouseEntered (MouseEvent e) { }
public void mouseExited (MouseEvent e) { }
private void createAndShowGUI () {
JPanel contentPane = new JPanel(null)
{
@Override
public Dimension getPreferredSize()
{
int count = getComponentCount();
Component last = getComponent(count - 1);
Rectangle bounds = last.getBounds();
int width = 10 + bounds.x + bounds.width;
int height = 10 + bounds.y + bounds.height;
return new Dimension(width, height);
}
@Override
public boolean isOptimizedDrawingEnabled()
{
return false;
}
};
for (int i = 0; i < blacks.length; i++) {
blacks [i] = new BlackKey (i);
contentPane.add (blacks [i]);
blacks [i].addMouseListener (this);
}
for (int i = 0; i < whites.length; i++) {
whites [i] = new WhiteKey (i);
contentPane.add (whites [i]);
whites [i].addMouseListener (this);
}
JFrame frame = new JFrame("Midi Piano");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
//frame.add( contentPane );
frame.add( new JScrollPane(contentPane) );
frame.pack();
frame.setLocationRelativeTo (null);
frame.setVisible(true);
}
public static void main (String[] args) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
new MidiPiano ().createAndShowGUI ();
}
});
}
}
interface Key {
// change WD to suit your screen
int WD = 16;
int HT = (WD * 9) / 2;
// change baseNote for starting octave
// multiples of 16 only
int baseNote = 48;
int getNote ();
}
class BlackKey extends JButton implements Key {
final int note;
public BlackKey (int pos) {
note = baseNote + 1 + 2 * pos + (pos + 3) / 5 + pos / 5;
int left = 10 + WD
+ ((WD * 3) / 2) * (pos + (pos / 5)
+ ((pos + 3) / 5));
setBackground (Color.BLACK);
setBounds (left, 10, WD, HT);
}
public int getNote () {
return note;
}
}
class WhiteKey extends JButton implements Key {
static int WWD = (WD * 3) / 2;
static int WHT = (HT * 3) / 2;
final int note;
public WhiteKey (int pos) {
note = baseNote + 2 * pos
- (pos + 4) / 7
- pos / 7;
int left = 10 + WWD * pos;
// I think metal looks better!
//setBackground (Color.WHITE);
setBounds (left, 10, WWD, WHT);
}
public int getNote () {
return note;
}
}