Javafx : How can I produce an animation with 3D path? - animation

I'm new to JavaFX, and I encountered a problem when trying to deal with animation.
I know class PathTransition provides methods to move a node between two points along an arbitrary curve by class Path; but it seems that all the classes that are related to PathTransition, like Path and MoveTo and CubicCurveTo and including itself, can only work in the xy plane. What if I want to move a node in the yz plane or xz plane? I just can't find any information about it on the internet. Any advice would be appreciated.

As shown in Animation Basics, Animations, you can compose multiple kinds of Transition, including PathTransition, in a SequentialTransition or ParallelTransition. The approach is especially convenient when the equation of motion can be expressed in parametric form. Motion along a helix, shown below, uses a ParallelTransition to combine a PathTransition along a Circle with a Timeline along a line.
animation = new ParallelTransition(
createTransition(circle, arrow),
createTimeline(size / 2));
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.PathTransition;
import javafx.animation.PathTransition.OrientationType;
import javafx.animation.Timeline;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.effect.Bloom;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* #see http://stackoverflow.com/a/37370840/230513
*/
public class Helix extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
public void play() {
content.animation.play();
}
private static final class Content {
private static final Duration DURATION = Duration.seconds(4);
private static final Color COLOR = Color.AQUA;
private static final double WIDTH = 3;
private final Group group = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Shape circle;
private final Shape arrow;
private final Animation animation;
private static Content create(double size) {
Content c = new Content(size);
c.group.getChildren().addAll(c.arrow, c.circle,
c.xAxis, c.yAxis, c.zAxis);
c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
c.group.setTranslateX(-size / 2);
c.group.setTranslateY(-size / 2);
c.group.setTranslateZ(size / 2);
c.rx.setAngle(35);
c.ry.setAngle(-45);
return c;
}
private Content(double size) {
xAxis = createBox(size, WIDTH, WIDTH);
yAxis = createBox(WIDTH, size, WIDTH);
zAxis = createBox(WIDTH, WIDTH, size);
circle = createCircle(size);
arrow = createShape();
animation = new ParallelTransition(
createTransition(circle, arrow),
createTimeline(size / 2));
}
private Circle createCircle(double size) {
Circle c = new Circle(size / 4);
c.setFill(Color.TRANSPARENT);
c.setStroke(COLOR);
return c;
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(COLOR));
return b;
}
private Shape createShape() {
Shape s = new Polygon(0, 0, -10, -10, 10, 0, -10, 10);
s.setStrokeWidth(WIDTH);
s.setStrokeLineCap(StrokeLineCap.ROUND);
s.setStroke(COLOR);
s.setEffect(new Bloom());
return s;
}
private Transition createTransition(Shape path, Shape node) {
PathTransition t = new PathTransition(DURATION, path, node);
t.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
t.setCycleCount(Timeline.INDEFINITE);
t.setInterpolator(Interpolator.LINEAR);
return t;
}
private Timeline createTimeline(double size) {
Timeline t = new Timeline();
t.setCycleCount(Timeline.INDEFINITE);
t.setAutoReverse(true);
KeyValue keyX = new KeyValue(group.translateXProperty(), size);
KeyValue keyY = new KeyValue(group.translateYProperty(), size);
KeyValue keyZ = new KeyValue(group.translateZProperty(), -size);
KeyFrame keyFrame = new KeyFrame(DURATION.divide(2), keyX, keyY, keyZ);
t.getKeyFrames().add(keyFrame);
return t;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-3 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
play();
}
public static void main(String[] args) {
launch(args);
}
}
In this related example, the yellow shapes follow a Timeline animation comprised of rotations of the cube's orthogonal axes, while also following a PathTransition along the edges of a cube's face.
cube.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(final MouseEvent e) {
animation = new Timeline();
animation.getKeyFrames().addAll(
new KeyFrame(new Duration(2000),
new KeyValue(cube.rx.angleProperty(), e.getY()),
new KeyValue(cube.ry.angleProperty(), -e.getX()),
new KeyValue(cube.rz.angleProperty(), e.getY())
));
animation.play();
}
});
…
pathBackFaceTransition = new PathTransition();
pathBackFaceTransition.setPath(rectangleBackFace);
…
pathFrontFaceTransition = new PathTransition();
pathFrontFaceTransition.setPath(rectangleFrontFace);
…
public void play() {
pathBackFaceTransition.play();
pathFrontFaceTransition.play();
}
Finally, you can simulate motion along the x, y and z axes by using a KeyValue that targets the scale and translate properties. Referring again to Animation Basics, the following change to TimelineEvents creates the illusion of a 3-D shape moving to and fro.
//create a keyValue with factory: scaling the circle 2times
KeyValue keyValueX = new KeyValue(stack.scaleXProperty(), 2);
KeyValue keyValueY = new KeyValue(stack.scaleYProperty(), 2);
KeyValue keyTransX = new KeyValue(stack.translateXProperty(), 100);
KeyValue keyTransY = new KeyValue(stack.translateYProperty(), 100);
//create a keyFrame, the keyValue is reached at time 2s
Duration duration = Duration.millis(2000);
//one can add a specific action when the keyframe is reached
EventHandler<ActionEvent> onFinished = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
//reset counter
i = 0;
}
};
KeyFrame keyFrame = new KeyFrame(duration, onFinished,
keyValueX, keyValueY, keyTransX, keyTransY);

Related

How to slide the node when added to an HBox in JavaFX?

I am creating a panel with an HBox that has 2 or 3 children. The first child is a VBox with icons and stays on the left side of the screen, when I hover the mouse over the VBox (1st child) I want to add my second child that is a VBox with buttons. My third child is an AnchorPane that supports my content.
My issue is, how do I add my second VBox to the HBox with a transition slide (left to right)?
The finality of hide my second child (VBox with buttons) was at increase my width content of third child (content AnchorPane);
Example Code
public class Test extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Scene scene = new Scene(root, 300, 250);
VBox c1 = new VBox();
ImageView i1 = new ImageView(new Image(getClass().getResourceAsStream("home/home.png")));
ImageView i2 = new ImageView(new Image(getClass().getResourceAsStream("home/contactos.png")));
ImageView i3 = new ImageView(new Image(getClass().getResourceAsStream("home/info.png")));
c1.getChildren().addAll(i1, i2, i3);
VBox c2 = new VBox();
Button b1 = new Button("home opção1");
Button b2 = new Button("home opção2");
c2.getChildren().addAll(b1, b2);
AnchorPane c3 = new AnchorPane();
// Set backgrounds
c1.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
c2.setBackground(new Background(new BackgroundFill(Color.GRAY, CornerRadii.EMPTY, Insets.EMPTY)));
c3.setBackground(new Background(new BackgroundFill(Color.rgb(255,255,148), CornerRadii.EMPTY, Insets.EMPTY)));
root.getChildren().addAll(c1, c3);
c1.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
root.getChildren().add(1, c2);
// Fault transation slide
}
});
c1.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
root.getChildren().remove(1);
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Unfortunately there is no predefined approach for getting the sliding effect in JavaFX. You can get that effect by understanding/implementing the concept of clipping. Internally the same concept is used for ScrollPane and other controls where we need to clip the content.
The idea is that, we clip the layout that we want to slide and change its value gradually by using a timeline. A more detailed explanation regarding this can be found in my blog. (I wrote this blog back in early 2012, so the code may look a bit obsolete ;) but you should get the concept)
I quickly worked the below demo for your requirement. This should help you to get some idea.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.stream.Stream;
public class SlideMenuDemo extends Application {
/* THE ONLY DRAWBACK OF THIS APPROACH IS YOU HAVE TO KNOW THE WIDTH OF THE SUBMENU AHEAD*/
double subMenuWidth = 140;
HBox root;
VBox subMenu;
VBox menuPane;
StackPane subMenuPane;
private Rectangle clipRect;
private Timeline timelineHide;
private Timeline timelineShow;
private final Color[] colors = {Color.RED, Color.BLUE, Color.GREEN, Color.LAVENDER, Color.PINK};
private final String[] shapes = {"cirlce", "triangle", "square", "rectangle"};
#Override
public void start(Stage primaryStage) {
root = new HBox();
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Sliding Demo");
primaryStage.setScene(scene);
primaryStage.show();
// Main menu pane
menuPane = new VBox();
menuPane.setStyle("-fx-background-color:#DDDDDD;");
Stream.of(shapes).forEach(shape -> menuPane.getChildren().addAll(buildMenuButton(shape, Color.BLACK, null)));
// Sub menu pane
subMenu = new VBox();
subMenuPane = new StackPane(subMenu);
subMenuPane.setMinWidth(0);
subMenuPane.setPrefWidth(0);
StackPane subMenuContainer = new StackPane(subMenuPane);
subMenuContainer.setStyle("-fx-background-color:#AAAAAA");
HBox menuBox = new HBox(menuPane, subMenuContainer);
menuBox.setOnMouseExited(e -> hideSubMenu());
// Content Pane
StackPane contentPane = new StackPane(new Text("Hello Slide Checking"));
contentPane.setAlignment(Pos.TOP_LEFT);
contentPane.setPadding(new Insets(15));
HBox.setHgrow(contentPane, Priority.ALWAYS);
contentPane.setStyle("-fx-background-color:#0000FF70,#FFFFFF;-fx-background-insets:0,1;");
root.getChildren().addAll(menuBox, contentPane);
setAnimation();
}
private void hideSubMenu() {
timelineHide.play();
}
private void showSubMenu() {
timelineShow.play();
}
private void setAnimation() {
clipRect = new Rectangle();
clipRect.setWidth(0);
clipRect.heightProperty().bind(root.heightProperty());
clipRect.translateXProperty().set(subMenuWidth);
subMenuPane.setClip(clipRect);
subMenuPane.translateXProperty().set(-subMenuWidth);
/* Event handler hide is finished. */
EventHandler<ActionEvent> onFinished = e -> {
menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
subMenu.getChildren().clear();
};
timelineShow = new Timeline();
timelineHide = new Timeline();
/* Animation for show. */
timelineShow.setCycleCount(1);
final KeyValue kvDwn1a = new KeyValue(clipRect.widthProperty(), subMenuWidth);
final KeyValue kvDwn1b = new KeyValue(subMenuPane.prefWidthProperty(), subMenuWidth);
final KeyValue kvDwn1c = new KeyValue(subMenuPane.minWidthProperty(), subMenuWidth);
final KeyValue kvDwn2 = new KeyValue(clipRect.translateXProperty(), 0);
final KeyValue kvDwn3 = new KeyValue(subMenuPane.translateXProperty(), 0);
final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), kvDwn1a, kvDwn1b, kvDwn1c, kvDwn2, kvDwn3);
timelineShow.getKeyFrames().add(kfDwn);
/* Animation for hide. */
timelineHide.setCycleCount(1);
final KeyValue kvUp1a = new KeyValue(clipRect.widthProperty(), 0);
final KeyValue kvUp1b = new KeyValue(subMenuPane.prefWidthProperty(), 0);
final KeyValue kvUp1c = new KeyValue(subMenuPane.minWidthProperty(), 0);
final KeyValue kvUp2 = new KeyValue(clipRect.translateXProperty(), subMenuWidth);
final KeyValue kvUp3 = new KeyValue(subMenuPane.translateXProperty(), -subMenuWidth);
final KeyFrame kfUp = new KeyFrame(Duration.millis(200), onFinished, kvUp1a, kvUp1b, kvUp1c, kvUp2, kvUp3);
timelineHide.getKeyFrames().add(kfUp);
}
private StackPane buildMenuButton(String type, Color color, String text) {
double size = 50;
double sSize = (size / 5) * 4;
double hSize = sSize / 2;
StackPane menuButton = new StackPane();
menuButton.setPadding(new Insets(0, 5, 0, 5));
menuButton.setMaxHeight(size);
menuButton.setMinHeight(size);
Node shape = null;
switch (type) {
case "triangle":
StackPane s = new StackPane();
s.setPrefSize(sSize, sSize);
s.setMaxSize(sSize, sSize);
s.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
s.setStyle("-fx-shape:\"M0 1 L1 1 L.5 0 Z\";");
s.setPadding(new Insets(hSize));
shape = s;
break;
case "square":
shape = new Rectangle(sSize, sSize, color);
break;
case "rectangle":
shape = new Rectangle(sSize, hSize, color);
break;
default:
shape = new Circle(hSize, color);
break;
}
HBox hb = new HBox(shape);
hb.setAlignment(Pos.CENTER_LEFT);
if (text != null) {
hb.setSpacing(10);
hb.getChildren().add(new Label(text));
}
menuButton.getChildren().add(hb);
if (text == null) {
// Main menu button
menuButton.setOnMouseEntered(e -> {
menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
subMenu.getChildren().clear();
menuButton.setStyle("-fx-background-color:#AAAAAA;");
Stream.of(colors).forEach(c -> subMenu.getChildren().addAll(buildMenuButton(type, c, c.toString())));
if (subMenuPane.getWidth() == 0) {
showSubMenu();
}
});
} else {
// Sub menu button
menuButton.setPrefWidth(subMenuWidth);
menuButton.setMinWidth(subMenuWidth);
menuButton.setOnMouseEntered(e -> menuButton.setStyle("-fx-background-color:#777777;"));
menuButton.setOnMouseExited(e -> menuButton.setStyle(null));
}
return menuButton;
}
public static void main(String[] args) {
launch(args);
}
}

Animating a sprite with JavaFX on key input

I'm new to JavaFX but have a good understanding of object orientated Java. The following program is a combination of two examples, one that animates and moves shapes , the other animates an object on a mouse button press. Much of the functionality has been removed or changed for my needs.
I've searched through many examples but haven't found one I fully understand regarding moving a sprite and animating on key press.In my program I'm sure that I'm not using the right classes to create the game object, even though with some tweaking I'm sure it could work.
I added some println functions to test the animation. The problem seems to be that the KeyFrame part in the walkSouth animation isn't working/playing.
My question is:
Should I be using different JavaFX classes to create the sprite-sheet animation?
Can this code be easily adapted to function so I can get a better understanding of how JavaFX works.
Here is the main class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private enum UserAction{
NONE,NORTH,SOUTH;
}
private static int APP_W = 200;
private static int APP_H = 200;
private Scene scene;
private UserAction action = UserAction.NONE;
private Timeline timeline = new Timeline();
private boolean running = true;
private int FPS = 60;
private Parent createContent(){
Pane root = new Pane();
root.setPrefSize(APP_W,APP_H);
Image cat_image = new Image("file:res/cata.png");
GameObject obj = new GameObject(cat_image,12,8);
obj.setTranslateX(100);
obj.setTranslateY(100);
KeyFrame frame = new KeyFrame(Duration.millis(1000/FPS), event -> {
if(!running)
return;
switch(action){
case NORTH:
obj.setTranslateY(obj.getTranslateY()-1);
break;
case SOUTH:
obj.walkSouth();
obj.setTranslateY(obj.getTranslateY()+1);
break;
case NONE:
obj.pauseAnimation();
break;
}
});
timeline.getKeyFrames().add(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
root.getChildren().add(obj);
return root;
}
private void restartGame(){
stopGame();
startGame();
}
private void stopGame(){
running = false;
timeline.stop();
}
private void startGame(){
timeline.play();
running = true;
}
public void start(Stage primaryStage) throws Exception{
scene = new Scene(createContent());
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NORTH;
break;
case S:
action = UserAction.SOUTH;
break;
}
});
scene.setOnKeyReleased(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NONE;
break;
case S:
action = UserAction.NONE;
break;
}
});
primaryStage.setTitle("Simple Animation");
primaryStage.setScene(scene);
primaryStage.show();
startGame();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the GameObject class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
/**
* Created by matt on 26/02/17.
*/
public class GameObject extends Pane {
ObjectImage objectImage;
public GameObject( Image image, int columns, int rows){
objectImage = new ObjectImage(image,columns,rows);
getChildren().setAll(objectImage);
}
public void pauseAnimation(){
getChildren().setAll(objectImage);
objectImage.pauseAnimation();
}
public void walkSouth(){
getChildren().setAll(objectImage);
objectImage.walkSouth();
}
}
class ObjectImage extends ImageView {
private Rectangle2D[] clips;
private double width,height;
private Timeline timeline = new Timeline();
public ObjectImage(Image image,int columns,int rows){
width = image.getWidth()/columns;
height = image.getHeight()/rows;
clips = new Rectangle2D[rows*columns];
int count=0;
for(int row =0;row < rows;row++ )
for(int column = 0 ; column < columns; column++,count++)
clips[count] = new Rectangle2D(width * column, height * row,width,height);
setImage(image);
setViewport(clips[0]);
}
public void pauseAnimation(){
timeline.pause();
}
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/5), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
System.out.println("frame test");
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames();
timeline.play();
}
}
This is the sprite-sheet image I'm working with
This is the outcome
As hinted by the comment, you did forget to add the frame in the walkSouth method. (Also you set each picture frame in walkSouth method to 200ms. Did you meant to change that?) Here's the code after changing:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/FPS), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames().add(frame); //This was the offending line.
timeline.play();
}
To answer your first question, yes there are many other options of classes you could use. Two options you could do is use the AnimationTimer or the Transition class. Here's a brief explanation for both (with code samples).
AnimationTimer is called every cycle or frame of rendering, which I believe you might be wanting this one:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
AnimationTimer tmr = new AnimationTimer() {
#Override
public void handle(long nanoTime)
{
//nanoTime specifies the current time at the beginning of the frame in nano seconds.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
tmr.start();
//call tmr.stop() to stop/ pause timer.
}
If however, you don't want an animation to be called each frame, you could extend Transition. A transition has an frac (fractional) value ranging from 0 to 1 that increases with respect to time. I'm not going to go into a whole lot detail, but I'm sure you could look up some more information on the api.
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
Transition trans = new Transition() {
{
setCycleDuration(Duration.millis(1000 / 60.0));
}
#Override
public void interpolate(double frac)
{
if (frac != 1)
return;
//End of one cycle.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
trans.setCycleCount(Animation.INDEFINITE);
trans.playFromStart();
//Use trans.pause to pause, trans.stop to stop.
}

fading in and fading out of an circle in javafx canvas

I would like to fade in and fadeout a circle in a javafx canvas.
I can move a circle from one part of the screen to another but i just can't seem to get this object to fade in and out.
Below is the code i used for moving a circle from one part of the screen to the other
public class AnimatedCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty x = new SimpleDoubleProperty();
DoubleProperty y = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(x, 0),
new KeyValue(y, 0)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(x, W - D),
new KeyValue(y, H - D)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN);
gc.fillOval(
x.doubleValue(),
y.doubleValue(),
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
Your help is greatly appreciated
Animate a DoubleProperty for the opacity in values ranging from 1 to 0 in exactly the same way as you animated the properties for x and y, and then use Color.deriveColor(...) to set the color based on the changing property:
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty opacity = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(opacity, 1)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(opacity, 0)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN.deriveColor(0, 1, 1, opacity.get()));
gc.fillOval(
W/2,
H/2,
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
In general, I prefer not to use a Canvas for this, and just to use Shape instances placed in a Pane. Then you can directly change the pre-defined properties of the Shape, or in many cases use specific pre-defined animations (TranslateTransition or FadeTransition for example).
Here's the same example using this technique:
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
Circle circle = new Circle(W/2, H/2, D, Color.FORESTGREEN);
FadeTransition fade = new FadeTransition(Duration.seconds(3), circle);
fade.setFromValue(1);
fade.setToValue(0);
fade.setAutoReverse(true);
fade.setCycleCount(Timeline.INDEFINITE);
Pane pane = new Pane(circle);
stage.setScene(
new Scene(
pane, W, H, Color.CORNSILK
)
);
stage.show();
fade.play();
}
public static void main(String[] args) { launch(args); }
}

