Skip to content

Instantly share code, notes, and snippets.

@jewelsea
Created May 11, 2012 08:52
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save jewelsea/2658491 to your computer and use it in GitHub Desktop.
Save jewelsea/2658491 to your computer and use it in GitHub Desktop.
Sample of an animated clock in JavaFX
/** clock.css (place input same directory as Clock.java and ensure build script copies the file to the same location as Clock.class) */
.root {
-fx-background-color: linear-gradient(to bottom, cornsilk, wheat);
-fx-padding: 12;
-fx-background-insets: 5;
-fx-effect: dropshadow(three-pass-box, wheat, 10, 0, 0, 0);
}
#face {
-fx-fill: radial-gradient(radius 180%, burlywood, derive(burlywood, -30%), derive(burlywood, 30%));
-fx-stroke: derive(burlywood, -45%);
-fx-stroke-width: 5;
-fx-effect: dropshadow(three-pass-box, grey, 10, 0, 4, 4);
}
#brand {
-fx-font-size: 14px;
}
#hourHand {
-fx-stroke: darkslategray;
-fx-stroke-width: 4;
-fx-stroke-line-cap: round;
}
#minuteHand {
-fx-stroke: derive(darkslategray, -5%);
-fx-stroke-width: 3;
-fx-stroke-line-cap: round;
}
#secondHand {
-fx-stroke: derive(firebrick, -15%);
-fx-stroke-width: 2;
-fx-stroke-line-cap: round;
}
#spindle {
-fx-fill: derive(darkslategray, +5%);
}
#digitalClock {
-fx-font-size: 14px;
-fx-font-family: 'Courier New';
}
.tick {
-fx-stroke: derive(darkgoldenrod, -15%);
-fx-stroke-width: 3;
-fx-stroke-line-cap: round;
}
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
import javafx.stage.*;
import javafx.util.Duration;
import java.util.*;
/** Note that this clock does not keep perfect time, but is close.
It's main purpose is to demonstrate various features of JavaFX. */
public class Clock extends Application {
public static void main(String[] args) throws Exception { launch(args); }
public void start(final Stage stage) throws Exception {
// construct the analogueClock pieces.
final Circle face = new Circle(100, 100, 100);
face.setId("face");
final Label brand = new Label("Splotch");
brand.setId("brand");
brand.layoutXProperty().bind(face.centerXProperty().subtract(brand.widthProperty().divide(2)));
brand.layoutYProperty().bind(face.centerYProperty().add(face.radiusProperty().divide(2)));
final Line hourHand = new Line(0, 0, 0, -50);
hourHand.setTranslateX(100); hourHand.setTranslateY(100);
hourHand.setId("hourHand");
final Line minuteHand = new Line(0, 0, 0, -75);
minuteHand.setTranslateX(100); minuteHand.setTranslateY(100);
minuteHand.setId("minuteHand");
final Line secondHand = new Line(0, 15, 0, -88);
secondHand.setTranslateX(100); secondHand.setTranslateY(100);
secondHand.setId("secondHand");
final Circle spindle = new Circle(100, 100, 5);
spindle.setId("spindle");
Group ticks = new Group();
for (int i = 0; i < 12; i++) {
Line tick = new Line(0, -83, 0, -93);
tick.setTranslateX(100); tick.setTranslateY(100);
tick.getStyleClass().add("tick");
tick.getTransforms().add(new Rotate(i * (360 / 12)));
ticks.getChildren().add(tick);
}
final Group analogueClock = new Group(face, brand, ticks, spindle, hourHand, minuteHand, secondHand);
// construct the digitalClock pieces.
final Label digitalClock = new Label();
digitalClock.setId("digitalClock");
// determine the starting time.
Calendar calendar = GregorianCalendar.getInstance();
final double seedSecondDegrees = calendar.get(Calendar.SECOND) * (360 / 60);
final double seedMinuteDegrees = (calendar.get(Calendar.MINUTE) + seedSecondDegrees / 360.0) * (360 / 60);
final double seedHourDegrees = (calendar.get(Calendar.HOUR) + seedMinuteDegrees / 360.0) * (360 / 12) ;
// define rotations to map the analogueClock to the current time.
final Rotate hourRotate = new Rotate(seedHourDegrees);
final Rotate minuteRotate = new Rotate(seedMinuteDegrees);
final Rotate secondRotate = new Rotate(seedSecondDegrees);
hourHand.getTransforms().add(hourRotate);
minuteHand.getTransforms().add(minuteRotate);
secondHand.getTransforms().add(secondRotate);
// the hour hand rotates twice a day.
final Timeline hourTime = new Timeline(
new KeyFrame(
Duration.hours(12),
new KeyValue(
hourRotate.angleProperty(),
360 + seedHourDegrees,
Interpolator.LINEAR
)
)
);
// the minute hand rotates once an hour.
final Timeline minuteTime = new Timeline(
new KeyFrame(
Duration.minutes(60),
new KeyValue(
minuteRotate.angleProperty(),
360 + seedMinuteDegrees,
Interpolator.LINEAR
)
)
);
// move second hand rotates once a minute.
final Timeline secondTime = new Timeline(
new KeyFrame(
Duration.seconds(60),
new KeyValue(
secondRotate.angleProperty(),
360 + seedSecondDegrees,
Interpolator.LINEAR
)
)
);
// the digital clock updates once a second.
final Timeline digitalTime = new Timeline(
new KeyFrame(Duration.seconds(0),
new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent actionEvent) {
Calendar calendar = GregorianCalendar.getInstance();
String hourString = pad(2, '0', calendar.get(Calendar.HOUR) == 0 ? "12" : calendar.get(Calendar.HOUR) + "");
String minuteString = pad(2, '0', calendar.get(Calendar.MINUTE) + "");
String secondString = pad(2, '0', calendar.get(Calendar.SECOND) + "");
String ampmString = calendar.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM";
digitalClock.setText(hourString + ":" + minuteString + ":" + secondString + " " + ampmString);
}
}
),
new KeyFrame(Duration.seconds(1))
);
// time never ends.
hourTime.setCycleCount(Animation.INDEFINITE);
minuteTime.setCycleCount(Animation.INDEFINITE);
secondTime.setCycleCount(Animation.INDEFINITE);
digitalTime.setCycleCount(Animation.INDEFINITE);
// start the analogueClock.
digitalTime.play();
secondTime.play();
minuteTime.play();
hourTime.play();
stage.initStyle(StageStyle.TRANSPARENT);
// add a glow effect whenever the mouse is positioned over the clock.
final Glow glow = new Glow();
analogueClock.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
analogueClock.setEffect(glow);
}
});
analogueClock.setOnMouseExited(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
analogueClock.setEffect(null);
}
});
// fade out the scene and shut it down when the mouse is clicked on the clock.
analogueClock.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
analogueClock.setMouseTransparent(true);
FadeTransition fade = new FadeTransition(Duration.seconds(1.2), analogueClock);
fade.setOnFinished(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent actionEvent) {
stage.close();
}
});
fade.setFromValue(1);
fade.setToValue(0);
fade.play();
}
});
// layout the scene.
final VBox layout = new VBox();
layout.getChildren().addAll(analogueClock, digitalClock);
layout.setAlignment(Pos.CENTER);
final Scene scene = new Scene(layout, Color.TRANSPARENT);
scene.getStylesheets().add(getResource("clock.css"));
stage.setScene(scene);
// allow the clock background to be used to drag the clock around.
final Delta dragDelta = new Delta();
layout.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = stage.getX() - mouseEvent.getScreenX();
dragDelta.y = stage.getY() - mouseEvent.getScreenY();
scene.setCursor(Cursor.MOVE);
}
});
layout.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
scene.setCursor(Cursor.HAND);
}
});
layout.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
stage.setX(mouseEvent.getScreenX() + dragDelta.x);
stage.setY(mouseEvent.getScreenY() + dragDelta.y);
}
});
layout.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
scene.setCursor(Cursor.HAND);
}
}
});
layout.setOnMouseExited(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
scene.setCursor(Cursor.DEFAULT);
}
}
});
// show the scene.
stage.show();
}
private String pad(int fieldWidth, char padChar, String s) {
StringBuilder sb = new StringBuilder();
for (int i = s.length(); i < fieldWidth; i++) {
sb.append(padChar);
}
sb.append(s);
return sb.toString();
}
static String getResource(String path) {
return Clock.class.getResource(path).toExternalForm();
}
// records relative x and y co-ordinates.
class Delta { double x, y; }
}
@jewelsea
Copy link
Author

