Wie kann man mit Graphics2D die Zeigerdrehung einer analogen Stoppuhr realisieren?
Das Beispiel definiert zwei Klassen: Die Hauptklasse mit
der main
-Methode stellt den
größten Teil des GUI in einem JFrame
mit BorderLayout
bereit. Hier werden drei JButton
zum Starten, Stoppen und Zurücksetzen der Uhr
deklariert und beim ActionListener
der
Klasse angemeldet.
Über die Buttons wird in
das Zentrum des Frames ein Objekt der Klasse UhrPanel
gesetzt. Es beinhaltet die eigentliche
Funktionalität. Um die zeitliche Steuerung zu
ermöglichen, implementiert die von JPanel
als Zeichenfläche abgeleitete Klasse das Interface
Runnable
. Die Klasse kann so als Thread
ausgeführt werden. Dies geschieht bereits bei der
Instanzierung der Klasse, indem in deren Konstruktor die
Methode start()
aufgerufen wird. Sie
erzeugt ggf. den Thread und startet ihn.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Stoppuhr implements ActionListener {
private JButton startButt, stopButt, resetButt;
private UhrPanel panel;
public Stoppuhr() {
init();
}
private void init() {
JFrame frame = new JFrame("Stopuhr");
panel = new UhrPanel();
panel.setRunning(false);
frame.add(panel, BorderLayout.CENTER);
startButt = new JButton("start");
startButt.addActionListener(this);
stopButt = new JButton("stop");
stopButt.addActionListener(this);
resetButt = new JButton("reset");
resetButt.addActionListener(this);
JPanel buttPanel = new JPanel(new FlowLayout());
buttPanel.add(startButt);
buttPanel.add(stopButt);
buttPanel.add(resetButt);
frame.add(buttPanel, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Stoppuhr());
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startButt) {
panel.setRunning(true);
}
if (e.getSource() == stopButt) {
panel.setRunning(false);
}
if (e.getSource() == resetButt) {
panel.setIndex(0);
panel.setRunning(false);
panel.repaint();
}
}
}
class UhrPanel extends JPanel implements Runnable {
public UhrPanel() {
start();
}
double winkel = Math.PI / -30;
int index = 0;
boolean running;
private Thread thread;
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
AffineTransform at = new AffineTransform();
at.setToScale(1, -1);
AffineTransform aff = new AffineTransform();
aff.setToTranslation(this.getWidth() / 2, this.getHeight() / 2);
at.preConcatenate(aff);
g2d.transform(at);
// Zifferzeichen
g2d.setColor(Color.RED);
Line2D.Double ziffer = new Line2D.Double(0, 60, 0, 70);
Shape zShape;
for (int i = 0; i < 61; i += 5) {
at.setToRotation(winkel * i);
zShape = at.createTransformedShape(ziffer);
g2d.draw(zShape);
}
// Zifferzeichen an den Positionen 12/3/6/9
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(3));
Line2D.Double viertel = new Line2D.Double(0, 60, 0, 70);
Shape qShape;
for (int i = 0; i < 61; i += 15) {
at.setToRotation(winkel * i);
qShape = at.createTransformedShape(viertel);
g2d.draw(qShape);
}
// Zeiger
g2d.setColor(Color.BLACK);
Line2D.Double line = new Line2D.Double(0, 0, 0, 50);
at.setToRotation(winkel * index, 0, 0);
Shape s = at.createTransformedShape(line);
g2d.draw(s);
}
public void start() {
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void setRunning(boolean running) {
this.running = running;
}
public void setIndex(int i) {
this.index = i;
}
public void run() {
while (true) {
if (running) {
index++;
index = index > 60 ? 1 : index;
repaint();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Die Methode run()
des Threads führt
eine Endlosschleife aus, in der die boolsche Variable running
abgefragt wird. Ist sie true
wird das Feld
index
incrementiert. Es kann Werte zwischen
1 und 60 annehmen und steuert den Winkel des
Sekundenzeigers. Dies wird erreicht, indem mittels repaint()
die Methode paintComponent()
wiederholt
aufgerufen wird. Sie dient dazu, das Panel neu zu
zeichnen und so u.a. auch index
jedes Mal
neu auszuwerten. Thread.sleep()
bewirkt die
Unterbrechung der Threadausführung innerhalb der
kontinuierlich ablaufenden while
-Schleife
für 1000 Millisekunden. Jede Sekunde wird somit index
hochgezählt und die Komponente mit diesem Wert neu
gezeichnet.
Schauen wir uns paintComponent(Graphics
g)
etwas genauer an. Das übergebene Graphics
-Objekt
wird als erstes in ein Graphics2D
-Objekt
gecastet. Auf diesem werden die nachfolgenden
Operationen ausgeführt.
Nach dem
Überzeichnen mit einer weißen
Grundfläche wird als erstes die Orientierung der
Y-Achse des der Zeichnung zugrunde liegenden
Koordinatensystems umgekehrt. Im Normalfalle
verläuft sie von oben nach unten, sodass in der
linken oberen Ecke des Zeichnungsfensters der Punkt 0|0
liegt und üblicherweise von oben nach unten
gezeichnet wird. Obwohl es natürlich auch so ginge,
soll der 0-Punkt hier in die Mitte der
Zeichenfläche gelegt und von unten nach oben
gezeichnet werden. Um das zu erreichen wird eine affine
Transformation durchgeführt, die durch die
gleichnamige Klasse realisiert werden kann. Ihre Methode
setToScale()
führt hier eine
Skalierung mit dem Faktor 1 (also keine) durch, deren
Y-Wert jedoch negativiert wird. Ein weiteres AffineTransform
-Objekt
mit einer Verschiebung des Initialisierungspunktes in
die Mitte des Panels wird erzeugt und mit dem ersten
verknüpft. Das Graphics2D
-Objekt wird
dann mit diesem Initialisierungspunkt versehen.
Die drei dann folgenden Blöcke sind recht
ähnlich.
- Eine Zeichenfarbe wird definiert
- Das zu zeichnende Objekt wird gebildet
- Die jeweils benötigte Rotation wird - ggf.
wiederholt in einer Schleife - dem
AffineTransform
-Objekt mitgeteilt - Ein
Shape
-Objekt wird erzeugt und schließlich gezeichnet
Im letzten Block findet sich das oben erwähnte Feld
index
wieder, das den Wnkel winkel
multipliziert und den Sekundenzeiger um diesen um den
Punkt 0|0 rotieren lässt.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.