如何用 Java Swing 绘制二维箭头?
How to draw 2d arrows with Java Swing?
我正在尝试用 Swing 2d 绘制一些可旋转的箭头,网上有一些示例代码,所以我将它们复制并组合到一个应用程序中,但是这 3 种方法中的每一种都有问题:第一种没有从它的中心旋转,其他 2 个在箭头中看起来不正确,有人可以告诉我如何修复它们吗?
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class Arrow_Test extends JPanel implements ChangeListener {
Path2D.Double arrow = createArrow();
double theta = 0;
public void stateChanged(ChangeEvent e) {
int value = ((JSlider) e.getSource()).getValue();
theta = Math.toRadians(value);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(6));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
AffineTransform at = AffineTransform.getTranslateInstance(cx, cy);
at.rotate(theta);
at.scale(2.0, 2.0);
Shape shape = at.createTransformedShape(arrow);
g2.setPaint(Color.blue);
g2.draw(shape);
GeneralPath a = drawArrow(20, 20, 30, 20, 50, 13);
AffineTransform at1 = AffineTransform.getTranslateInstance(cx, cy);
at1.rotate(theta);
at1.scale(2.0, 2.0);
Shape shape1 = at1.createTransformedShape(a);
g2.setPaint(Color.blue);
g2.draw(shape1);
drawArrow(100, 100, 50, 0, g2);
}
private Path2D.Double createArrow() {
int length = 80;
int barb = 15;
double angle = Math.toRadians(20);
Path2D.Double path = new Path2D.Double();
path.moveTo(-length / 2, 0);
path.lineTo(length / 2, 0);
double x = length / 2 - barb * Math.cos(angle);
double y = barb * Math.sin(angle);
path.lineTo(x, y);
x = length / 2 - barb * Math.cos(-angle);
y = barb * Math.sin(-angle);
path.moveTo(length / 2, 0);
path.lineTo(x, y);
return path;
}
GeneralPath drawArrow(int x1, int y1, int x2, int y2, double length,
double width) {
double x, y;
length = 50;
width = 5;
Point2D start = new Point2D.Double(x1, y1);
Point2D end = new Point2D.Double(x2, y2);
Point2D base = new Point2D.Double();
Point2D back1 = new Point2D.Double();
Point2D back2 = new Point2D.Double();
length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
// Compute normalized line vector
x = (x2 - x1) / length;
y = (y2 - y1) / length;
// Compute points for arrow head
base.setLocation(x2 - x * length, y2 - y * length);
back1.setLocation(base.getX() - width * y, base.getY() + width * x);
back2.setLocation(base.getX() + length * y, base.getY() - width * x);
Line2D.Double l1 = new Line2D.Double(start, end);
Line2D.Double l2 = new Line2D.Double(end, back2);
Line2D.Double l3 = new Line2D.Double(end, back1);
GeneralPath c = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
c.append(l1, true);
c.append(l2, true);
c.append(l3, true);
return c;
}
private void drawArrow(int x1, int y1, int x2, int y2, Graphics2D g2d) {
int x[] = { 0, 36, 0 };
int y[] = { -10, 0, 10 };
g2d.rotate(theta);
g2d.drawLine(x1 - 20, y1, x1 + 20, y1);
// will move the orgin
g2d.translate(x1, y1);
double angle = findLineAngle(x1 - 20, y1, x1 + 20, y1);
System.out.println("angle is===>" + angle);
g2d.rotate(angle);
g2d.fillPolygon(new Polygon(x, y, 3));
// /will restore orgin
g2d.translate(-x2, -y2);
g2d.rotate(-angle);
}
private double findLineAngle(int x1, int y1, int x2, int y2) {
if ((x2 - x1) == 0)
return Math.PI / 2;
return Math.atan((y2 - y1) / (x2 - x1));
}
private JSlider getSlider() {
JSlider slider = new JSlider(-180, 180, 0);
slider.addChangeListener(this);
return slider;
}
public static void main(String[] args) {
Arrow_Test test = new Arrow_Test();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(test);
f.add(test.getSlider(), "Last");
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
我真的不想进入 "why",因为你的代码很难读。
旋转对象时,您应该指定旋转所围绕的锚点 (x/y)。默认情况下,这是当前上下文的 0x0
位置。
为什么你的基于 "path" 的箭头看起来...很有趣,可能与它们的创建方式有关,但我并没有真正使用它们。
你需要注意的另一件事是,转换是复合的,这是好事和坏事,你只需要小心处理它们;)
让我们从基本形状开始...
public class Arrow extends Path2D.Double {
public Arrow() {
moveTo(0, 10);
lineTo(36, 10);
moveTo(36 - 16, 0);
lineTo(36, 10);
moveTo(36 - 16, 20);
lineTo(36, 10);
}
}
好吧,没什么了不起的,您可以添加 width/height 参数使箭头以您想要的方式显示,但这是一个基本的开始。我更喜欢使用基于 Shape
的对象,它们比旧的 Polygon
样式更易于使用 API.
Arrow
基本上是三行,在垂直的中间和水平的末端相交。如果箭头是单线,你可能会得到更好的结果,但我会把它留给你玩
接下来,我们需要定位和旋转对象(arrow
是Arrow
BTW的一个实例)
double x = (getWidth() - arrow.getBounds().getWidth()) / 2d;
double y = (getHeight() - arrow.getBounds().getHeight()) / 2d;
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.rotate(theta, arrow.getBounds().getWidth() / 2d, arrow.getBounds().getHeight() / 2d);
g2d.setTransform(at);
g2d.draw(arrow);
我们首先应用翻译,这使得 Graphics
上下文的 0x0
位置现在是我们指定的 x/y
位置。这使得计算箭头应围绕其旋转的锚点位置变得更加容易...
还有一个可运行的示例将它们绑定在一起
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Arrow_Test extends JPanel implements ChangeListener {
double theta = 0;
Path2D arrow = new Arrow();
public void stateChanged(ChangeEvent e) {
int value = ((JSlider) e.getSource()).getValue();
theta = Math.toRadians(value);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setStroke(new BasicStroke(4));
double x = (getWidth() - arrow.getBounds().getWidth()) / 2d;
double y = (getHeight() - arrow.getBounds().getHeight()) / 2d;
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.rotate(theta, arrow.getBounds().getWidth() / 2d, arrow.getBounds().getHeight() / 2d);
g2d.setTransform(at);
g2d.draw(arrow);
g2d.dispose();
}
private JSlider getSlider() {
JSlider slider = new JSlider(-180, 180, 0);
slider.addChangeListener(this);
return slider;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Arrow_Test test = new Arrow_Test();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(test);
f.add(test.getSlider(), "Last");
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
public class Arrow extends Path2D.Double {
public Arrow() {
moveTo(0, 10);
lineTo(36, 10);
moveTo(36 - 16, 0);
lineTo(36, 10);
moveTo(36 - 16, 20);
lineTo(36, 10);
}
}
}
我正在尝试用 Swing 2d 绘制一些可旋转的箭头,网上有一些示例代码,所以我将它们复制并组合到一个应用程序中,但是这 3 种方法中的每一种都有问题:第一种没有从它的中心旋转,其他 2 个在箭头中看起来不正确,有人可以告诉我如何修复它们吗?
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class Arrow_Test extends JPanel implements ChangeListener {
Path2D.Double arrow = createArrow();
double theta = 0;
public void stateChanged(ChangeEvent e) {
int value = ((JSlider) e.getSource()).getValue();
theta = Math.toRadians(value);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(6));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
AffineTransform at = AffineTransform.getTranslateInstance(cx, cy);
at.rotate(theta);
at.scale(2.0, 2.0);
Shape shape = at.createTransformedShape(arrow);
g2.setPaint(Color.blue);
g2.draw(shape);
GeneralPath a = drawArrow(20, 20, 30, 20, 50, 13);
AffineTransform at1 = AffineTransform.getTranslateInstance(cx, cy);
at1.rotate(theta);
at1.scale(2.0, 2.0);
Shape shape1 = at1.createTransformedShape(a);
g2.setPaint(Color.blue);
g2.draw(shape1);
drawArrow(100, 100, 50, 0, g2);
}
private Path2D.Double createArrow() {
int length = 80;
int barb = 15;
double angle = Math.toRadians(20);
Path2D.Double path = new Path2D.Double();
path.moveTo(-length / 2, 0);
path.lineTo(length / 2, 0);
double x = length / 2 - barb * Math.cos(angle);
double y = barb * Math.sin(angle);
path.lineTo(x, y);
x = length / 2 - barb * Math.cos(-angle);
y = barb * Math.sin(-angle);
path.moveTo(length / 2, 0);
path.lineTo(x, y);
return path;
}
GeneralPath drawArrow(int x1, int y1, int x2, int y2, double length,
double width) {
double x, y;
length = 50;
width = 5;
Point2D start = new Point2D.Double(x1, y1);
Point2D end = new Point2D.Double(x2, y2);
Point2D base = new Point2D.Double();
Point2D back1 = new Point2D.Double();
Point2D back2 = new Point2D.Double();
length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
// Compute normalized line vector
x = (x2 - x1) / length;
y = (y2 - y1) / length;
// Compute points for arrow head
base.setLocation(x2 - x * length, y2 - y * length);
back1.setLocation(base.getX() - width * y, base.getY() + width * x);
back2.setLocation(base.getX() + length * y, base.getY() - width * x);
Line2D.Double l1 = new Line2D.Double(start, end);
Line2D.Double l2 = new Line2D.Double(end, back2);
Line2D.Double l3 = new Line2D.Double(end, back1);
GeneralPath c = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
c.append(l1, true);
c.append(l2, true);
c.append(l3, true);
return c;
}
private void drawArrow(int x1, int y1, int x2, int y2, Graphics2D g2d) {
int x[] = { 0, 36, 0 };
int y[] = { -10, 0, 10 };
g2d.rotate(theta);
g2d.drawLine(x1 - 20, y1, x1 + 20, y1);
// will move the orgin
g2d.translate(x1, y1);
double angle = findLineAngle(x1 - 20, y1, x1 + 20, y1);
System.out.println("angle is===>" + angle);
g2d.rotate(angle);
g2d.fillPolygon(new Polygon(x, y, 3));
// /will restore orgin
g2d.translate(-x2, -y2);
g2d.rotate(-angle);
}
private double findLineAngle(int x1, int y1, int x2, int y2) {
if ((x2 - x1) == 0)
return Math.PI / 2;
return Math.atan((y2 - y1) / (x2 - x1));
}
private JSlider getSlider() {
JSlider slider = new JSlider(-180, 180, 0);
slider.addChangeListener(this);
return slider;
}
public static void main(String[] args) {
Arrow_Test test = new Arrow_Test();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(test);
f.add(test.getSlider(), "Last");
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
我真的不想进入 "why",因为你的代码很难读。
旋转对象时,您应该指定旋转所围绕的锚点 (x/y)。默认情况下,这是当前上下文的 0x0
位置。
为什么你的基于 "path" 的箭头看起来...很有趣,可能与它们的创建方式有关,但我并没有真正使用它们。
你需要注意的另一件事是,转换是复合的,这是好事和坏事,你只需要小心处理它们;)
让我们从基本形状开始...
public class Arrow extends Path2D.Double {
public Arrow() {
moveTo(0, 10);
lineTo(36, 10);
moveTo(36 - 16, 0);
lineTo(36, 10);
moveTo(36 - 16, 20);
lineTo(36, 10);
}
}
好吧,没什么了不起的,您可以添加 width/height 参数使箭头以您想要的方式显示,但这是一个基本的开始。我更喜欢使用基于 Shape
的对象,它们比旧的 Polygon
样式更易于使用 API.
Arrow
基本上是三行,在垂直的中间和水平的末端相交。如果箭头是单线,你可能会得到更好的结果,但我会把它留给你玩
接下来,我们需要定位和旋转对象(arrow
是Arrow
BTW的一个实例)
double x = (getWidth() - arrow.getBounds().getWidth()) / 2d;
double y = (getHeight() - arrow.getBounds().getHeight()) / 2d;
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.rotate(theta, arrow.getBounds().getWidth() / 2d, arrow.getBounds().getHeight() / 2d);
g2d.setTransform(at);
g2d.draw(arrow);
我们首先应用翻译,这使得 Graphics
上下文的 0x0
位置现在是我们指定的 x/y
位置。这使得计算箭头应围绕其旋转的锚点位置变得更加容易...
还有一个可运行的示例将它们绑定在一起
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Arrow_Test extends JPanel implements ChangeListener {
double theta = 0;
Path2D arrow = new Arrow();
public void stateChanged(ChangeEvent e) {
int value = ((JSlider) e.getSource()).getValue();
theta = Math.toRadians(value);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setStroke(new BasicStroke(4));
double x = (getWidth() - arrow.getBounds().getWidth()) / 2d;
double y = (getHeight() - arrow.getBounds().getHeight()) / 2d;
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.rotate(theta, arrow.getBounds().getWidth() / 2d, arrow.getBounds().getHeight() / 2d);
g2d.setTransform(at);
g2d.draw(arrow);
g2d.dispose();
}
private JSlider getSlider() {
JSlider slider = new JSlider(-180, 180, 0);
slider.addChangeListener(this);
return slider;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Arrow_Test test = new Arrow_Test();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(test);
f.add(test.getSlider(), "Last");
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
public class Arrow extends Path2D.Double {
public Arrow() {
moveTo(0, 10);
lineTo(36, 10);
moveTo(36 - 16, 0);
lineTo(36, 10);
moveTo(36 - 16, 20);
lineTo(36, 10);
}
}
}