您可以使用翻译转换 https://openjfx.io/javadoc/18/javafx.graphics/javafx/animation/TranslateTransition.html,以动画方式移动轨道上开关的旋钮。
虽然与动画无关,但该解决方案也修改了原始示例 https://stackoverflow.com/questions/30593193/creating-sliding-on-off-switch-button-in-javafx#answer-57290206要在样式表中使用样式类,on
伪类和暴露的on
自定义控件的状态属性。
这个答案也是基于之前问题的想法的组合:
- 在javaFX中创建滑动开/关开关按钮 https://stackoverflow.com/questions/30593193/creating-sliding-on-off-switch-button-in-javafx#answer-57290206
- 根据模型状态更改 JavaFX 样式类 https://stackoverflow.com/questions/36094736/change-javafx-style-class-based-on-model-state
相关问题:
- 一个控制多个点击监听器 https://stackoverflow.com/questions/73043337/one-control-many-click-listeners/73043974#73043974
示例代码
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");
}
建议进一步改进
可以对控件进行一些其他增强,这些增强与原始问题无关,但会提高控件的质量和可重用性。许多应用程序不需要这些增强功能,而且这些增强功能可能会增加实现的额外复杂性,而这对于这些应用程序来说是不必要的。
在此解决方案中,控件大小仍然是硬编码的,但如果愿意,您可以修改控件以使用基于em
大小(类似于标准 JavaFX 控件)。
此外,此解决方案中未提供,您可以利用 JavaFX CSS 查找和派生颜色来使控件与为默认 Java 定义的颜色方案相匹配modena.css
样式表,以便设置,例如-fx-base
将应用程序的颜色设置为不同的值将更改此控件的颜色方案,就像标准控件一样。
内置控件使用Skin https://openjfx.io/javadoc/18/javafx.controls/javafx/scene/control/Skin.html抽象将公共 API 与控件 UI 的内部实现分开,以便用户可以分配自定义外观来完全更改控件的 UI。从概念上讲,该开关实际上是一种切换按钮,因此可以将其实现为可应用于现有 ToggleButton 控件的自定义外观,而不是使用自定义控件。或者,不太可取的是,因为它更加冗余,您可以在此处采用 Switch 实现,并将其拆分为一个扩展公共 API 的 Control 的 Switch 类和一个扩展 UI 和行为实现的 Skin 的 SwitchSkin 类。
替代实施
在采用此答案中链接的其他解决方案或此答案中的解决方案之前,对于像开关这样的相当常见的控件,请考虑使用来自 JavaFX 框架核心控件的现成控件或来自第三方库。通常,这些基于库的控件比您自己创建或在论坛和问答帖子中找到的控件质量更高。
对于这个特定的动画开关控件,材质FX https://github.com/palexdev/MaterialFX#documentation图书馆有一个特别好的实施 https://camo.githubusercontent.com/77ad469b3d74a12add42ac154f864cae42f1af4074af9350263c06abd5080a3a/68747470733a2f2f696d6775722e636f6d2f417255684835382e676966,由额外的金光闪闪提供动力:-)