Updating a rotated JLabel using Graphics2D causes

2019-08-31 05:08发布

问题:

I am trying to rotate a JLabel 90 degrees that shows the current time 90 degrees. After doing some research, most people have recommended using Graphics2D and AffineTransform. This almost works, but when the minute in the time is updated, the new digit appears to merge with the old digit.

This does not happen for the seconds. Does anybody have any idea how to fix this issue or have an alternate solution?

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);
    }
}

回答1:

A few things jump out at me:

  • I wouldn't use a JLabel for this purpose, it's a complicate component, starting with a JPanel and simply painting the text would be simpler. In my testing it was very hard to get the sizing hints to work correctly when the graphics context was rotated.
  • You're not managing the component's "new" sizing hints, this could be an issue when coupled with more complex layouts, as the width of the component should now be the height and visa-versa
  • I'd recommend the key bindings API over KeyListener
  • Swing is NOT thread safe, updating the UI from outside the context of the UI could produce any number of issues; instead of using a Thread, you should be using a Swing Timer, and since you probably really only want to update the seconds, running it at a much slower speed. See Concurrency in Swing and How to Use Swing Timers for more details
  • And Calendar and Date are effectively deprecated. See Standard Calendar for more details

For example...

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();
        }
    }
}

Now, if you "absolutely, must, no questions asked" use a component like Label, then I recommend using JLayer instead.

Unfourtantly, I've not had time to update my examples to use JLayer, but they use the predecessor library, JXLayer

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