Home > Back-end >  Animate custom switch button
Animate custom switch button

Time:07-20

I used the custom switch button in the custom SwitchButton answer. Now I would like to animate the circle part when the user toggles between the 2 values (state). I used KeyValue and KeyFrame in order to do so.

The snippet of the animation that I added to the SwitchButton() method :

KeyValue start = new KeyValue(button.alignmentProperty(), Pos.CENTER_RIGHT);
KeyValue end = new KeyValue(button.alignmentProperty(), Pos.CENTER_LEFT);

KeyFrame frame = new KeyFrame(Duration.seconds(4), start, end);
Timeline timeline = new Timeline(frame);
timeline.play();

How can I make the animation?

CodePudding user response:

You can use a TranslateTransition, to animate moving the knob for the switch on the track.

Although unrelated to the animation, this solution also modifies the original example to use style classes in a stylesheet, an on pseudo-class, and an exposed on state property for the custom control.

Control sizes are still hardcoded in this solution, but if preferred, you could modify the control to use a system based on em sizes (similar to the standard JavaFX controls).

This answer is also based on a combination of ideas from previous questions:

Related question:

Example Code

import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SwitchApp extends Application {
    @Override
    public void start(Stage stage) {
        Switch lightSwitch = new Switch();
        lightSwitch.onProperty().addListener((observable, wasOn, nowOn) -> {
            System.out.println(nowOn ? "on" : "off");
        });

        StackPane layout = new StackPane(lightSwitch);
        layout.setPadding(new Insets(30));

        stage.setScene(new Scene(layout));
        stage.show();
    }

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

class Switch extends StackPane {
    private static final double TRACK_WIDTH = 30;
    private static final double TRACK_HEIGHT = 10;
    private static final double KNOB_DIAMETER = 15;
    private static final Duration ANIMATION_DURATION = Duration.seconds(0.25);

    public static final String CSS = "data:text/css,"   // language=CSS
            """
            .switch > .track {
                -fx-fill: #ced5da;
            }
            .switch > .knob  {
                -fx-effect: dropshadow(
                    three-pass-box,
                    rgba(0,0,0,0.2),
                    0.2, 0.0, 0.0, 2
                ); 
                -fx-background-color: WHITE; 
            }
            
            .switch:on > .track {
                -fx-fill: #80C49E;
            }
            .switch:on > .knob { 
                -fx-effect: dropshadow(
                    three-pass-box, 
                    rgba(0,0,0,0.2), 
                    0.2, 0.0, 0.0, 2
                ); 
                -fx-background-color: #00893d;
            }
            """;

    private final TranslateTransition onTransition;
    private final TranslateTransition offTransition;

    public Switch() {
        // construct switch UI
        getStylesheets().add(CSS);
        getStyleClass().add("switch");

        Rectangle track = new Rectangle(TRACK_WIDTH, TRACK_HEIGHT);
        track.getStyleClass().add("track");
        track.setArcHeight(track.getHeight());
        track.setArcWidth(track.getHeight());

        Button knob = new Button();
        knob.getStyleClass().add("knob");
        knob.setShape(new Circle(KNOB_DIAMETER / 2));
        knob.setMaxSize(KNOB_DIAMETER, KNOB_DIAMETER);
        knob.setMinSize(KNOB_DIAMETER, KNOB_DIAMETER);
        knob.setFocusTraversable(false);
        setAlignment(knob, Pos.CENTER_LEFT);

        getChildren().addAll(track, knob);
        setMinSize(TRACK_WIDTH, KNOB_DIAMETER);

        // define animations
        onTransition = new TranslateTransition(ANIMATION_DURATION, knob);
        onTransition.setFromX(0);
        onTransition.setToX(TRACK_WIDTH - KNOB_DIAMETER);

        offTransition = new TranslateTransition(ANIMATION_DURATION, knob);
        offTransition.setFromX(TRACK_WIDTH - KNOB_DIAMETER);
        offTransition.setToX(0);

        // add event handling
        EventHandler<Event> click = e -> setOn(!isOn());
        setOnMouseClicked(click);
        knob.setOnMouseClicked(click);

        onProperty().addListener((observable, wasOn, nowOn) -> updateState(nowOn));
        updateState(isOn());
    }

    private void updateState(Boolean nowOn) {
        onTransition.stop();
        offTransition.stop();

        if (nowOn != null && nowOn) {
            onTransition.play();
        } else {
            offTransition.play();
        }
    }

    public void setOn(boolean on) {
        this.on.set(on);
    }

    public boolean isOn() {
        return on.get();
    }

    public BooleanProperty onProperty() {
        return on;
    }

    public BooleanProperty on =
            new BooleanPropertyBase(false) {
                @Override protected void invalidated() {
                    pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
                }

                @Override public Object getBean() {
                    return Switch.this;
                }

                @Override public String getName() {
                    return "on";
                }
            };

    private static final PseudoClass
            ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
}
  • Related