-1

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 KeyValues 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?

Dharman
  • 30,962
  • 25
  • 85
  • 135
SpaceCore186
  • 586
  • 1
  • 8
  • 22
  • The `buttonSetOnAction` method is invoked in the 105th line of the `CustomButton` class. – SpaceCore186 Jun 15 '16 at 11:59
  • The only semicolon that I see at the `Timeline` constructor's invocation is at the end of it. – SpaceCore186 Jun 15 '16 at 12:14
  • @JavaNoob There are semicolons at the end of every line that makes up the call to the `Timeline` constructor. As for the error, the error message tells you the problem: the format is expecting an integer for one of the arguments and is getting something different. I recommend reading the documentation for [that method](http://docs.oracle.com/javase/8/javafx/api/javafx/beans/binding/Bindings.html#format-java.lang.String-java.lang.Object...-) and for [`Formatter`](http://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html) – James_D Jun 15 '16 at 12:20
  • Oh. I forgot to add the editted code to the question. Sorry. – SpaceCore186 Jun 15 '16 at 12:33

2 Answers2

0

You can use numeric (or other interpolatable types, such as color) in key values, and bind a string property to their values. Have a look at this example:

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

public class AnimatedStyleButton extends Application {

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Grow");
        ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.RED);
        DoubleProperty size = new SimpleDoubleProperty(50);
        DoubleProperty fontSize = new SimpleDoubleProperty(12);
        DoubleProperty rotate = new SimpleDoubleProperty(0);
        StringProperty text = button.textProperty();
        BooleanProperty large = new SimpleBooleanProperty(false);

        Timeline timeline = new Timeline();

        button.setOnAction(e -> {

            timeline.getKeyFrames().clear();

            if (large.get()) {
                timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), 
                        new KeyValue(text, "Grow"),
                        new KeyValue(size, 80),
                        new KeyValue(large, false),
                        new KeyValue(rotate, 0),
                        new KeyValue(color, Color.RED),
                        new KeyValue(fontSize, 12)
                ));
            } else {
                timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), 
                        new KeyValue(text, "Shrink"),
                        new KeyValue(size, 200),
                        new KeyValue(large, true),
                        new KeyValue(rotate, 360),
                        new KeyValue(color, Color.BLUE),
                        new KeyValue(fontSize, 24)
                ));
            }

            timeline.play();
        });

        StringProperty colorAsString = new SimpleStringProperty();
        colorAsString.bind(Bindings.createStringBinding(() -> toWebColor(color.get()), color)); 

        button.styleProperty().bind(Bindings.format(
                "-fx-pref-width: %f;"
                + "-fx-pref-height: %f;"
                + "-fx-rotate: %f;"
                + "-fx-font-size: %f;"
                + "-fx-base: %s",
                size, size, rotate, fontSize, colorAsString));

        button.disableProperty().bind(timeline.statusProperty().isEqualTo(Animation.Status.RUNNING));

        StackPane root = new StackPane(button);
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private String toWebColor(Color c) {
        return String.format("#%02x%02x%02x", 
            (int) (c.getRed() * 255),
            (int) (c.getGreen() * 255),
            (int) (c.getBlue() * 255)
        );
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Edit: I probably should have looked at the linked question as this solution is very similar to that. Maybe it will be helpful anyway.

Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Hi. Thank's for your answer. I just editted the code by implementing your solution, but now I get an error in the 109th line of the `CustomButton` class. I just updated the question. – SpaceCore186 Jun 15 '16 at 11:25
0

If you are talking about css string property you can use it like this:-

Timeline animation = new Timeline(new KeyFrame(Duration.seconds(0.3),new KeyValue(myButton.translateYProperty(),-10,animationTime)),
            new KeyFrame(Duration.seconds(0.3),new KeyValue(myButton.styleProperty(), "-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.09), 10, 0, 0, 8);" ,animationTime)));

    animation.play();
Rahul Malik
  • 11
  • 1
  • 2