Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

jewelsea commented May 12, 2012

@jewelsea

This comment has been minimized.

Copy link
Owner Author

jewelsea commented Aug 18, 2012

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

This comment has been minimized.

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

This comment has been minimized.

Copy link

ProfessorX commented Jun 29, 2018

VERY GOOD. WORTH REVIEWING.

@antic-ml

This comment has been minimized.

Copy link

antic-ml commented Nov 20, 2019

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

@antic-ml

This comment has been minimized.

Copy link

antic-ml commented Nov 22, 2019

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

This comment has been minimized.

Copy link
Owner Author

jewelsea commented Dec 12, 2019

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

This comment has been minimized.

Copy link

antic-ml commented Dec 12, 2019

@Amkaaa

This comment has been minimized.

Copy link

Amkaaa commented Mar 6, 2020

This is amazing though, good Job

@NayanaMadhuwantha

This comment has been minimized.

Copy link

NayanaMadhuwantha commented Mar 26, 2020

Super!!! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.