在Java中绘制抗锯齿形状并旋转它们

4

背景

我是一个 Java 新手,今天开始学习它(使用 thenewboston.org)。我已经知道如何制作简单的窗口/表单/ GUI,如何绘制线条等。

我的目标是在 Java 中创建像这样的仪表:

enter image description here

这是我在.NET C# WPF中创建的仪表,现在我想将其重写为Java。
主要问题:
如何创建带有一定透明度并旋转的三角形或其他形状?
我尝试使用Graphics对象绘制类似以下的东西:
public void paint(Graphics g){
   g.drawLine(0, 0, 100, 100);
}

但我认为这是错误的方向,因为当我把东西放在图形上时,它只会停留在那里,我无法移动或旋转它。

我必须清除整个图形并重新绘制它才能进行“动画”,或者有更简单的方法吗?


编辑: 我已经知道如何进行抗锯齿处理了(Hovercraft Full Of Eels 已经帮助我解决了这个问题 - 谢谢)。


编辑2:

我的代码实际上长这样:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainWindow extends JPanel {

    private Point p1 = new Point(100, 100);
    private Point p2 = new Point(740, 450);

    public MainWindow() {
        this.setPreferredSize(new Dimension(800, 600));
    }

    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);
        drawLines(g);
    }

    private void drawLines(Graphics g)
    {
        Graphics2D g2d = (Graphics2D) g;

        g2d.setColor(Color.DARK_GRAY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);     
        g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));

        g.drawLine(p1.x, p1.y, p2.x, p2.y);

    }

    private void display() {
        JFrame f = new JFrame("Main Window");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {

        new MainWindow().display();
    }

}

可能会有帮助 http://stackoverflow.com/questions/8313526/graphics2d-rotating-shapes-on-a-graphics2d-object - kyticka
2个回答

5
您说:

您声明:

I tried to draw something by using Graphics object like this:

public void paint(Graphics g){
   g.drawLine(0, 0, 100, 100);
}

But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.

I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?

Suggestions:
  • 不要硬编码数字。使用类字段(变量)代替,这样您的程序可以轻松更改绘制项的位置。
  • 不要覆盖组件的paint(...)方法。而是覆盖从JComponent派生的对象或其子对象(如JPanel)的paintComponent(Graphics g)方法。这将使您受益于自动双缓冲以获得更平滑的动画,并减少组件的子元素或边框错误绘制的可能性。
  • 将您的Graphics对象强制转换为Graphics2D对象,以便您可以使用实现Shape接口的类进行更高级别的绘制,包括Rectangle2D、Ellipse2D、Line2D、Path2D等等。
  • 使用Graphics#drawImage(...)方法将背景图像绘制为BufferedImage,然后再使用Graphics2D对象在其上绘制移动图像,并根据对象的状态(其字段所持有的值)再次更改绘制的图像。
  • 在进行动画时,请注意遵守Swing线程规则,不要有任何占用Swing线程的动画或游戏循环。Swing计时器可以让您创建快速、简单(尽管有些原始)的游戏循环。

