使用 Graphics2D 更新旋转的 JLabel 会导致新旧文本合并在一起

Updating a rotated JLabel using Graphics2D causes old and new text to merge together

我正在尝试将显示当前时间的 JLabel 旋转 90 度 90 度。在做了一些研究之后,大多数人都推荐使用 Graphics2D 和 AffineTransform。这几乎可以工作,但是当更新时间中的分钟时,新数字似乎与旧数字合并。

这不会发生几秒钟。有人知道如何解决这个问题或有替代解决方案吗?

Driver class:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Driver extends JFrame implements KeyListener {

    private boolean running = true;
    ClockWidget clockWidget;
    static Dimension screenSize;


public static void main(String[] args) {
    screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    DisplayMode displayMode = new DisplayMode((int) screenSize.getWidth(), (int) screenSize.getHeight(), 32,
            DisplayMode.REFRESH_RATE_UNKNOWN);
    new Driver().run(displayMode);
}



public void run(DisplayMode displayMode) {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLayout(null);
    getContentPane().setBackground(Color.BLACK);
    setFont(new Font("Arial", Font.PLAIN, 24));

    Screen screen = new Screen();
    screen.setFullScreen(displayMode, this);

    initClockWidgit();

    addKeyListener(this);
    System.out.println("RUNNING");
    while (running) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    quitProgram(screen);
    return;
}

public void initClockWidgit() {
    clockWidget = new ClockWidget();
    clockWidget.setFont(new Font("Arial", Font.PLAIN, 36));
    clockWidget.setForeground(Color.WHITE);
    clockWidget.setBackground(Color.BLUE);
    clockWidget.setBounds((int) (screenSize.getWidth() * 0.90), (int) (screenSize.getHeight() * 0.10), 250, 100);

    add(clockWidget);
    new Thread(clockWidget).start();
}

public void quitProgram(Screen screen) {
    screen.restoreScreen();
    clockWidget.disable();
}

@Override
public void keyPressed(KeyEvent keyEvent) {
    int keyCode = keyEvent.getKeyCode();
    if (keyCode == KeyEvent.VK_SPACE) {
        running = false;
    }
    keyEvent.consume();
}

@Override
public void keyReleased(KeyEvent keyEvent) {
    keyEvent.consume();
}

@Override
public void keyTyped(KeyEvent keyEvent) {
    keyEvent.consume();
}
}

ClockWidget Class:

import java.text.SimpleDateFormat;
import java.util.Calendar;

import javax.swing.JLabel;

public class ClockWidget extends RotatedJLabel implements Runnable{

    private String currentTime;
    private boolean running;

    public ClockWidget() {
        running = true;
    }

    @Override
    public void run() {
        while(running) {
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss a");
            currentTime = simpleDateFormat.format(calendar.getTime());
            setText(currentTime);   
        }
    }

    public void disable() {
        running = false;
    }

}

RotatedJLabel Class:

[import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;

import javax.swing.Icon;
import javax.swing.JLabel;

public class RotatedJLabel extends JLabel {

    public RotatedJLabel() {
        super();
    }

    public RotatedJLabel(Icon image) {
        super(image);
    }

    public RotatedJLabel(Icon image, int horizontalAlignment) {
        super(image, horizontalAlignment);
    }

    public RotatedJLabel(String text) {
        super(text);
    }

    public RotatedJLabel(String text, Icon icon, int horizontalAlignment) {
        super(text, icon, horizontalAlignment);
    }

    public RotatedJLabel(String text, int horizontalAlignment) {
        super(text, horizontalAlignment);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
        AffineTransform aT = g2.getTransform();
        Shape oldshape = g2.getClip();
        double x = getWidth()/2.0;
        double y = getHeight()/2.0;
        aT.rotate(Math.toRadians(90), x, y);
        g2.setTransform(aT);
        g2.setClip(oldshape);
        super.paintComponent(g);
    }
}

有几件事让我印象深刻:

  • 我不会为此目的使用 JLabel,它是一个复杂的组件,从 JPanel 开始,简单地绘制文本会更简单。在我的测试中,当图形上下文旋转时,很难让调整大小提示正常工作。
  • 您没有管理组件的 "new" 大小调整提示,这在结合更复杂的布局时可能会成为一个问题,因为组件的宽度现在应该是高度,反之亦然
  • 相比 KeyListener
  • ,我更推荐 key bindings API
  • Swing 不是线程安全的,从 UI 的上下文之外更新 UI 可能会产生许多问题;而不是使用 Thread,您应该使用 Swing Timer,并且由于您可能真的只想更新秒数,运行 它的速度要慢得多。有关详细信息,请参阅 Concurrency in Swing and How to Use Swing Timers
  • CalendarDate 已被有效弃用。有关详细信息,请参阅 Standard Calendar

例如...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private RotatedLabel timeLabel;
        private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm:ss a");

        public TestPane() {
            setLayout(new GridBagLayout());
            timeLabel = new RotatedLabel(currentTime());
            add(timeLabel);

            Timer timer = new Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    timeLabel.setText(currentTime());
                }
            });
            timer.start();
        }

        public String currentTime() {
            LocalTime lt = LocalTime.now();
            return lt.format(formatter);
        }

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

    }

    public class RotatedLabel extends JPanel {

        private String text;

        public RotatedLabel() {
            super();
            setOpaque(false);
            setFont(UIManager.getDefaults().getFont("label.font"));
        }

        public RotatedLabel(String text) {
            this();
            this.text = text;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
            revalidate();
            repaint();
        }

        protected Dimension getTextBounds() {
            FontMetrics fm = getFontMetrics(getFont());
            return new Dimension(fm.stringWidth(text), fm.getHeight());
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = getTextBounds();
            return new Dimension(size.height, size.width);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
            AffineTransform aT = g2.getTransform();
            double x = getWidth() / 2.0;
            double y = getHeight() / 2.0;
            aT.rotate(Math.toRadians(90), x, y);
            g2.setTransform(aT);

            FontMetrics fm = g2.getFontMetrics();
            float xPos = (getWidth() - fm.stringWidth(getText())) / 2.0f;
            float yPos = ((getHeight() - fm.getHeight()) / 2.0f) + fm.getAscent();
            g2.drawString(text, xPos, yPos);
            g2.dispose();
        }
    }
}

现在,如果您 "absolutely, must, no questions asked" 使用像 Label 这样的组件,那么我建议您改用 JLayer

不幸的是,我没有时间更新我的示例以使用 JLayer,但它们使用了前身库 JXLayer

  • Java rotating non-square JPanel component
  • Is there any way I can rotate this 90 degrees?