如何解决有没有用Java制作钢琴图形的好方法?
我在互联网上搜索了用Java Swing制作钢琴的正确方法。但是他们要么在黑键之间有缝隙,要么没有解释他们是如何做到的。
我尝试使用具有null布局的JPanel并先将白色按键(Jpanels或Jbuttons)与MouseListener一起添加,然后再添加黑色按键,使它们应位于白色上方。问题在于它不是很优雅的代码,除此之外,它也不起作用。
有人知道如何用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,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,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钢琴:
我认为发布完整程序非常困难,因为它与我的一些实用程序类纠缠在一起,并且您大概拥有自己想要构建的设计。但是,这里是图形化“键盘”组件的来源,该组件没有依赖性:
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,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,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,new Color(0x00000000,}
));
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,new Color(0x60FFFFFF,bounds.y + bounds.height * 0.5f,new Color(0x00FFFFFF,true)
));
g.fillRoundRect(bounds.x,bounds.width,bounds.height,4,4);
g.setPaint(new LinearGradientPaint(
bounds.x,bounds.x + bounds.width,0.2f,0.8f,new Color[] {
new Color(0x60FFFFFF,}
));
g.fillRoundRect(bounds.x,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
)。将键盘作为一个组件而不是单独的按钮具有两个优点。一种是您可以做完全任意的形状边界,而不仅仅是矩形。另一种是您可以沿键盘笔直拖动/滑动鼠标(这不适用于单独的按钮)。
添加白色键...然后添加黑色键,使它们应位于白色上方。
实际上,Swing绘画逻辑会绘画最先添加的最后一个组件。因此,您的黑色按键将首先被绘制,白色被顶部绘制。通常这不是问题,因为使用布局管理器时组件不会重叠。
因此,您需要在面板上添加黑键,然后再添加白键。
但是,这不能解决所有问题。
在不重叠的情况下优化了摇摆绘画。因为您的组件确实重叠,所以您还需要重写isOptimizedDrawingEnable()
方法以返回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,WWD,WHT);
}
public int getNote () {
return note;
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。