了解 getStyleClass().add() 和几行代码
Understanding getStyleClass().add() and a few lines of code
几天前,我通过创建一个简单的按钮并使用 setStyle()
方法和 String
objects(其值各不相同)修改其样式,设法使用 JavaFX 创建了一个自定义按钮取决于按钮是否被点击)作为参数。
我不知道如何将该自定义按钮转换为 class
,每次我想使用它时都可以导入,所以我一直在研究并找到 this项目,其中包括几个使用 Material 设计定制的 JavaFX 控制器。我现在感兴趣的控制器是MaterialButton
,它的源代码如下:
import com.sun.javafx.scene.control.skin.ButtonSkin;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.animation.Transition;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
@SuppressWarnings("restriction")
public class CustomButton extends Button {
private static final Duration RIPPLE_DURATION = Duration.millis(250); // Duration of the ripple effect
private static final Duration SHADOW_DURATION = Duration.millis(350); // Duration of the shadow effect
private static final Color RIPPLE_COLOR = Color.web("#FFF", 0.3); // Ripple color
public CustomButton() { // Except from the setPrefHeifht() method, everything between this braces seems useless.
// Probably I'm wrong, but why would you want to do this?
textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
if (!oldValue.endsWith(newValue.toUpperCase())) {
textProperty().set(newValue.toUpperCase());
}
});
setPrefHeight(36); // Height of the button
}
@Override
public Skin<?> createDefaultSkin() { // Overrides the default skin of the button.
ButtonSkin buttonSkin = (ButtonSkin) getSkin();
if (buttonSkin == null) {
buttonSkin = new ButtonSkin(this);
Circle circleRipple = new Circle(0.1, RIPPLE_COLOR); // Creates the circle that must appear when the
// ripple effect animation is displayed.
buttonSkin.getChildren().add(0, circleRipple); // Adds the nodes to the screen.
setSkin(buttonSkin);
createRippleEffect(circleRipple); // Creates the ripple effect animation.
getStyleClass().add("ripple-button"); // I don't know what this line does, but if it is
// removed, the button is narrowed.
}
return buttonSkin;
}
public ButtonSkin getButtonSkin() { // Returns the skin casted to a ButtonSkin.
return (ButtonSkin) getSkin();
}
public void setFlated(boolean flated) { // The button is "flated" when it's pressed, so I guess that this is the same that saying "clicked".
if (flated) {
getStyleClass().add("flat"); // I don't know what this does.
} else {
getStyleClass().remove("flat"); // I don't know what does this do, either.
}
}
public boolean getFlated() {
return getStyleClass().indexOf("flat") != -1; // If the style class doesn't contain "flat", it returns false.
}
public void toggled(boolean toggled) { // For as far as I know, a toggle is the switch from one effect to another.
if (toggled) {
getStyleClass().add("toggle"); // I know as much about this line as I know about the other "getStyleClass()" lines.
} else {
getStyleClass().remove("toggle"); // I know as much about this line as I know about the other "getStyleClass()" lines.
}
}
public boolean getToggled() {
return getStyleClass().indexOf("toggle") != -1; // If the style class doesn't contain "toggle". it returns false.
}
private void createRippleEffect(Circle circleRipple) { // Defines the ripple effect animation.
Rectangle rippleClip = new Rectangle(); // Creates a new Rectangle
rippleClip.widthProperty().bind(widthProperty()); // For as far as I understand, it binds the width property of the
// rippleClip to itself. Why would you do that?
rippleClip.heightProperty().bind(heightProperty()); // For as far as I understand, it binds the width property of the
// rippleClip to itself. Why would you do that?
circleRipple.setClip(rippleClip); // For as far as I know, clipping is the process that consists
// in hiding everything that is outside of a specified area.
// What this does is specifying that area so that the parts of the circle
// that are outside of the rectangle, can be hided.
circleRipple.setOpacity(0.0); // Sets the circle's opacity to 0.
/*Fade Transition*/
FadeTransition fadeTransition = new FadeTransition(RIPPLE_DURATION, circleRipple); // Creates the fadeTransition.
fadeTransition.setInterpolator(Interpolator.EASE_OUT);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
/*ScaleTransition*/
final Timeline scaleRippleTimeline = new Timeline(); // Creates the scaleRippleTimeLine Timeline.
DoubleBinding circleRippleRadius = new DoubleBinding() { // Binds the radius of the circle to a double value.
{
bind(heightProperty(), widthProperty());
}
@Override
protected double computeValue() {
return Math.max(heightProperty().get(), widthProperty().get() * 0.45); // Returns the greater of both.
}
};
// The below line adds a listener to circleRippleRadius.
circleRippleRadius.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
KeyValue scaleValue = new KeyValue(circleRipple.radiusProperty(), newValue, Interpolator.EASE_OUT);
KeyFrame scaleFrame = new KeyFrame(RIPPLE_DURATION, scaleValue);
scaleRippleTimeline.getKeyFrames().add(scaleFrame);
});
/*ShadowTransition*/
Animation animation = new Transition() { // Creates and defines the animation Transition.
{
setCycleDuration(SHADOW_DURATION); // Sets the duration of "animation".
setInterpolator(Interpolator.EASE_OUT); // It sets the EASE_OUT interpolator,
// so that the shadow isn't displayed forever and its an animation.
}
@Override
protected void interpolate(double frac) {
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.30), 5 + (10 * frac), 0.10 + ((3 * frac) / 10), 0, 2 + (4 * frac)));
// Creates a a DropShadow effect and then sets it to "animation".
}
};
animation.setCycleCount(2);
animation.setAutoReverse(true);
final SequentialTransition rippleTransition = new SequentialTransition(); // Creates a SequentialTransition. The circle's scaling is the
// first transition to occur, and then the color of the button
// changes to the original one with fadeTransition
rippleTransition.getChildren().addAll(
scaleRippleTimeline,
fadeTransition
);
final ParallelTransition parallelTransition = new ParallelTransition();
getStyleClass().addListener((ListChangeListener.Change<? extends String> c) -> { // For as far as I understand, each time that the
// Style class changes, the lines of code between the
// braces are executed, but I still don't understand how
// does the Style class work.
if (c.getList().indexOf("flat") == -1 && c.getList().indexOf("toggle") == -1) {
setMinWidth(88);
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.30), 5, 0.10, 0, 2));
parallelTransition.getChildren().addAll(rippleTransition, animation);
} else {
parallelTransition.getChildren().addAll(rippleTransition);
setMinWidth(USE_COMPUTED_SIZE);
setEffect(null);
}
});
this.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { // When the button is clicked, each object's value is assigned to the first
// that it must have at the beginning of the animation. Then, the animation
// starts.
parallelTransition.stop();
circleRipple.setOpacity(0.0);
circleRipple.setRadius(0.1);
circleRipple.setCenterX(event.getX());
circleRipple.setCenterY(event.getY());
parallelTransition.playFromStart();
});
}
public void setRippleColor(Color color) {
((Shape) ((SkinBase) getSkin()).getChildren().get(0)).setFill(color); // I don't understand this line of code.
}
}
由于我是 JavaFX 的新手,我将整个 GitHub 项目视为一座金矿,因为不仅我可以访问展示如何创建自定义控制器的示例 class 可以从另一个导入,但它也展示了如何自定义其他几个控制器。
问题是有些代码行我不明白(如果你阅读我对源代码的评论,你就会明白)。
例如,getStyleClass().add("something")
被多次使用。
我知道 getStylesheets().add()
是如何工作的,但这是不同的;我会说 "Style" class 与 CSS 文件不同。
如果是这样,它是如何工作的?据我了解,用作 getStyleClass().add()
方法参数的 String
s 用于确定它是否在 "Style" class 内稍后 if()
声明;但这 class 到底是什么?我还没有在互联网上看到任何关于它的文档。
我也无法理解源代码末尾的 setRippleColor()
方法。如果有人知道它是如何工作的或者我应该寻找什么来理解它,我将不胜感激。
提前致谢。
更新:有人指出 ripple-button
是 CSS 文件的一部分,可以在 GitHub 项目中找到。
我复制了 MaterialButton
class 并将其粘贴到一个新项目中,因此它无法通过仅提及它来访问 ripple-button
。然而,事实证明,如果我删除这行代码,按钮就会变窄。我可以通过任何方式更改 "ripple-button",结果将是相同的,但该行必须在那里。为什么会这样?
更新 2:我已经理解了 setRippleColor(Color color)
方法的作用:基本上它得到了圆的表皮并得到了它的 children 所以然后它可以在将矩形转换为 Shape
后更改矩形的颜色。它被铸造成一个形状,因为 Rectangle
延伸 Shape
。其实很简单。
有些问题可能会阐明您的困惑。
首先,这些东西不叫 'controllers',而是叫 'controls',这只是为了清楚起见,因为它可能很容易混淆。
方法 getStyleSheets()
returns 类型 String
的 ObservableList
。此列表包含定义应用程序样式的 .css
文件的各种路径。在 Scene
or a Control
of type Parent
上添加样式。有关详细信息,请查看链接的 JavaDoc 或这些文章:
- http://docs.oracle.com/javafx/2/css_tutorial/jfxpub-css_tutorial.htm
- https://blog.idrsolutions.com/2014/04/use-external-css-files-javafx/
样式表定义控件的样式。它们还提供了一些额外的样式 classes,可以在任何 Node
到 getStyleClass()
上设置,这也是 returns 类型 String
的 ObservableList
, 这次定义样式 class 名称。呈现名称时,会在为该 Node
定义的样式表集中查找然后应用。如果没有找到这样的样式 class ,它就会被忽略。 Node
是任何控件的基础 class。
方法 createDefaultSkin()
不会覆盖默认皮肤,正如您在评论中提到的那样,但它定义了默认皮肤(嗯,您是部分正确的,因为 CustomButton
扩展了 Button
Skin
被覆盖)。通常一个控件由一个 'control' class 和一个 'skin' class 组成,至少在 JavaFX 版本 8 发生变化时就是这种情况。有关详细信息,请参阅 the control architecture 上的文章。
几天前,我通过创建一个简单的按钮并使用 setStyle()
方法和 String
objects(其值各不相同)修改其样式,设法使用 JavaFX 创建了一个自定义按钮取决于按钮是否被点击)作为参数。
我不知道如何将该自定义按钮转换为 class
,每次我想使用它时都可以导入,所以我一直在研究并找到 this项目,其中包括几个使用 Material 设计定制的 JavaFX 控制器。我现在感兴趣的控制器是MaterialButton
,它的源代码如下:
import com.sun.javafx.scene.control.skin.ButtonSkin;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.animation.Transition;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
@SuppressWarnings("restriction")
public class CustomButton extends Button {
private static final Duration RIPPLE_DURATION = Duration.millis(250); // Duration of the ripple effect
private static final Duration SHADOW_DURATION = Duration.millis(350); // Duration of the shadow effect
private static final Color RIPPLE_COLOR = Color.web("#FFF", 0.3); // Ripple color
public CustomButton() { // Except from the setPrefHeifht() method, everything between this braces seems useless.
// Probably I'm wrong, but why would you want to do this?
textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
if (!oldValue.endsWith(newValue.toUpperCase())) {
textProperty().set(newValue.toUpperCase());
}
});
setPrefHeight(36); // Height of the button
}
@Override
public Skin<?> createDefaultSkin() { // Overrides the default skin of the button.
ButtonSkin buttonSkin = (ButtonSkin) getSkin();
if (buttonSkin == null) {
buttonSkin = new ButtonSkin(this);
Circle circleRipple = new Circle(0.1, RIPPLE_COLOR); // Creates the circle that must appear when the
// ripple effect animation is displayed.
buttonSkin.getChildren().add(0, circleRipple); // Adds the nodes to the screen.
setSkin(buttonSkin);
createRippleEffect(circleRipple); // Creates the ripple effect animation.
getStyleClass().add("ripple-button"); // I don't know what this line does, but if it is
// removed, the button is narrowed.
}
return buttonSkin;
}
public ButtonSkin getButtonSkin() { // Returns the skin casted to a ButtonSkin.
return (ButtonSkin) getSkin();
}
public void setFlated(boolean flated) { // The button is "flated" when it's pressed, so I guess that this is the same that saying "clicked".
if (flated) {
getStyleClass().add("flat"); // I don't know what this does.
} else {
getStyleClass().remove("flat"); // I don't know what does this do, either.
}
}
public boolean getFlated() {
return getStyleClass().indexOf("flat") != -1; // If the style class doesn't contain "flat", it returns false.
}
public void toggled(boolean toggled) { // For as far as I know, a toggle is the switch from one effect to another.
if (toggled) {
getStyleClass().add("toggle"); // I know as much about this line as I know about the other "getStyleClass()" lines.
} else {
getStyleClass().remove("toggle"); // I know as much about this line as I know about the other "getStyleClass()" lines.
}
}
public boolean getToggled() {
return getStyleClass().indexOf("toggle") != -1; // If the style class doesn't contain "toggle". it returns false.
}
private void createRippleEffect(Circle circleRipple) { // Defines the ripple effect animation.
Rectangle rippleClip = new Rectangle(); // Creates a new Rectangle
rippleClip.widthProperty().bind(widthProperty()); // For as far as I understand, it binds the width property of the
// rippleClip to itself. Why would you do that?
rippleClip.heightProperty().bind(heightProperty()); // For as far as I understand, it binds the width property of the
// rippleClip to itself. Why would you do that?
circleRipple.setClip(rippleClip); // For as far as I know, clipping is the process that consists
// in hiding everything that is outside of a specified area.
// What this does is specifying that area so that the parts of the circle
// that are outside of the rectangle, can be hided.
circleRipple.setOpacity(0.0); // Sets the circle's opacity to 0.
/*Fade Transition*/
FadeTransition fadeTransition = new FadeTransition(RIPPLE_DURATION, circleRipple); // Creates the fadeTransition.
fadeTransition.setInterpolator(Interpolator.EASE_OUT);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
/*ScaleTransition*/
final Timeline scaleRippleTimeline = new Timeline(); // Creates the scaleRippleTimeLine Timeline.
DoubleBinding circleRippleRadius = new DoubleBinding() { // Binds the radius of the circle to a double value.
{
bind(heightProperty(), widthProperty());
}
@Override
protected double computeValue() {
return Math.max(heightProperty().get(), widthProperty().get() * 0.45); // Returns the greater of both.
}
};
// The below line adds a listener to circleRippleRadius.
circleRippleRadius.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
KeyValue scaleValue = new KeyValue(circleRipple.radiusProperty(), newValue, Interpolator.EASE_OUT);
KeyFrame scaleFrame = new KeyFrame(RIPPLE_DURATION, scaleValue);
scaleRippleTimeline.getKeyFrames().add(scaleFrame);
});
/*ShadowTransition*/
Animation animation = new Transition() { // Creates and defines the animation Transition.
{
setCycleDuration(SHADOW_DURATION); // Sets the duration of "animation".
setInterpolator(Interpolator.EASE_OUT); // It sets the EASE_OUT interpolator,
// so that the shadow isn't displayed forever and its an animation.
}
@Override
protected void interpolate(double frac) {
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.30), 5 + (10 * frac), 0.10 + ((3 * frac) / 10), 0, 2 + (4 * frac)));
// Creates a a DropShadow effect and then sets it to "animation".
}
};
animation.setCycleCount(2);
animation.setAutoReverse(true);
final SequentialTransition rippleTransition = new SequentialTransition(); // Creates a SequentialTransition. The circle's scaling is the
// first transition to occur, and then the color of the button
// changes to the original one with fadeTransition
rippleTransition.getChildren().addAll(
scaleRippleTimeline,
fadeTransition
);
final ParallelTransition parallelTransition = new ParallelTransition();
getStyleClass().addListener((ListChangeListener.Change<? extends String> c) -> { // For as far as I understand, each time that the
// Style class changes, the lines of code between the
// braces are executed, but I still don't understand how
// does the Style class work.
if (c.getList().indexOf("flat") == -1 && c.getList().indexOf("toggle") == -1) {
setMinWidth(88);
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.30), 5, 0.10, 0, 2));
parallelTransition.getChildren().addAll(rippleTransition, animation);
} else {
parallelTransition.getChildren().addAll(rippleTransition);
setMinWidth(USE_COMPUTED_SIZE);
setEffect(null);
}
});
this.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { // When the button is clicked, each object's value is assigned to the first
// that it must have at the beginning of the animation. Then, the animation
// starts.
parallelTransition.stop();
circleRipple.setOpacity(0.0);
circleRipple.setRadius(0.1);
circleRipple.setCenterX(event.getX());
circleRipple.setCenterY(event.getY());
parallelTransition.playFromStart();
});
}
public void setRippleColor(Color color) {
((Shape) ((SkinBase) getSkin()).getChildren().get(0)).setFill(color); // I don't understand this line of code.
}
}
由于我是 JavaFX 的新手,我将整个 GitHub 项目视为一座金矿,因为不仅我可以访问展示如何创建自定义控制器的示例 class 可以从另一个导入,但它也展示了如何自定义其他几个控制器。
问题是有些代码行我不明白(如果你阅读我对源代码的评论,你就会明白)。
例如,getStyleClass().add("something")
被多次使用。
我知道 getStylesheets().add()
是如何工作的,但这是不同的;我会说 "Style" class 与 CSS 文件不同。
如果是这样,它是如何工作的?据我了解,用作 getStyleClass().add()
方法参数的 String
s 用于确定它是否在 "Style" class 内稍后 if()
声明;但这 class 到底是什么?我还没有在互联网上看到任何关于它的文档。
我也无法理解源代码末尾的 setRippleColor()
方法。如果有人知道它是如何工作的或者我应该寻找什么来理解它,我将不胜感激。
提前致谢。
更新:有人指出 ripple-button
是 CSS 文件的一部分,可以在 GitHub 项目中找到。
我复制了 MaterialButton
class 并将其粘贴到一个新项目中,因此它无法通过仅提及它来访问 ripple-button
。然而,事实证明,如果我删除这行代码,按钮就会变窄。我可以通过任何方式更改 "ripple-button",结果将是相同的,但该行必须在那里。为什么会这样?
更新 2:我已经理解了 setRippleColor(Color color)
方法的作用:基本上它得到了圆的表皮并得到了它的 children 所以然后它可以在将矩形转换为 Shape
后更改矩形的颜色。它被铸造成一个形状,因为 Rectangle
延伸 Shape
。其实很简单。
有些问题可能会阐明您的困惑。
首先,这些东西不叫 'controllers',而是叫 'controls',这只是为了清楚起见,因为它可能很容易混淆。
方法 getStyleSheets()
returns 类型 String
的 ObservableList
。此列表包含定义应用程序样式的 .css
文件的各种路径。在 Scene
or a Control
of type Parent
上添加样式。有关详细信息,请查看链接的 JavaDoc 或这些文章:
- http://docs.oracle.com/javafx/2/css_tutorial/jfxpub-css_tutorial.htm
- https://blog.idrsolutions.com/2014/04/use-external-css-files-javafx/
样式表定义控件的样式。它们还提供了一些额外的样式 classes,可以在任何 Node
到 getStyleClass()
上设置,这也是 returns 类型 String
的 ObservableList
, 这次定义样式 class 名称。呈现名称时,会在为该 Node
定义的样式表集中查找然后应用。如果没有找到这样的样式 class ,它就会被忽略。 Node
是任何控件的基础 class。
方法 createDefaultSkin()
不会覆盖默认皮肤,正如您在评论中提到的那样,但它定义了默认皮肤(嗯,您是部分正确的,因为 CustomButton
扩展了 Button
Skin
被覆盖)。通常一个控件由一个 'control' class 和一个 'skin' class 组成,至少在 JavaFX 版本 8 发生变化时就是这种情况。有关详细信息,请参阅 the control architecture 上的文章。