例如:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class DailAnimation extends JPanel {
   private static final int PREF_W = 400;
   private static final int PREF_H = 350;
   private static final Point2D CENTER = new Point2D.Double(PREF_W / 2.0,
         PREF_W / 2.0);
   private static final double RADIUS = PREF_W / 2.0;
   private static final Color LARGE_TICK_COLOR = Color.green;
   private static final Color CENTER_HUB_COLOR = Color.LIGHT_GRAY;
   private static final Stroke LARGE_TICK_STROKE = new BasicStroke(3f);
   private static final int LRG_TICK_COUNT = 9;
   private static final double TOTAL_LRG_TICKS = 12;
   private static final double LRG_TICK_OUTER_RAD = 0.9;
   private static final double LRG_TICK_INNER_RAD = 0.8;
   private static final int START_TICK = 10;
   private static final double CENTER_HUB_RADIUS = 10;
   public static final int MAX_SPEED = 100;
   private static final double INIT_SPEED = 0;
   private static final double DIAL_INNER_RAD = 0.02;
   private static final double DIAL_OUTER_RAD = 0.75;
   private static final Color DIAL_COLOR = Color.DARK_GRAY;
   private BufferedImage backgroundImg;

   private double speed;
   private double theta;
   private double cosTheta;
   private double sinTheta;

   public DailAnimation() {
      setBackground(Color.white);

      backgroundImg = createBackgroundImg();
      setSpeed(INIT_SPEED);
   }

   public void setSpeed(double speed) {
      if (speed < 0) {
         speed = 0;
      } else if (speed > MAX_SPEED) {
         speed = MAX_SPEED;
      }
      this.speed = speed;
      this.theta = ((speed / MAX_SPEED) * LRG_TICK_COUNT * 2.0 + START_TICK)
            * Math.PI / TOTAL_LRG_TICKS;
      cosTheta = Math.cos(theta);
      sinTheta = Math.sin(theta);

      repaint();
   }

   private BufferedImage createBackgroundImg() {
      BufferedImage img = new BufferedImage(PREF_W, PREF_H,
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();

      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(LARGE_TICK_COLOR);
      g2.setStroke(LARGE_TICK_STROKE);

      for (int i = 0; i < LRG_TICK_COUNT; i++) {
         double theta = (i * 2.0 + START_TICK) * Math.PI / TOTAL_LRG_TICKS;
         double cosTheta = Math.cos(theta);
         double sinTheta = Math.sin(theta);

         int x1 = (int) (LRG_TICK_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
         int y1 = (int) (LRG_TICK_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
         int x2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
         int y2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());

         g2.drawLine(x1, y1, x2, y2);
      }

      g2.setColor(CENTER_HUB_COLOR);

      int x = (int) (CENTER.getX() - CENTER_HUB_RADIUS);
      int y = (int) (CENTER.getY() - CENTER_HUB_RADIUS);
      int width = (int) (2 * CENTER_HUB_RADIUS);
      int height = width;
      g2.fillOval(x, y, width, height);
      // g2.draw(ellipse);

      g2.dispose();
      return img;
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (backgroundImg != null) {
         g.drawImage(backgroundImg, 0, 0, this);
      }

      Graphics2D g2 = (Graphics2D) g;
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

      g.setColor(DIAL_COLOR);
      int x1 = (int) (DIAL_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
      int y1 = (int) (DIAL_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
      int x2 = (int) (DIAL_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
      int y2 = (int) (DIAL_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());

      g.drawLine(x1, y1, x2, y2);

   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private static void createAndShowGui() {
      final DailAnimation mainPanel = new DailAnimation();

      JFrame frame = new JFrame("DailAnimation");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);

      int delay = 100;
      new Timer(delay, new ActionListener() {
         int speed = 0;

         @Override
         public void actionPerformed(ActionEvent evt) {
            speed ++;
            if (speed > DailAnimation.MAX_SPEED) {
               ((Timer)evt.getSource()).stop();
            }
            mainPanel.setSpeed(speed);
         }
      }).start();
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

谢谢。我没有硬编码数字,那只是简化的代码。我不确定如何消除覆盖组件绘制方法的问题。 - Kamil
@Kamil:你不应该抛弃它。 - Hovercraft Full Of Eels
我的意思是,我不知道如何将您回复的这部分应用到我的代码中:“覆盖JComponent或其子类(例如JPanel)派生的对象的paintComponent(Graphics g)方法”。 - Kamil
你的意思是我应该把我的线条和形状放在某个子组件上,而不是直接在“主窗口”上绘制吗?我更新了我的问题并添加了一些代码。 - Kamil
我将“分辨率”(MAX_SPEED)更改为1024以获得平滑的运动,并将计时器减少到1。它预计在大约1秒钟内达到最大值,但实际上需要更长时间。这是因为Java速度慢,还是我漏掉了什么? - Kamil

0

通过Graphics.drawLine()绘制一条线会直接写入像素到Graphics实例的背景中。如果你想要旋转这条线,你必须计算出它旋转后的坐标。这是在AWT和Swing中绘制图形的唯一方法。

你可以编写一个指针类来维护它的角度,并让它在每一帧中处理渲染。


我可以做到,没有问题,但是我应该使用 Graphics 并清除之前绘制的形状并每次重新绘制它们吗? - Kamil
1
@Kamil:有一些图像将会保留——例如表盘,应该在BufferedImage中绘制;还有一些图像的部分将会改变,例如指针,可以每次重新绘制。 - Hovercraft Full Of Eels
1
是的,如果你自己处理表单的绘制,通常会用背景颜色的矩形或基于图像的背景来填充它,然后再次绘制所有动态几何图形。你需要确保你所绘制的内容是双缓冲的,否则会出现闪烁。 - Chris Bode
2
@Kamil:此外,您需要将Graphics对象转换为Graphics2D对象,并使用其setRenderingHint(...)方法进行抗锯齿处理:g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - Hovercraft Full Of Eels
@HovercraftFullOfEels 我明白我不必重新绘制静态的东西,比如表盘等,现在我想专注于动画指针。感谢您提供的抗锯齿提示,我会搜索RenderingHints,我认为这已经足够了。 - Kamil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接