I'm trying to make an animated button by gradually changing it's style using a CSS file.
I researched about it, and I got to this this question. I saw the accepted answer and I tried to implement the solution that that person gave.
What I did is basically creating a CustomButton
class that extends Button
, and then setting up StringProperties
whose values change depending on if the button is clicked or not; then, every single StringProperty
is concatenated so then they work as an alternative to a CSS file, being applied as a style to a button.
I think everything is coded fine, the problem is that I can't create the animation that should be played when the button is pressed because for that I'd need to create a Timeline
whose Keyframe
's KeyValue
s specify the value of each one of the components that conform the animation, and it turns out that StringProperties can't work as parameters for a KeyValue
method.
This is the CustomButton class:
public class CustomButton extends Button{
BooleanProperty clicked = new SimpleBooleanProperty(false); // Its value changes each time the button is clicked.
StringProperty oldRotationValue = new SimpleStringProperty("-fx-rotate: -360;"); // Stores the last value of rotationStringProperty, so
// then it can be used as the first frame of an animation.
StringProperty oldColorValue = new SimpleStringProperty("#E74C3C;"); // Stores the last value of colorStringProperty, so
// then it can be used as the first frame of an animation.
StringProperty oldSizeValue = new SimpleStringProperty("-fx-size: " + "180px;"); // Stores the last value of sizeStringProperty, so
// then it can be used as the first frame of an animation.
public CustomButton(){
Button button = createButton(rotationStringProperty, colorStringProperty, sizeStringProperty);
} //CustomButton constructor
StringProperty rotationStringProperty = new SimpleStringProperty(); // Creates the rotationStringProperty.
StringProperty colorStringProperty = new SimpleStringProperty(); // Creates the colorStringProperty.
StringProperty sizeStringProperty = new SimpleStringProperty(); // Creates the sizeStringProperty.
private void setRotationStringProperty() { // Method that sets the rotation value depending on whether the button was pressed or not.
if (!clicked.getValue()) { // If the button wasn't clicked.
oldRotationValue.set("-fx-rotate: " + "-360;");
rotationStringProperty.set("-fx-rotate: " + "360;");
} else { // If the button was clicked.
oldRotationValue.set("-fx-rotate: " + "360;");
rotationStringProperty.set("-fx-rotate: " + "-360;");
}
}
private StringProperty setColorStringProperty() { // Method that sets the color depending on whether the button was pressed or not.
if (!clicked.getValue()) { // If the button wasn't clicked.
oldColorValue.set("#EA6153;");
colorStringProperty.set("#E74C3C;");
} else { // If the button was clicked.
oldColorValue.set("#E74C3C;");
colorStringProperty.set("#EA6153;");
}
}
private StringProperty setSizeStringProperty() { // Method that sets the size depending on whether the button was pressed or not.
if (!clicked.getValue()) { // If the button wasn't pressed
oldSizeValue.set("-fx-size: " + "200px;");
sizeStringProperty.set("-fx-size: " + "180px;");
} else { // If the button was pressed.
oldSizeValue.set("-fx-size: " + "180px;");
sizeStringProperty.set("-fx-size: " + "200px;");
}
}
private void setSizeStringMediumProperty(){ // Sets the size that the button must have in the middle of the animation.
if(!clicked.getValue()){ // If the button wasn't pressed.
sizeStringProperty.set("-fx-size: " + "170px;");
}else{ // If the button was pressed.
sizeStringProperty.set("-fx-size: " + "210px;");
}
}
private Button createButton(StringProperty rotationStringProperty, //Method that creates a button and defines the its rotation,
// color, size, and how these has to be animated.
// Once everything is done, it returns the customized button.
StringProperty colorStringProperty,
StringProperty sizeStringProperty) {
Button button = new Button(); // Creates a normal JavaFX Button so then it can be modified.
buttonSetOnAction(button); // Sets the action that the button must perform when it's clicked.
setRotationStringProperty(); // Sets the rotationStringProperty.
setColorStringProperty(); // Sets the colorStringProperty.
setSizeStringProperty(); // Sets the sizeStringProperty.
button.styleProperty().bind(new SimpleStringProperty("-fx-background-color: ") // Set's the style of the button.
.concat(colorStringProperty)
.concat(sizeStringProperty)
.concat(rotationStringProperty)
.concat("fx-border-color; #c0392b;")
.concat("-fx-border-width: 15px;"));
return button; // Returns the button.
}
private void buttonSetOnAction(Button button){ // Definition of a method that sets the actions that the button must perform when it's clicked.
button.setOnAction(e -> {
clicked.set(!clicked.getValue()); // The value of clicked is set to its opposite.
Timeline animation = new Timeline( //Defines an animation
new KeyFrame(Duration.seconds(0), new KeyValue(oldRotationValue, oldColorValue, oldSizeValue));
setSizeStringMediumProperty(); // Sets the size that the button must have at the middle of the animation.
new KeyFrame(Duration.seconds(0.25), new KeyValue(sizeStringProperty));
setSizeStringProperty(); // Sets the size that the button must have at the end of the animation.
new KeyFrame(Duration.seconds(0.50), new KeyValue(rotationStringProperty, colorStringProperty, sizeStringProperty));
);
animation.play(); // Plays the animation;
});
}
}
Can anyone think of an alternative to using StringProperties?
NOTE: I know that "-fx-size: "
does nothing, but I don't know how to change the button's size in CSS. I'll ask that in another question once I can solve the animation problem.
UPDATE: I just edited the code by implementing @James_D 's alternative:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.control.Button;
import javafx.util.Duration;
public class CustomButton extends Button{
BooleanProperty clicked = new SimpleBooleanProperty(false); // Its value changes each time the button is clicked.
IntegerProperty oldRotationValue = new SimpleIntegerProperty(-360); // Stores the last value of rotationIntegerProperty, so
// then it can be used as the first frame of an animation.
IntegerProperty oldColorValue = new SimpleIntegerProperty(Integer.decode("E74C3C")); // Stores the last value of colorIntegerProperty, so
// then it can be used as the first frame of an animation.
IntegerProperty oldSizeValue = new SimpleIntegerProperty(180); // Stores the last value of sizeIntegerProperty, so
// then it can be used as the first frame of an animation.
public CustomButton(){ //CustomButton constructor
Button button = createButton(rotationIntegerProperty, colorIntegerProperty, sizeIntegerProperty);
}
IntegerProperty rotationIntegerProperty = new SimpleIntegerProperty(); // Creates the rotationIntegerProperty.
IntegerProperty colorIntegerProperty = new SimpleIntegerProperty(); // Creates the rotationIntegerProperty.
IntegerProperty sizeIntegerProperty = new SimpleIntegerProperty(); // Creates the sizeIntegerProperty.
private IntegerProperty setRotationIntegerProperty() { // Method that sets the rotation value depending on whether the button was pressed or not.
IntegerProperty newRotationIntegerProperty = new SimpleIntegerProperty();
if (!clicked.getValue()) { // If the button wasn't clicked.
oldRotationValue.set(-360);
rotationIntegerProperty.set(360);
} else { // If the button was clicked.
oldRotationValue.set(260);
rotationIntegerProperty.set(-360);
}
return newRotationIntegerProperty;
}
private IntegerProperty setColorIntegerProperty() { // Method that sets the color depending on whether the button was pressed or not.
IntegerProperty newColorIntegerProperty = new SimpleIntegerProperty();
if (!clicked.getValue()) { // If the button wasn't clicked.
oldColorValue.set(Integer.decode("EA6153")); // oldColorValue.set("#EA6153;");
colorIntegerProperty.set(Integer.decode("E74C3C"));
} else { // If the button was clicked.
oldColorValue.set(Integer.decode("E74C3C"));
colorIntegerProperty.set(Integer.decode("EA6153"));
}
return newColorIntegerProperty;
}
private IntegerProperty setSizeIntegerProperty() { // Method that sets the size depending on whether the button was pressed or not.
IntegerProperty newSizeIntegerProperty = new SimpleIntegerProperty();
if (!clicked.getValue()) { // If the button wasn't pressed
oldSizeValue.set(200);
sizeIntegerProperty.set(180);
} else { // If the button was pressed.
oldSizeValue.set(180);
sizeIntegerProperty.set(200);
}
return newSizeIntegerProperty;
}
private IntegerProperty setSizeIntegerMediumProperty(){ // Sets the size that the button must have in the middle of the animation.
IntegerProperty newSizeIntegerMediumProperty = new SimpleIntegerProperty();
if(!clicked.getValue()){ // If the button wasn't pressed.
sizeIntegerProperty.set(180);
}else{ // If the button was pressed.
sizeIntegerProperty.set(180);
}
return newSizeIntegerMediumProperty;
}
private Button createButton(IntegerProperty rotationIntegerProperty, //Method that creates a button and defines the its rotation,
// color, size, and how these has to be animated.
// Once everything is done, it returns the customized button.
IntegerProperty colorIntegerProperty,
IntegerProperty sizeIntegerProperty) {
Button button = new Button(); // Creates a normal JavaFX Button so then it can be modified.
buttonSetOnAction(button); // Sets the action that the button must perform when it's clicked.
setRotationIntegerProperty(); // Sets the rotationIntegerProperty.
setRotationIntegerProperty(); // Sets the colorIntegerProperty.
setSizeIntegerProperty(); // Sets the sizeIntegerProperty.
button.styleProperty().bind(Bindings.format( // Set's the style of the button.
"-fx-pref-width: #%f;"
+ "-fx-pref-height: #%f;"
+ "-fx-rotate: %f;"
+ "-fx-background-color: #%s;"
+ "-fx-border-color: #c0392b;"
+ "-fx-border-width: 15px;",
sizeIntegerProperty, sizeIntegerProperty, rotationIntegerProperty, colorIntegerProperty));
return button; // Returns the button.
}
private void buttonSetOnAction(Button button){ // Definition of a method that sets the actions that the button must perform when it's clicked.
Timeline animation = new Timeline();
button.setOnAction(e -> {
clicked.set(!clicked.getValue()); // The value of clicked is set to its opposite.
animation.getKeyFrames().clear();
animation.getKeyFrames().addAll(new KeyFrame(Duration.seconds(0),
new KeyValue (rotationIntegerProperty, oldRotationValue.getValue()),
new KeyValue (colorIntegerProperty, oldColorValue.getValue()),
new KeyValue (sizeIntegerProperty, oldSizeValue.getValue())),
new KeyFrame(Duration.seconds(0.25),
new KeyValue (sizeIntegerProperty, setSizeIntegerMediumProperty().getValue())),
new KeyFrame(Duration.seconds(0.25),
new KeyValue (sizeIntegerProperty, setSizeIntegerProperty().getValue()),
new KeyValue (rotationIntegerProperty, setRotationIntegerProperty().getValue()),
new KeyValue (colorIntegerProperty, setColorIntegerProperty().getValue())));
animation.play(); // Plays the animation;
button.disableProperty().bind(animation.statusProperty().isEqualTo(Animation.Status.RUNNING));
});
}
}
Everything seems fine until I execute the code, and the console prints this out:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.util.IllegalFormatConversionException: f != java.lang.Integer
at java.util.Formatter$FormatSpecifier.failConversion(Unknown Source)
at java.util.Formatter$FormatSpecifier.printFloat(Unknown Source)
at java.util.Formatter$FormatSpecifier.print(Unknown Source)
at java.util.Formatter.format(Unknown Source)
at java.util.Formatter.format(Unknown Source)
at java.lang.String.format(Unknown Source)
at com.sun.javafx.binding.StringFormatter$4.computeValue(Unknown Source)
at javafx.beans.binding.StringBinding.get(Unknown Source)
at com.sun.javafx.binding.StringFormatter.format(Unknown Source)
at javafx.beans.binding.Bindings.format(Unknown Source)
at CustomButton.createButton(CustomButton.java:109)
at CustomButton.<init>(CustomButton.java:26)
at StartingPoint.start(StartingPoint.java:15)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
... 1 more
Exception running application StartingPoint
For as far as I understand, this is due to that there's a problem in the 109th line of the CustomButton
class, which is this:
button.styleProperty().bind(Bindings.format( // Set's the style of the button.
What could be the problem?