如何用 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 基本上是三行,在垂直的中间和水平的末端相交。如果箭头是单线,你可能会得到更好的结果,但我会把它留给你玩

接下来,我们需要定位和旋转对象(arrowArrow 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);
        }

    }
}