我正在研究任意形状的按钮。第一阶段成功了,因为按钮出现在预期的位置.第二阶段失败了,因为从doClick调用的paintImmediately油漆到一个完全不同的位置.我尝试了几种方法来使用AffineTransform,但无法计算出正确的位置。
任何提示是感激的。
我试过的是这样的--Buttonclass:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
public class MyCircleButton extends JButton implements MouseListener {
public MyCircleButton(String text, double xCenter, double yCenter, double rOuter, double rInner,
double start, double extend) {
super(text);
this.text = text;
this.xCenter = xCenter;
this.yCenter = yCenter;
this.xOffset = xCenter - rOuter;
this.yOffset = yCenter - rOuter;
this.rOuter = rOuter;
this.rInner = rInner;
this.angStart = start;
this.angSize = extend;
int fontSize = (int) (rOuter * 0.15);
this.font = new Font("Arial", Font.BOLD, fontSize);
this.addMouseListener(this);
calcShape();
calcText();
}
@Override
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
@Override
public void doClick(int pressTime) {
model.setPressed(true);
Component c = SwingUtilities.getRoot(this);
Graphics g = c.getGraphics();
Rectangle r = shape.getBounds();
if (g != null) {
Graphics2D g2 = (Graphics2D) g;
Point p = SwingUtilities.convertPoint(this, new Point(0, 0), getParent());
AffineTransform t = new AffineTransform();
t.setToTranslation(p.getX(), p.getY());
// t.setToTranslation(xText, yText);
// t.setToTranslation(xCenter, yCenter);
// g2.getTransform().concatenate(t);
g2.setTransform(t);
paint(g);
}
try {
Thread.currentThread().sleep(pressTime);
} catch (InterruptedException ie) {
}
model.setPressed(false);
}
@Override
public int getHeight() {
return shape.getBounds().height;
}
@Override
public int getWidth() {
return shape.getBounds().width;
}
@Override
public int getX() {
return shape.getBounds().x;
}
@Override
public int getY() {
return shape.getBounds().y;
}
@Override
public void mouseClicked(MouseEvent me) {
System.out.println("mouse clicked CIRCLE-BUTTON<" + text + "> at " + me.getX() + "/" + me.getY());
doClick(50);
}
@Override
public void mouseEntered(MouseEvent me) {
}
@Override
public void mouseExited(MouseEvent me) {
}
@Override
public void mousePressed(MouseEvent me) {
}
@Override
public void mouseReleased(MouseEvent me) {
}
@Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Shape oldClip = g2.getClip();
Paint oldPaint = g2.getPaint();
Stroke oldStroke = g2.getStroke();
Font oldFont = g2.getFont();
FontMetrics fm = g2.getFontMetrics(font);
Rectangle2D tb = fm.getStringBounds(text, g2);
g2.setClip(this.shape);
if (getModel().isArmed() || getModel().isPressed())
g2.setPaint(Color.RED);
else
g2.setPaint(Color.GREEN);
g2.fill(shape);
g2.setStroke(new BasicStroke(4));
g2.setPaint(Color.WHITE);
g2.draw(shape);
g2.setPaint(Color.BLACK);
g2.setFont(font);
g2.drawString(text, (int) (xText + 2 - tb.getWidth() / 2), (int) (yText + 2 + fm.getAscent() * 0.35));
g2.setPaint(Color.WHITE);
g2.drawString(text, (int) (xText - tb.getWidth() / 2), (int) (yText + fm.getAscent() * 0.35));
g2.setFont(oldFont);
g2.setStroke(oldStroke);
g2.setPaint(oldPaint);
g2.setClip(oldClip);
}
protected void calcShape() {
Arc2D a = new Arc2D.Double(xOffset, yOffset, 2 * rOuter, 2 * rOuter, angStart, angSize,
Arc2D.PIE);
Ellipse2D e = new Ellipse2D.Double(xOffset + rOuter - rInner, yOffset + rOuter - rInner,
2 * rInner, 2 * rInner);
Area resShape = new Area(a);
resShape.subtract(new Area(e));
this.shape = resShape;
}
protected void calcText() {
double angle = angStart + angSize / 2;
double r = rInner + (rOuter - rInner) / 2;
double xOff = r * Math.sin(Math.toRadians(90 + angle));
double yOff = r * Math.cos(Math.toRadians(90 + angle));
xText = xCenter + (Math.abs(xOff) > 0.1 ? xOff : 0);
yText = yCenter + (Math.abs(yOff) > 0.1 ? yOff : 0);
}
private double xCenter;
private double yCenter;
private double xOffset;
private double yOffset;
private double rOuter;
private double rInner;
private double angStart;
private double angSize;
private double xText;
private double yText;
private String text;
private Shape shape;
private Font font;
}
Buttonclass: 和Pane class:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestCirclePane extends JPanel {
public TestCirclePane(int w, int h) {
diameter = (int) (0.9 * (w < h ? w : h));
Dimension s = new Dimension(w, h);
xCenter = w / 2;
yCenter = h / 2;
setMinimumSize(s);
setPreferredSize(s);
setLayout(null);
createComponents(this);
}
@Override
public void paintChildren(Graphics g) {
for (Component c : getComponents()) {
if (c instanceof MyCircleButton)
((MyCircleButton) c).paint(g);
}
}
protected void createComponents(JPanel p) {
double r = diameter / 2;
double w = r * 0.66;
double r3o = r;
double r3i = r - w * 0.5;
add(new MyCircleButton("B0", xCenter, yCenter, r3o, r3i, 135, 90));
add(new MyCircleButton("B1", xCenter, yCenter, r3o, r3i, 45, 90));
add(new MyCircleButton("B2", xCenter, yCenter, r3o, r3i, -45, 90));
add(new MyCircleButton("B3", xCenter, yCenter, r3o, r3i, 225, 90));
}
@Override
protected void paintComponent(Graphics arg0) {
super.paintComponent(arg0);
Graphics2D g2 = (Graphics2D) arg0;
Dimension d = getSize();
Paint oldPaint = g2.getPaint();
g2.setPaint(Color.WHITE);
g2.fill3DRect(0, 0, d.width, d.height, true);
g2.setPaint(Color.LIGHT_GRAY);
g2.drawLine(0, yCenter, d.width, yCenter);
g2.drawLine(xCenter, 0, xCenter, d.height);
g2.setPaint(oldPaint);
}
public static void createWindow() {
JFrame frame = new JFrame("Test - CircleButton");
JPanel client = new JPanel();
client.setLayout(new BorderLayout());
client.add(placeHolder("top"), BorderLayout.NORTH);
client.add(placeHolder("bottom"), BorderLayout.SOUTH);
client.add(placeHolder("left"), BorderLayout.WEST);
client.add(placeHolder("right"), BorderLayout.EAST);
client.add(new TestCirclePane(800, 600), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(client);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createWindow();
}
});
}
public static JComponent placeHolder(String s) {
JLabel l = new JLabel(s);
l.setPreferredSize(new Dimension(300, 300));
return l;
}
private int xCenter;
private int yCenter;
private int diameter;
private static final long serialVersionUID = 1L;
}
不知道这是否会有帮助,但这里有我的两点意见。
不要覆盖 paintChildren(…)
. JPanel会自动重新绘制任何添加到面板上的组件,不要覆盖paint(...)。
不要覆盖paint(...)。自定义绘画是通过重写 paintComponent(…)
而你需要调用 super.paintComponent(…)
否则你可能会出现绘画伪影。
不要覆盖 getX(
和 getY()
这些方法是用来控制组件在面板上的位置,我猜你不应该覆盖getWidth()和getHeight()。
我也猜测你不应该覆盖getWidth()和getHeight()。我想你应该覆盖的是 getPreferredSize()
方法来控制组件的大小。
如果你要使用null布局,那么你的代码要负责设置面板上每个组件的大小位置。
不要在你的绘制方法中进行翻译。组件的绘制应该总是相对于(0,0)进行。这个位置决定了组件在面板上的位置。
不要在doClick()方法中调用Thread.sleep()。Thread.sleep()会导致EDT进入睡眠状态,这意味着GUI无法重新绘制自己。不知道为什么你还需要覆盖这个方法。
所以这就是变体,可以用。首先是Button类,然后是Pane类
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
public class MyCircleButton extends JButton implements MouseListener {
public MyCircleButton(String text, double xCenter, double yCenter, double rOuter, double rInner,
double start, double extend) {
super(text);
this.xCenter = xCenter;
this.yCenter = yCenter;
this.xOffset = xCenter - rOuter;
this.yOffset = yCenter - rOuter;
this.rOuter = rOuter;
this.rInner = rInner;
this.angStart = start;
this.angSize = extend;
int fontSize = (int) ((rOuter - rInner) * 0.5);
setFont(new Font("Arial", Font.BOLD, fontSize));
this.addMouseListener(this);
setBorderPainted(false);
setContentAreaFilled(false);
calcShape();
calcText();
}
@Override
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
@Override
public int getHeight() {
return shape.getBounds().height;
}
@Override
public int getWidth() {
return shape.getBounds().width;
}
@Override
public int getX() {
return shape.getBounds().x;
}
@Override
public int getY() {
return shape.getBounds().y;
}
@Override
public void mouseClicked(MouseEvent me) {
}
@Override
public void mouseEntered(MouseEvent me) {
}
@Override
public void mouseExited(MouseEvent me) {
}
@Override
public void mousePressed(MouseEvent me) {
model.setArmed(true);
model.setPressed(true);
paintImmediately();
}
@Override
public void mouseReleased(MouseEvent me) {
model.setArmed(false);
model.setPressed(false);
paintImmediately();
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Paint oldPaint = g2.getPaint();
Stroke oldStroke = g2.getStroke();
Font oldFont = g2.getFont();
AffineTransform oldTrans = g2.getTransform();
FontMetrics fm = g2.getFontMetrics(getFont());
Rectangle2D tb = fm.getStringBounds(getText(), g2);
AffineTransform t = new AffineTransform();
t.setToTranslation(-1 * shape.getBounds().getX(), -1 * shape.getBounds().getY());
t.concatenate(oldTrans);
g2.setTransform(t);
if (getModel().isArmed() || getModel().isPressed())
g2.setPaint(Color.RED);
else
g2.setPaint(Color.GREEN);
g2.fill(shape);
g2.setStroke(new BasicStroke(4));
g2.setPaint(Color.WHITE);
g2.draw(shape);
g2.setPaint(Color.BLACK);
g2.setFont(getFont());
g2.drawString(getText(), (int) (xText + 2 - tb.getWidth() / 2),
(int) (yText + 2 + fm.getAscent() * 0.35));
g2.setPaint(Color.WHITE);
g2.drawString(getText(), (int) (xText - tb.getWidth() / 2), (int) (yText + fm.getAscent() * 0.35));
g2.setFont(oldFont);
g2.setStroke(oldStroke);
g2.setPaint(oldPaint);
}
protected void calcShape() {
Arc2D a = new Arc2D.Double(xOffset, yOffset, 2 * rOuter, 2 * rOuter, angStart, angSize,
Arc2D.PIE);
Ellipse2D e = new Ellipse2D.Double(xOffset + rOuter - rInner, yOffset + rOuter - rInner,
2 * rInner, 2 * rInner);
Area resShape = new Area(a);
resShape.subtract(new Area(e));
this.shape = resShape;
}
protected void calcText() {
double angle = angStart + angSize / 2;
double r = rInner + (rOuter - rInner) / 2;
double xOff = r * Math.sin(Math.toRadians(90 + angle));
double yOff = r * Math.cos(Math.toRadians(90 + angle));
xText = xCenter + (Math.abs(xOff) > 0.1 ? xOff : 0);
yText = yCenter + (Math.abs(yOff) > 0.1 ? yOff : 0);
}
protected void paintImmediately() {
Component c = SwingUtilities.getRoot(this);
Graphics g = c.getGraphics();
Graphics2D g2 = (Graphics2D) g;
Point p = SwingUtilities.convertPoint(this, new Point(0, 0), c);
AffineTransform oT = g2.getTransform();
AffineTransform t = new AffineTransform();
t.setToTranslation(p.getX(), p.getY());
g2.setTransform(t);
paintComponent(g);
g2.setTransform(oT);
}
private double xCenter;
private double yCenter;
private double xOffset;
private double yOffset;
private double rOuter;
private double rInner;
private double angStart;
private double angSize;
private double xText;
private double yText;
private Shape shape;
}
现在是Pane类:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestCirclePane extends JPanel {
public TestCirclePane(int w, int h) {
diameter = (int) (0.9 * (w < h ? w : h));
Dimension s = new Dimension(w, h);
xCenter = w / 2;
yCenter = h / 2;
setMinimumSize(s);
setPreferredSize(s);
setLayout(null);
createComponents(this);
}
protected void createComponents(JPanel p) {
double r = diameter / 2;
double w = r * 0.66;
double r3o = r;
double r3i = r - w * 0.5;
add(new MyCircleButton("B0", xCenter, yCenter, r3o, r3i, 135, 90));
add(new MyCircleButton("B1", xCenter, yCenter, r3o, r3i, 45, 90));
add(new MyCircleButton("B2", xCenter, yCenter, r3o, r3i, -45, 90));
add(new MyCircleButton("B3", xCenter, yCenter, r3o, r3i, 225, 90));
}
@Override
protected void paintComponent(Graphics arg0) {
super.paintComponent(arg0);
Graphics2D g2 = (Graphics2D) arg0;
Dimension d = getSize();
Paint oldPaint = g2.getPaint();
g2.setPaint(Color.WHITE);
g2.fill3DRect(0, 0, d.width, d.height, true);
g2.setPaint(Color.LIGHT_GRAY);
g2.drawLine(0, yCenter, d.width, yCenter);
g2.drawLine(xCenter, 0, xCenter, d.height);
g2.setPaint(oldPaint);
}
public static void createWindow() {
JFrame frame = new JFrame("Test - CircleButton");
JPanel client = new JPanel();
client.setLayout(new BorderLayout());
client.add(placeHolder("top"), BorderLayout.NORTH);
client.add(placeHolder("bottom"), BorderLayout.SOUTH);
client.add(placeHolder("left"), BorderLayout.WEST);
client.add(placeHolder("right"), BorderLayout.EAST);
client.add(new TestCirclePane(800, 600), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(client);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createWindow();
}
});
}
public static JComponent placeHolder(String s) {
JLabel l = new JLabel(s);
l.setPreferredSize(new Dimension(300, 300));
return l;
}
private int xCenter;
private int yCenter;
private int diameter;
private static final long serialVersionUID = 1L;
}
由于这个形状的按钮并不像一个普通的按钮 我需要添加一个鼠标监听器 为了给鼠标点击提供视觉反馈,我使用了以下方法 mousePressed()
和 mouseReleased()
.但当我的 paintComponent()
被从油漆链外调用,我意识到,它不知道自己(按钮)的来源。
所以我想,在我的 paintImmediately里面是有buttons paint概念的。因此,我准备了环境,就像superclass在正常的油漆操作上会做的那样。那么,我以为superclass会做什么呢?不知道--只是一个猜测。
所以,如果你认为我的编码是错误的,请告诉我,如何做得更好。
我终于找到了窍门=:O
我把创建形状的工作从面板移到了Layoutmanager中,这样就可以设置按钮的大小和位置,现在可以正常工作了。没有重载位置或大小获取器,也没有paintImmediately :)
谢谢你的帮助。