Issue
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?
Solution
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.
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-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");
}
Suggested Further Improvements
There are some other enhancements that could be made to the control which are unrelated to the original question but would improve the quality and reusability of the control. Many applications won't need these enhancements, plus the enhancements can add additional complexity to the implementation which is unnecessary for these applications.
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).
Also, not provided in this solution, you could make use of JavaFX CSS looked up and derived colors to have the control match the color scheme defined for the default Java modena.css
stylesheet, so that setting, for instance, the -fx-base
color for the application to a different value will change the color scheme for this control as it does with the standard controls.
The in-built controls use a Skin abstraction to separate the public API from the internal implementation of the UI for the control so that users can assign custom skins to completely change the control's UI. Conceptually in operation, this switch is actually a kind of toggle button, so instead of having a custom control, it could be implemented as a custom skin that can be applied to the existing ToggleButton control. Or, less preferable, because it is more redundant, you could take the Switch implementation here and split it into a Switch class extending Control for the public API and a SwitchSkin class extending Skin for the UI and behavior implementation.
Alternative implementation
Before adopting either the other solutions linked in this answer or the solution in this answer, for a fairly common control like a switch, consider whether you would be better off using an off-the-shelf control either from the JavaFX framework core controls or a third-party library. Often those library-based controls will be of higher quality than something you create yourself or find in forum and Q&A posts.
For this particular animated switch control, the MaterialFX library has a particularly nice implementation, powered by additional bling :-)
Answered By - jewelsea
Answer Checked By - Mary Flores (JavaFixing Volunteer)