@jewelsea
Copy link
Author

In response to Per Lundholm's criticism of the coding style used in this example, I also created a refactored version of this Clock demonstration code which follows the principles espoused in Per's blog entry on JavaFX coding style. Should you have time, I'd appreciate it if you can view both this code version and the refactored version and comment on Per's blogs as to which coding style you prefer and why.

@Ovakefali13
Copy link

Ovakefali13 commented Apr 19, 2018

Hello,
I just want to thank you very much. This example really helped me to understand the structure of a clock and the basics of rotation.
I mean it's now nearly six years ago you posted this, but hey thank you so much!!

@ProfessorX
Copy link

VERY GOOD. WORTH REVIEWING.

@antic-ml
Copy link

I was searching Google for JavaFX clock implementations and yours is the best I found.

@antic-ml
Copy link

I forked this Gist and added the following features to the clock:

  • Time zone location
  • Latitude & longitude geo coordinates
  • Day of the week
  • Latest weather reports

It is available here: JavaFXClock

@jewelsea
Copy link
Author

That is a nice feature set @antic-ml. The refactored code, may have been a better starting point for the fork as that code is better organized.

@antic-ml
Copy link

antic-ml commented Dec 12, 2019 via email

@Amkaaa
Copy link

Amkaaa commented Mar 6, 2020

This is amazing though, good Job

@NayanaMadhuwantha
Copy link

Super!!! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment