This is my first project in Java and JavaFX. I have most of the work done but I need a button that does something. I used things found in different tutorials but my button still doesn't work. Nothing happens when I click it. I will be VERY thankful for any tips. Please find a picture of my application attached so that you can understand better what the code draws. Here is my code (button is called b1):
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package po_javafx;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
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.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import static javafx.scene.paint.Color.CORNSILK;
import static javafx.scene.paint.Color.DARKGOLDENROD;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PO_JavaFX extends Application {
public static World world = new World("Game");
static protected int width;
static protected int height;
static protected int civilOffsetX;
static protected int civilOffsetY;
static protected int bossOffsetX;
static protected int bossOffsetY;
static protected int heroOffsetX;
static protected int heroOffsetY;
/**
* #return the civilOffsetX
*/
public static int getCivilOffsetX() {
return civilOffsetX;
}
/**
* #param aCivilOffsetX the civilOffsetX to set
*/
public static void setCivilOffsetX(int aCivilOffsetX) {
civilOffsetX = aCivilOffsetX;
}
/**
* #return the civilOffsetY
*/
public static int getCivilOffsetY() {
return civilOffsetY;
}
/**
* #param aCivilOffsetY the civilOffsetY to set
*/
public static void setCivilOffsetY(int aCivilOffsetY) {
civilOffsetY = aCivilOffsetY;
}
/**
* #return the bossOffsetX
*/
public static int getBossOffsetX() {
return bossOffsetX;
}
/**
* #param aBossOffsetX the bossOffsetX to set
*/
public static void setBossOffsetX(int aBossOffsetX) {
bossOffsetX = aBossOffsetX;
}
/**
* #return the bossOffsetY
*/
public static int getBossOffsetY() {
return bossOffsetY;
}
/**
* #param aBossOffsetY the bossOffsetY to set
*/
public static void setBossOffsetY(int aBossOffsetY) {
bossOffsetY = aBossOffsetY;
}
/**
* #return the heroOffsetX
*/
public static int getHeroOffsetX() {
return heroOffsetX;
}
/**
* #param aHeroOffsetX the heroOffsetX to set
*/
public static void setHeroOffsetX(int aHeroOffsetX) {
heroOffsetX = aHeroOffsetX;
}
/**
* #return the heroOffsetY
*/
public static int getHeroOffsetY() {
return heroOffsetY;
}
/**
* #param aHeroOffsetY the heroOffsetY to set
*/
public static void setHeroOffsetY(int aHeroOffsetY) {
heroOffsetY = aHeroOffsetY;
}
#Override
public void start(Stage stage) throws Exception {
Pane root = new Pane();
setWidth(1400);
setHeight(1000);
Canvas background = new Canvas(getWidth(), getHeight());
final GraphicsContext context = background.getGraphicsContext2D();
File f = new File("background.png");
final Image image = new Image(new FileInputStream(f));
root.getChildren().add(background);
Button b1 = new Button("Spawn Civilian");
b1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
root.getChildren().add(b1);
b1.setLayoutX(1300);
b1.setLayoutY(10);
Canvas animation = new Canvas(getWidth(), getHeight());
Canvas animation2 = new Canvas(getWidth(), getHeight());
final GraphicsContext context2 = animation.getGraphicsContext2D();
final GraphicsContext context3 = animation2.getGraphicsContext2D();
File overlay100 = new File("close.png");
final Image but1;
but1 = new Image(new FileInputStream(overlay100));
File overlay = new File("cywil.png");
final Image cywil;
cywil = new Image(new FileInputStream(overlay));
File overlay2 = new File("city.png");
final Image miasto;
miasto = new Image(new FileInputStream(overlay2));
File overlay3 = new File("cross.png");
final Image skrzyzowanie;
skrzyzowanie = new Image(new FileInputStream(overlay3));
File overlay6 = new File("koksu.png");
final Image koksu = new Image(new FileInputStream(overlay6));
File overlay9 = new File("najman.png");
final Image najman = new Image(new FileInputStream(overlay9));
root.getChildren().add(animation);
root.getChildren().add(animation2);
Scene scene = new Scene(root, getWidth(), getHeight());
stage.setTitle("Old Gotham");
stage.setScene(scene);
stage.show();
final Duration oneFrameAmt = Duration.millis(1000 / 60);
final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,
new EventHandler() {
#Override
public void handle(Event event) {
context2.drawImage(image, 0, 0);
//context2.drawImage(but1, 1250, 100, 100, 100);
int offset = 700;
context2.setLineWidth(5.0);
context2.setStroke(DARKGOLDENROD);
for (Road road : World.roads) {
if (road.getOrientation().equals("horizontal")) {
context2.strokeLine(road.getX(), road.getY(), road.getX2(), road.getY2());
} else if (road.getOrientation().equals("vertical")) {
context2.strokeLine(road.getX(), road.getY(), road.getX2(), road.getY2());
}
}
for (City city : World.cities) {
context3.drawImage(miasto, city.getX() - offset / 2, city.getY() - offset / 2, offset, offset);
}
int crossroadOffsetX = 150;
int crossroadOffsetY = 100;
for (Crossroad crossroad : World.crossroads) {
context2.drawImage(skrzyzowanie, crossroad.getX() - crossroadOffsetX / 2, crossroad.getY() - crossroadOffsetY / 2 + 20, crossroadOffsetX, crossroadOffsetY);
}
setCivilOffsetX(70);
setCivilOffsetY(40);
for (Civilian civilian : World.civilians) {
context2.drawImage(cywil, civilian.getX() - getCivilOffsetX() / 2, civilian.getY() - getCivilOffsetY() / 2, getCivilOffsetX(), getCivilOffsetY());
}
setBossOffsetX(70);
setBossOffsetY(40);
for (Boss boss : World.bosses) {
context2.drawImage(najman, boss.getX() - getBossOffsetX() / 2, boss.getY() - getBossOffsetY() / 2, getBossOffsetX(), getBossOffsetY());
}
setHeroOffsetX(70);
setHeroOffsetY(40);
for (SuperHero hero : World.superheroes) {
context2.drawImage(koksu, hero.getX() - getHeroOffsetX() / 2, hero.getY() - getHeroOffsetY() / 2, getHeroOffsetX(), getHeroOffsetY());
}
}
});
final Timeline tl = new Timeline(oneFrame);
tl.setCycleCount(Animation.INDEFINITE);
tl.play();
}
public static void main(String[] args) {
launch(args);
}
/**
* #return the width
*/
public static int getWidth() {
return width;
}
/**
* #param width the width to set
*/
public void setWidth(int width) {
this.width = width;
}
/**
* #return the height
*/
public static int getHeight() {
return height;
}
/**
* #param height the height to set
*/
public void setHeight(int height) {
this.height = height;
}
}
The Canvas instances you are adding to the root Pane after you add the button (animation and animation2) have the size of the scene, and these are covering everything below them, including the button, so you can't click on the button.
As a first simple solution, you can make these canvases transparent:
animation.setMouseTransparent(true);
animation2.setMouseTransparent(true);
But you can benefit from using different layouts, so you can have the graphic area in one pane and the controls in other. For instance, you could use a BorderPane. Something like this:
BorderPane root = new BorderPane();
Pane paneCenter= new Pane();
Canvas background= new Canvas(1200,1000);
Canvas animation = new Canvas(1200,1000);
Canvas animation2 = new Canvas(1200,1000);
paneCenter.getChildren().addAll(background, animation, animation2);
root.setCenter(paneCenter);
VBox paneRight = new VBox();
paneRight.setPrefSize(200, 1000);
paneRight.setPadding(new Insets(20));
paneRight.setAlignment(Pos.TOP_CENTER);
Button b1 = new Button("Spawn Civilian");
paneRight.getChildren().add(b1);
root.setRight(paneRight);
Scene scene = new Scene(root, 1400, 1000);
Related
I have some code, in javafx, that has a pane that works like a paint canvas. I need to be able to fill the background color of the pane from a color picker.
Currently I have a Color variable that gets the color chosen from the color picker and I try to set it to my Pane (named canvas) as below:
Color newColour = backgroundColourPicker.getValue();
canvas.setStyle("-fx-background-color: " + newColour + ";");
However I get this output:
June 11, 2022 7:47:57 PM javafx.css.CssParser term
WARNING: CSS Error parsing '*{-fx-background-color: 0x00ffffff;}: Unexpected token '0x' at [1,24]
How do I either swap my Color value to a String to be able to remove the leading 0x and make it work or how do I get my Pane to accept the color value as a Color?
I have found this code worked for me if anyone needs it in the future:
Color newColour = backgroundColourPicker.getValue();
Double red = newColour.getRed()*100;
int rInt = red.intValue();
Double green = newColour.getGreen()*100;
int gInt = green.intValue();
Double blue = newColour.getBlue()*100;
int bInt = blue.intValue();
String hex = String.format("#%02X%02X%02X", rInt, gInt, bInt);
canvas.setStyle("-fx-background-color: " + hex + ";");
If you don't require all the features of the region's background—image, fills, etc.— and a solid color will suffice, also consider binding the color picker's value property to the fill color of a suitable Shape that fills the region. Based on the examples seen here, the variation below illustrates a ColorPane that renders a Rectangle whose size is bound to the enclosing Pane and whose color is bound to an ObjectProperty<Color> holding the rectangle's color. A similar arrangement is made for the foreground Circle. One benefit is that a custom color updates live as the the controls are adjusted. Several related examples are collected here.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/72583321/230513
* #see https://stackoverflow.com/a/70312046/230513
*/
public class ColorTest extends Application {
private final class ColorPane extends Pane {
public static final Color bgColor = Color.BLUE;
public static final Color fgColor = Color.CYAN;
private final Rectangle r = new Rectangle();
private final Circle c = new Circle(8, fgColor);
private final ObjectProperty<Color> bg = new SimpleObjectProperty<>(bgColor);
private final ObjectProperty<Color> fg = new SimpleObjectProperty<>(fgColor);
private final InvalidationListener listener = (o) -> update();
public ColorPane() {
this.setPrefSize(256, 256);
r.widthProperty().bind(this.widthProperty());
r.heightProperty().bind(this.heightProperty());
r.setFill(bgColor);
this.getChildren().add(r);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2).subtract(diameter.divide(10)));
this.getChildren().add(c);
bg.addListener(listener);
fg.addListener(listener);
}
private void update() {
r.setFill(bg.get());
c.setFill(fg.get());
}
public ObjectProperty<Color> bgProperty() {
return bg;
}
public ObjectProperty<Color> fgProperty() {
return fg;
}
}
private Pane createControlPane(ColorPane view) {
ColorPicker bgPicker = new ColorPicker(view.bgProperty().get());
bgPicker.setTooltip(new Tooltip("Background color."));
view.bgProperty().bindBidirectional(bgPicker.valueProperty());
ColorPicker fgPicker = new ColorPicker(view.fgProperty().get());
fgPicker.setTooltip(new Tooltip("Foreground color."));
view.fgProperty().bindBidirectional(fgPicker.valueProperty());
return new VBox(16, bgPicker, fgPicker);
}
#Override
public void start(Stage stage) {
var root = new BorderPane();
ColorPane colorPane = new ColorPane();
root.setCenter(colorPane);
root.setLeft(createControlPane(colorPane));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you do require features of the region's background, the following example illustrates dynamically replacing the background of a GradientPane with one that contains a LinearGradient based on the chosen color properties. The approach has the same benefit of live custom color updates as the the controls are adjusted, illustrated above.
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/72583321/230513
* #see https://stackoverflow.com/a/70312046/230513
*/
public class ColorTest extends Application {
private final class GradientPane extends Pane {
public static final Color c1Color = Color.BLUE;
public static final Color c2Color = Color.CYAN;
private final Circle c = new Circle(8, c2Color);
private final ObjectProperty<Color> c1 = new SimpleObjectProperty<>(c1Color);
private final ObjectProperty<Color> c2 = new SimpleObjectProperty<>(c2Color);
private final InvalidationListener listener = (o) -> update();
public GradientPane() {
this.setPrefSize(256, 256);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2).subtract(diameter.divide(10)));
this.getChildren().add(c);
c1.addListener(listener);
c2.addListener(listener);
update();
}
private void update() {
Stop[] stops = new Stop[]{new Stop(0, c1.get()), new Stop(1, c2.get())};
LinearGradient lg = new LinearGradient(0.5, 0, 0.5, 1, true, CycleMethod.NO_CYCLE, stops);
this.setBackground(new Background(new BackgroundFill(lg, CornerRadii.EMPTY, Insets.EMPTY)));
stops = new Stop[]{new Stop(0, c2.get()), new Stop(1, c1.get())};
lg = new LinearGradient(0.5, 0, 0.5, 1, true, CycleMethod.NO_CYCLE, stops);
c.setFill(lg);
}
public ObjectProperty<Color> c1Property() {
return c1;
}
public ObjectProperty<Color> c2Property() {
return c2;
}
}
private Pane createControlPane(GradientPane view) {
ColorPicker bgPicker = new ColorPicker(view.c1Property().get());
bgPicker.setTooltip(new Tooltip("Color stop one."));
view.c1Property().bindBidirectional(bgPicker.valueProperty());
ColorPicker fgPicker = new ColorPicker(view.c2Property().get());
fgPicker.setTooltip(new Tooltip("Color stop two."));
view.c2Property().bindBidirectional(fgPicker.valueProperty());
VBox vBox = new VBox(10, bgPicker, fgPicker);
vBox.setPadding(new Insets(10));
return vBox;
}
#Override
public void start(Stage stage) {
var root = new BorderPane();
GradientPane colorPane = new GradientPane();
root.setCenter(colorPane);
root.setLeft(createControlPane(colorPane));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So I'm desinging an application for offline quiz systems. Currently I'm working on the screen that is visible for the participants (only question, maybe possible answers and a media element).
This screen contains a slide (derived from JPanel) the screen itself is a JFrame. I have to use the JPanel in another JFrame. In the JPanel of the slide is another JPanel for displaying media. I succeeded to display an image in the JPanel. But the application also has to display video and audio. I use VLCj to realize this in my application. Here is the code of the JFrame of the SlideView.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
/**
* Created by bram on 30/03/17.
*/
public class SlideView
{
private SlidePanel slidePanel;
private JFrame frame;
public SlideView(){
slidePanel = new SlidePanel();
frame = new JFrame("SlideView");
frame.setLayout(new BorderLayout());
Dimension dim = new Dimension();
dim.setSize(800,450);
frame.setMinimumSize(dim);
frame.add(slidePanel, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.addComponentListener(new ComponentListener() {
public void componentMoved(ComponentEvent e){}
public void componentHidden(ComponentEvent e){}
public void componentShown(ComponentEvent e){}
public void componentResized(ComponentEvent e)
{
frame.add(slidePanel,BorderLayout.CENTER);
}
});
}
/**
* Display the information of the slide
* #pre slide has to be initialized
* #pre the slideview is initialized
* #param slide the slide that has to be displayed
* #post the slide will be displayed
*/
public void setSlide(Slide slide)
{
slidePanel.initializeSlide(slide);
}
/**
* Set the slide panel
* #param panel
*/
public void setSlidePanel(SlidePanel panel)
{
frame.remove(slidePanel);
slidePanel = panel;
frame.add(slidePanel);
}
/**
* Get the slide panel
* #return the slide panel
*/
public SlidePanel getSlidePanel()
{
return slidePanel;
}
}
And here is the code of the panel that contains the information of the slide:
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* Created by bram on 30/03/17.
*/
public class SlidePanel extends JPanel {
private ImageIcon bgImage;
private JLabel title, body;
private Slide slide;
private MediaView media;
public SlidePanel()
{
bgImage = new ImageIcon("/home/bram/Documenten/School/3 BA/PSOPV/LOCAL/src/Slides/slide_background.jpg");
this.setSize(800,450);
createComponents();
}
/**
* Get the media view for controlling it
*/
public MediaView getMediaView()
{
return media;
}
/**
* Initialize the slide
*/
public void initializeSlide(Slide slide)
{
this.slide = slide;
title.setText(slide.getTitle());
body.setText(slide.getBodyText());
media.setFile(slide.getMediaFile());
resizeComponents();
}
#Override
/**
* Actions that will be executed when the window is resized
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the background image.
g.drawImage(getResizedBackground().getImage(), 0, 0, this);
resizeComponents();
}
/**
* Resize the different components to the dimensions of the frame
*/
private void resizeComponents()
{
/*Calculate the growing factor of the screen (minimum size is 800*450)*/
float growFactor = (float) this.getWidth() / 800f;
/*Scale the title label*/
title.setFont(new Font("SansSerif", Font.BOLD, (int)(40*growFactor)));
title.setBounds((int)(20 * growFactor), (int)(20*growFactor),this.getWidth(), (int)(50 * growFactor));
/*Scale the body section*/
body.setFont(new Font("SansSerif", Font.PLAIN,(int)(25*growFactor)));
body.setBounds((int)(50*growFactor),(int)(0.25f*this.getHeight()),(int)((this.getWidth()-50*growFactor) * 0.5f), (int)(0.75f*this.getHeight()-20));
/*Scale the media section*/
media.setBounds((int)(this.getWidth()*0.5),(int)(this.getHeight()*0.30), (int)(this.getWidth() * 0.40),(int)(this.getHeight()*0.60-20));
}
/**
* Create the title label of the slide
*/
private void createTitle()
{
title = new JLabel("",SwingConstants.LEFT);
title.setForeground(Color.WHITE);
this.add(title);
}
/**
* Create the body of the slide
*/
private void createBody()
{
body = new JLabel("");
this.add(body);
}
/**
* Create the media section
*/
private void createMedia()
{
media = new MediaView();
media.setSize(100,100);
this.add(media);
}
/**
* Create the gui components for displaying a slide
*/
private void createComponents()
{
createTitle();
createBody();
createMedia();
}
/**
* Scale the background to the height and width of the panel
* #return the resized background
*/
private ImageIcon getResizedBackground()
{
Image img = bgImage.getImage();
img = img.getScaledInstance(this.getWidth(),this.getHeight(), Image.SCALE_DEFAULT);
return new ImageIcon(img);
}
}
And finally here is the JPanel that has to display the media:
import com.sun.jna.NativeLibrary;
import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface;
import uk.co.caprica.vlcj.player.media.Media;
import uk.co.caprica.vlcj.runtime.RuntimeUtil;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* Created by bram on 31/03/17.
*/
public class MediaView extends JPanel
{
private MediaFile file;
//Label for displaying a picture
private JLabel picture;
//Media player for video and audio
private EmbeddedMediaPlayerComponent mediaPlayer;
private int width,height;
private static final String NATIVE_LIBRARY_SEARCH_PATH = "lib";
/**
* Default constructor for the mediaview class
*/
public MediaView()
{
setOpaque(false);
setLayout(new GridBagLayout());
NativeLibrary.addSearchPath(RuntimeUtil.getLibVlcLibraryName(), NATIVE_LIBRARY_SEARCH_PATH);
this.width = getWidth();
this.height = getHeight();
}
/**
* Add the appropriate components to the panel
*/
private void addComponents()
{
if(file.getFileType() == MediaFile.FileType.PICTURE)
{
picture = new JLabel();
this.add(picture);
}
if(file.getFileType() == MediaFile.FileType.VIDEO)
{
mediaPlayer = new EmbeddedMediaPlayerComponent();
setLayout(new BorderLayout());
add(mediaPlayer, BorderLayout.CENTER);
mediaPlayer.setSize(this.getWidth(),this.getHeight());
}
}
/**
* Set the media file to be displayed
* #pre the media file is initialized
* #param file the file that has to be displayed
*/
public void setFile(MediaFile file)
{
this.file = file;
addComponents();
generateView();
}
#Override
/**
* Actions that will be executed when the window is resized
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(this.height != getHeight() || this.width != getWidth())
{
generateView();
height = getHeight();
width = getWidth();
}
}
/**
* Generate the view of the file
*/
private void generateView()
{
if(file == null)
return;
if(file.getFileType() == MediaFile.FileType.PICTURE)
generateImage();
if(file.getFileType() == MediaFile.FileType.VIDEO)
generateVideo();
if(file.getFileType() == MediaFile.FileType.AUDIO)
generateAudio();
}
/**
* Generate the view of an audio file
*/
private void generateAudio()
{
}
/**
* Generate the view of a video
*/
public void generateVideo()
{
//Resize canvas
//Load native library of VLC
}
public void playMedia()
{
mediaPlayer.getMediaPlayer().playMedia(file.getPath());
}
/**
* Generate the view of an image
*/
private void generateImage()
{
/*Get the image by path*/
ImageIcon image = new ImageIcon(file.getPath());
/*Scale the image*/
Image img = image.getImage();
if(image.getIconHeight() > image.getIconWidth()) {
float growFactor = (float) this.getHeight() / (float) image.getIconHeight();
int width = (int) (image.getIconWidth() * growFactor);
img = img.getScaledInstance(width, this.getHeight(), Image.SCALE_DEFAULT);
} else {
float growFactor = (float) this.getWidth() / (float) image.getIconWidth();
int height = (int) (image.getIconHeight() * growFactor);
img = img.getScaledInstance(this.getWidth(),height,Image.SCALE_DEFAULT);
}
ImageIcon pic = new ImageIcon(img);
/*Set image as label icon*/
picture.setIcon(pic);
/*Set the bounds of the label*/
int xCoordinate = (this.getWidth() - pic.getIconWidth())/2;
int yCoordinate = (this.getHeight() - pic.getIconHeight())/2;
picture.setHorizontalAlignment(JLabel.CENTER);
picture.setVerticalAlignment(JLabel.CENTER);
picture.setBounds(xCoordinate,yCoordinate,picture.getWidth(),picture.getHeight());
}
}
But the video won't display (the video is a MP4 format). I tried several things (the above is the best result) does anyone can help me?
I have two circles that I want to turn them around a pivot clockwise if the right key is pressed and counter clockwise if the left key is pressed but my code does not work.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.util.Duration;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
public class Main extends Application {
public static final double CIRCLES_CENTER_X = 600;
public static final double CIRCLES_CENTER_Y = 450;
public static final double CIRCLES_RADIUS = 15;
public static final double CIRCLES_DISTANCE = 300;
public static final double GAME_HEIGHT = 700;
public static final double GAME_WIDTH = 1200;
private Stage primaryStage;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
primaryStage.setMinWidth(GAME_WIDTH);
primaryStage.setMinHeight(GAME_HEIGHT);
final Scene scene;
BorderPane root = new BorderPane();
scene = new Scene(root, Main.GAME_WIDTH, Main.GAME_HEIGHT);
Circle orangeCircle = new Circle(Main.CIRCLES_CENTER_X + Main.CIRCLES_DISTANCE / 2 * cos(0),
Main.CIRCLES_CENTER_Y + Main.CIRCLES_DISTANCE / 2 * sin(0),
Main.CIRCLES_RADIUS, Color.ORANGE);
Circle yellowCircle = new Circle(Main.CIRCLES_CENTER_X - Main.CIRCLES_DISTANCE / 2 * cos(0),
Main.CIRCLES_CENTER_Y - Main.CIRCLES_DISTANCE / 2 * sin(0),
Main.CIRCLES_RADIUS, Color.YELLOW);
Pane game = new Pane(orangeCircle, yellowCircle);
root.setCenter(game);
SimpleIntegerProperty angle = new SimpleIntegerProperty(0);
root.setOnKeyPressed(ke -> {
if (ke.getCode().toString().equals("RIGHT")) {
angle.set(360);
}
if (ke.getCode().toString().equals("LEFT")) {
angle.set(-360);
}
});
root.setOnKeyReleased(ke -> {
if (ke.getCode().toString().equals("RIGHT")) {
angle.set(0);
}
if (ke.getCode().toString().equals("LEFT")) {
angle.set(0);
}
});
Rotate orangeCircleRotation = new Rotate(0, Main.CIRCLES_CENTER_X, Main.CIRCLES_CENTER_Y);
orangeCircle.getTransforms().add(orangeCircleRotation);
Rotate yellowCircleRotation = new Rotate(0, Main.CIRCLES_CENTER_X, Main.CIRCLES_CENTER_Y);
yellowCircle.getTransforms().add(yellowCircleRotation);
Timeline rotationAnimation = new Timeline();
rotationAnimation.setCycleCount(Timeline.INDEFINITE);
angle.addListener((ov, old_val, new_val) -> {
System.out.println("fk");
rotationAnimation.stop();
while (rotationAnimation.getKeyFrames().size() > 0) {
rotationAnimation.getKeyFrames().remove(0);
}
rotationAnimation.getKeyFrames().add(
new KeyFrame(Duration.millis(2000),
new KeyValue(orangeCircleRotation.angleProperty(), angle.getValue())));
rotationAnimation.getKeyFrames().add(
new KeyFrame(Duration.millis(2000),
new KeyValue(yellowCircleRotation.angleProperty(), angle.getValue())));
rotationAnimation.play();
}
);
primaryStage.setScene(scene);
primaryStage.show();
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
It works almost fine but when i press a key and release it the circles does not stop. they just start turning backward until the last rotation change point and keep repeating that so also when i press the key again it jumps sometimes.(Because the backward turn has reached its end and starts from the beginning)(Hard to explain!you have to see it for yourself!)
Does anyone know how to fix or achieve this?
I wouldn't try to manipulate the key frames while the animation is in progress. Instead you can just pause/play the animation and change the rate. The only "gotcha" here is that it seems the animation ignores the change in rate if it is paused, so you need to call play() before setRate(...).
Here's the modified SSCCE:
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class RotatingCircles extends Application {
public static final double CIRCLES_CENTER_X = 600;
public static final double CIRCLES_CENTER_Y = 450;
public static final double CIRCLES_RADIUS = 15;
public static final double CIRCLES_DISTANCE = 300;
public static final double GAME_HEIGHT = 700;
public static final double GAME_WIDTH = 1200;
private Stage primaryStage;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
primaryStage.setMinWidth(GAME_WIDTH);
primaryStage.setMinHeight(GAME_HEIGHT);
final Scene scene;
BorderPane root = new BorderPane();
scene = new Scene(root, GAME_WIDTH, GAME_HEIGHT);
Circle orangeCircle = new Circle(CIRCLES_CENTER_X + CIRCLES_DISTANCE / 2 * cos(0),
CIRCLES_CENTER_Y + CIRCLES_DISTANCE / 2 * sin(0),
CIRCLES_RADIUS, Color.ORANGE);
Circle yellowCircle = new Circle(CIRCLES_CENTER_X - CIRCLES_DISTANCE / 2 * cos(0),
CIRCLES_CENTER_Y - CIRCLES_DISTANCE / 2 * sin(0),
CIRCLES_RADIUS, Color.YELLOW);
Pane game = new Pane(orangeCircle, yellowCircle);
root.setCenter(game);
Rotate orangeCircleRotation = new Rotate(0, CIRCLES_CENTER_X, CIRCLES_CENTER_Y);
orangeCircle.getTransforms().add(orangeCircleRotation);
Rotate yellowCircleRotation = new Rotate(0, CIRCLES_CENTER_X, CIRCLES_CENTER_Y);
yellowCircle.getTransforms().add(yellowCircleRotation);
Timeline rotationAnimation = new Timeline();
rotationAnimation.setCycleCount(Timeline.INDEFINITE);
rotationAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(2), new KeyValue(orangeCircleRotation.angleProperty(), 360)));
rotationAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(2), new KeyValue(yellowCircleRotation.angleProperty(), 360)));
root.setOnKeyPressed(ke -> {
if (ke.getCode() == KeyCode.RIGHT) {
rotationAnimation.play();
rotationAnimation.setRate(1);
} else if (ke.getCode() == KeyCode.LEFT) {
rotationAnimation.play();
rotationAnimation.setRate(-1);
}
});
root.setOnKeyReleased(ke -> {
rotationAnimation.pause();
});
primaryStage.setScene(scene);
primaryStage.show();
root.requestFocus();
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
BTW In this code you really don't need two separate rotations, since they are identical. Just create a single rotation and add it to both circles' transforms lists. It may be different in your real code, of course...
i have this picture(all of these effects are in one .png file ) i want to display for example second picture how can i use Image and ImageView in javafx to display specific part of this image ? thanks
This answer is overkill. But with a nice set of images like you have in your question, maybe overkill is what is called for :-)
The fundamental design is the same as Uluk's, it just adjusts the Viewport of the ImageView rather than setting a clip, but the concept is the same.
Beware => Java 8
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
class ExploadableImageView extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Duration FRAME_TIME = Duration.seconds(.5);
public ExploadableImageView(Image explosionImage, int numCells) {
this.numCells = numCells;
double cellWidth = explosionImage.getWidth() / numCells;
double cellHeight = explosionImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(explosionImage);
setViewport(cellClips[0]);
}
public void explode(EventHandler<ActionEvent> onFinished) {
final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
Timeline kaboom = new Timeline(
new KeyFrame(FRAME_TIME, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
kaboom.setCycleCount(numCells);
kaboom.setOnFinished(onFinished);
kaboom.play();
}
}
class ExplodableItem extends StackPane {
public ExplodableItem(Image objectImage, Image explosionImage, int numCells) {
ImageView objectView = new ImageView(objectImage);
ExploadableImageView explosionView = new ExploadableImageView(
explosionImage, numCells
);
setMinSize(
Math.max(
objectImage.getWidth(),
explosionView.getViewport().getWidth()
),
Math.max(
objectImage.getHeight(),
explosionView.getViewport().getHeight()
)
);
objectView.setPickOnBounds(false);
objectView.setOnMouseClicked(event -> {
getChildren().setAll(explosionView);
explosionView.explode(complete -> getChildren().setAll(objectView));
});
DropShadow drop = new DropShadow(10, Color.GOLD);
drop.setInput(new Glow());
objectView.setOnMouseEntered(event -> objectView.setEffect(drop));
objectView.setOnMouseExited(event -> objectView.setEffect(null));
getChildren().setAll(objectView);
}
}
public class CatWhack extends Application {
public static void main(String[] args) {
launch(args);
}
private static final int NUM_CELLS_PER_EXPLOSION = 6;
#Override
public void start(Stage stage) {
Image objectImage = new Image("http://icons.iconarchive.com/icons/iconka/meow/96/cat-box-icon.png"); // cat icon linkware: backlink to http://www.iconka.com required
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(CatWhack.class.getResourceAsStream("QMqbQ.png"));
Image explosionImage = new Image("http://i.stack.imgur.com/QMqbQ.png");
TilePane tiles = new TilePane();
tiles.setPrefColumns(4);
for (int i = 0; i <16; i++) {
tiles.getChildren().add(
new ExplodableItem(objectImage, explosionImage, NUM_CELLS_PER_EXPLOSION)
);
}
tiles.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stage.setTitle("Cat Whack - Click a cat to whack it!");
stage.setScene(new Scene(tiles));
stage.show();
}
}
Simpler example
Here is the same concept as demonstrated in the above game, but just with a simpler system of an animated image which can be controlled via method calls rather than user mouse clicks on the image.
The animated image is similar to a Sprite. The code below is not meant to be a production quality Sprite system (probably a true Sprite system for a game would have more functions and features), it just demonstrates very simple display of an animated image based upon a Viewport.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
class Sprite extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Timeline timeline;
private final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
public Sprite(Image animationImage, int numCells, Duration frameTime) {
this.numCells = numCells;
double cellWidth = animationImage.getWidth() / numCells;
double cellHeight = animationImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(animationImage);
setViewport(cellClips[0]);
timeline = new Timeline(
new KeyFrame(frameTime, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
}
public void playOnce() {
frameCounter.set(0);
timeline.setCycleCount(numCells);
timeline.stop();
timeline.playFromStart();
}
public void playContinuously() {
frameCounter.set(0);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.stop();
timeline.playFromStart();
}
public void stop() {
frameCounter.set(0);
setViewport(cellClips[frameCounter.get()]);
timeline.stop();
}
}
public class SpriteSample extends Application {
private static final int NUM_CELLS_PER_ANIMATION = 6;
private static final Duration FRAME_TIME = Duration.seconds(.5);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(Sprite.class.getResourceAsStream("QMqbQ.png"));
Image tilesheetImage = new Image(SpriteSample.class.getResourceAsStream("QMqbQ.png"));
Sprite sprite = new Sprite(tilesheetImage, NUM_CELLS_PER_ANIMATION, FRAME_TIME);
ToggleButton animationControl = new ToggleButton("Animate");
animationControl.setOnAction(event -> {
if (animationControl.isSelected()) {
animationControl.setText("Stop");
sprite.playContinuously();
} else {
animationControl.setText("Animate");
sprite.stop();
}
});
VBox layout = new VBox(10, sprite, animationControl);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}
You can utilize the clip property of the Node, along with x property of the ImageView. Below is a demo showing the part of image in timeline, like a gif animated picture:
#Override
public void start(Stage stage) {
Group root = new Group();
Image image = new Image(this.getClass().getResource("your.png").toExternalForm());
final int numberOfFrames = 6; // in image
double frameWidth = image.getWidth() / numberOfFrames;
Scene scene = new Scene(root, frameWidth, image.getHeight());
final ImageView view = new ImageView(image);
Rectangle mask = new Rectangle(frameWidth, image.getHeight());
view.setClip(mask);
Timeline timeline = new Timeline();
for (int i = 0; i <= numberOfFrames; i++) {
KeyFrame kf = new KeyFrame(Duration.seconds(i), new KeyValue(view.xProperty(), -frameWidth * i, Interpolator.DISCRETE));
timeline.getKeyFrames().add(kf);
}
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(view);
stage.setScene(scene);
stage.show();
}
As someone who is new to JavaFX, I had the same question. The question asks for much less than the above answers provide. We just want to manually select and display a portion of the image in an ImageView without animation. Here is the simplest solution wherein the user enters the image number and presses a button to display the desired image part.
package com.pakzaban;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class Controller {
#FXML
public ImageView imageView;
public TextField numberField;
private final int IMAGE_WIDTH = 50;
private final int IMAGE_HEIGHT = 80;
private int imageNumber = 1;
public void onSetImagePressed(){
try {
imageNumber = Integer.parseInt(numberField.getText());
//SET THE WHOLE IMAGE INTO IMAGEVIEW
Image wholeImage = new Image("com/pakzaban/wholePicture.png");
imageView.setImage(wholeImage);
//SET THE VIEWPORT TO DESIRED PART OF THE IMAGE
Rectangle2D imagePart = new Rectangle2D((imageNumber - 1) * IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
imageView.setViewport(imagePart);
}
catch (Exception e){
e.printStackTrace();
}
}
}
JavaFX 2.x
What I want to do:
This script translated into Java source code. I tried that myself, but some of that stuff is deprecated (e.g. PerspectiveTransform#time - not found in JavaFX 2.2)
Flipping like this and like that.
What I don't want to do:
Use RotateTransition because it depends on the PerspectiveCamera. Since I'll have many flippable tiles next to each other, the front/back replacement halfway through the animation won't go well.
What I have so far:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.effect.PerspectiveTransformBuilder;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class FX_Tester extends Application
{
#Override
public void start(final Stage stage) throws Exception
{
final StackPane stackPane = new StackPane();
final ImageView img1 = new ImageView("http://img3.wikia.nocookie.net/__cb20120816162009/mario/images/thumb/1/15/MarioNSMB2.png/200px-MarioNSMB2.png");
final ImageView img2 = new ImageView("http://img2.wikia.nocookie.net/__cb20120518002849/mario/images/thumb/7/78/Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png/180px-Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png");
final FlipView flipPane = new FlipView(img1, img2);
stackPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(final MouseEvent arg0)
{
flipPane.doFlip();
}
});
stackPane.getChildren().setAll(flipPane);
stage.setScene(new Scene(stackPane));
stage.show();
}
public static void main(final String[] args)
{
launch();
}
private class FlipView extends Group
{
private Node frontNode;
private Node backNode;
private boolean isFlipped = false;
private SimpleDoubleProperty time = new SimpleDoubleProperty(Math.PI / 2);
private Timeline anim = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(time, Math.PI / 2)),
new KeyFrame(Duration.ONE, new KeyValue(time, - Math.PI / 2)),
new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
isFlipped = !isFlipped;
}
})
);
private FlipView(final Node frontNode, final Node backNode)
{
this.frontNode = frontNode;
this.backNode = backNode;
getChildren().setAll(frontNode, backNode);
frontNode.setEffect(getPT(time.doubleValue()));
backNode.setEffect(getPT(time.doubleValue()));
frontNode.visibleProperty().bind(time.greaterThan(0));
backNode.visibleProperty().bind(time.lessThan(0));
}
private PerspectiveTransform getPT(final double t)
{
final double width = 200;
final double height = 200;
final double radius = width / 2;
final double back = height / 10;
return PerspectiveTransformBuilder.create()
.ulx(radius - Math.sin(t)*radius)
.uly(0 - Math.cos(t)*back)
.urx(radius + Math.sin(t)*radius)
.ury(0 + Math.cos(t)*back)
.lrx(radius + Math.sin(t)*radius)
.lry(height - Math.cos(t)*back)
.llx(radius - Math.sin(t)*radius)
.lly(height + Math.cos(t)*back)
.build();
}
public void doFlip()
{
if (isFlipped)
{
anim.setRate(1.0);
anim.setDelay(Duration.ZERO);
}
else
{
anim.setRate(-1.0);
anim.setDelay(Duration.ONE);
}
anim.play();
}
}
}
After heavy R&D, I've managed to implement the flip functionality without PerspectiveCamera, using only PerspectiveTransform.
If you're too lazy to run this SSCCE, then go here to see a demo on how the below code works.
Q: But George, how is this different from the other methods???
A: Well, first of: since you're not using PerspectiveCamera, the user's perspective won't be affected if say you have 100 flipping tiles on the screen. Second and last: The back node is ALREADY flipped. So it's not mirrored, it's not rotate, it's not scaled. It's "normal". Ain't that great?
Cheers.
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class DFXFlipPaneTester extends Application
{
// ==================== 1. Static Fields ========================
/*
* Mmm... pie.
*/
private static final Double PIE = Math.PI;
private static final Double HALF_PIE = Math.PI / 2;
private static final double ANIMATION_DURATION = 10000;
private static final double ANIMATION_RATE = 10;
// ====================== 2. Instance Fields =============================
private Timeline animation;
private StackPane flipPane;
private SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
private PerspectiveTransform transform = new PerspectiveTransform();
private SimpleBooleanProperty flippedProperty = new SimpleBooleanProperty(true);
// ==================== 3. Static Methods ====================
public static void main(final String[] args)
{
Application.launch(args);
}
// ==================== 5. Creators ====================
#Override
public void start(final Stage primaryStage) throws Exception
{
primaryStage.setScene(new Scene(createFlipPane()));
primaryStage.show();
}
private StackPane createFlipPane()
{
angle = createAngleProperty();
flipPane = new StackPane();
flipPane.setPadding(new Insets(30));
flipPane.setMinHeight(500);
flipPane.setMinWidth(500);
flipPane.getChildren().setAll(createBackNode(), createFrontNode());
flipPane.widthProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
flipPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
return flipPane;
}
private StackPane createFrontNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty);
node.getChildren().setAll(createButton("Front Button")); //$NON-NLS-1$
return node;
}
private StackPane createBackNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty.not());
node.getChildren().setAll(createButton("Back Button")); //$NON-NLS-1$
return node;
}
private Button createButton(final String text)
{
final Button button = new Button(text);
button.setMaxHeight(Double.MAX_VALUE);
button.setMaxWidth(Double.MAX_VALUE);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
flip();
}
});
return button;
}
private SimpleDoubleProperty createAngleProperty()
{
// --------------------- <Angle> -----------------------
final SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
angle.addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> obsValue, final Number oldValue, final Number newValue)
{
recalculateTransformation(newValue.doubleValue());
}
});
return angle;
}
private Timeline createAnimation()
{
return new Timeline(
new KeyFrame(Duration.millis(0), new KeyValue(angle, HALF_PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, 0, Interpolator.EASE_IN)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
// TODO -- Do they another way or API to do this?
flippedProperty.set( flippedProperty.not().get() );
}
}),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION), new KeyValue(angle, HALF_PIE, Interpolator.EASE_OUT))
);
}
// ==================== 6. Action Methods ====================
private void flip()
{
if (animation == null)
animation = createAnimation();
animation.setRate( flippedProperty.get() ? ANIMATION_RATE : -ANIMATION_RATE );
animation.play();
}
// ==================== 8. Business Methods ====================
private void recalculateTransformation(final double angle)
{
final double insetsTop = flipPane.getInsets().getTop() * 2;
final double insetsLeft = flipPane.getInsets().getLeft() * 2;
final double radius = flipPane.widthProperty().subtract(insetsLeft).divide(2).doubleValue();
final double height = flipPane.heightProperty().subtract(insetsTop).doubleValue();
final double back = height / 10;
/*
* Compute transform.
*
* Don't bother understanding these unless you're a math passionate.
*
* You may Google "Affine Transformation - Rotation"
*/
transform.setUlx(radius - Math.sin(angle) * radius);
transform.setUly(0 - Math.cos(angle) * back);
transform.setUrx(radius + Math.sin(angle) * radius);
transform.setUry(0 + Math.cos(angle) * back);
transform.setLrx(radius + Math.sin(angle) * radius);
transform.setLry(height - Math.cos(angle) * back);
transform.setLlx(radius - Math.sin(angle) * radius);
transform.setLly(height + Math.cos(angle) * back);
}
}
Oracle created a sample called DisplayShelf. It is similar to the PhotoFlip application you linked, but is implemented for Java 2+. The Oracle sample code is in the Ensemble Sample Application. You can review the DisplayShelf source in the JavaFX open source repository.
The DisplayShelf is a Cover Flow style implementation of PerspectiveTransform animations, so its not exactly the same as a full image flip. But many of the principles are the same, so you should be able to study the DisplayShelf example, then develop the code which you need to fit your requirement.
Related image flipping question for JavaFX => Flip a card animation.