Moving shapes in JavaFX canvas

I would like to know if it's possible to use the GraphicsContext of a Canvas to create a circle(or any shape created with GraphicsContext) and then move it around on the canvas. If it is, what's the algorithm for doing so? I'm used to working with Java and I just can't figure it out.
Thanks in advance for any help.
Basically, the way this works is that you setup a Canvas and update the location of the shape based on some Timeline. Then, in an AnimationTimer you paint your canvas.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimatedCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty x = new SimpleDoubleProperty();
DoubleProperty y = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(x, 0),
new KeyValue(y, 0)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(x, W - D),
new KeyValue(y, H - D)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN);
gc.fillOval(
x.doubleValue(),
y.doubleValue(),
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
It is however simpler to not use a Canvas, but instead use a Pane containing a Circle in combination with a TranslateTransition.

JavaFX : Canvas to Image in non GUI Thread

I have to visualize lot of data (real-time) and I am using JavaFX 2.2. So I have decided to "pre-visualize" data before they are inserted into GUI thread.
In my opinion the fastest way to do it (with antialliasing etc.) is let some NON GUI thread to generate image/bitmap and then put in GUI thread (so the UI is still responsive for user).
But I can't find way how to conver Canvas to Image and then use:
Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
Platform.runLater(new Runnable() {
#Override
public void run() {
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
Thx for some usable answers. :-)
btw: I have made test app to show my problem.
package canvasandthreads02;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CanvasAndThreads02 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Paint");
final AnchorPane root = new AnchorPane();
final Canvas canvas = new Canvas(900, 800);
canvas.setLayoutX(50);
canvas.setLayoutY(50);
root.getChildren().add(canvas);
root.getChildren().add(btn);
Scene scene = new Scene(root, 900, 800);
primaryStage.setTitle("Painting in JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Start painting");
/**
* Start Thread where some data will be visualized
*/
new Thread(new PainterThread(canvas, new DataToPaint())).start();
}
});
}
private class PainterThread implements Runnable{
private final DataToPaint data;
private final Canvas canvas;
public PainterThread(Canvas canvas, DataToPaint data){
this.canvas = canvas;
this.data = data;
}
#Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
Canvas tmpCanvas = new Canvas(data.width, data.height);
GraphicsContext graphicsContext2D = tmpCanvas.getGraphicsContext2D();
graphicsContext2D.setFill(data.color;);
for (int i = 0; i < data.height; i++) {
for (int j = 0; j < data.width; j++) {
graphicsContext2D.fillRect(j, i, 1, 1); //draw 1x1 rectangle
}
}
/**
* And now I need still in this Thread convert tmpCanvas to Image,
* or use some other method to put result to Main GIU Thread using Platform.runLater(...);
*/
final Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
System.out.println("Canvas painting: " + (System.currentTimeMillis()-currentTimeMillis));
Platform.runLater(new Runnable() {
#Override
public void run() {
//Start painting\n Canvas painting: 430 \n Time to convert:62
//long currentTimeMillis1 = System.currentTimeMillis();
//Image imageToDraw = tmpCanvas.snapshot(null, null);
//System.out.println("Time to convert:" + (System.currentTimeMillis()-currentTimeMillis1));
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
}
}
private class DataToPaint{
double offsetX = 0;
double offsetY = 0;
Color color;
int width = 500;
int height = 250;
public DataToPaint(){
Random rand = new Random();
color = new Color(rand.nextDouble(), rand.nextDouble(), rand.nextDouble(), rand.nextDouble());
offsetX = rand.nextDouble() * 20;
offsetY = rand.nextDouble() * 20;
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
use Canvas' snapshot(...) method to create a WritableImage from the Canvas' content. ^^
Works fine for me.
I know this is a really old question, but just for anyone who cares:
There is now a second version of canvas.snapshot that takes a callback and works asynchronously!
public void snapshot(Callback<SnapshotResult,Void> callback,
SnapshotParameters params,
WritableImage image)

Resources