java - 了解 getStyleClass().add() 和几行代码

标签 java css animation javafx custom-controls

几天前,我通过创建一个简单的按钮并使用 setStyle() 方法和 String 对象(其值取决于是否单击按钮)作为参数。

我不知道如何将该自定义按钮转换为每次我想使用它时都可以导入的,所以我一直在研究并找到了this元素,其中包括几个使用 Material Design 定制的 JavaFX Controller 。我现在感兴趣的 Controller 是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 元素视为一座金矿,因为我不仅可以访问展示如何将自定义 Controller 创建为可以从另一个导入的类的示例,而且它还展示了如何自定义其他几个 Controller 。

问题是有些代码行我不明白(如果你阅读我对源代码所做的评论,你就会明白)。

例如,多次使用 getStyleClass().add("something")。 我知道 getStylesheets().add() 是如何工作的,但这是不同的;我会说“Style”类不同于 CSS 文件。

如果是这样,它是如何工作的?据我所知,用作 getStyleClass().add() 方法参数的 String 用于确定它是否在稍后带有 if() 语句的“Style”类;但是这个类到底是什么?我没有在互联网上看到任何关于它的文档。

我也无法理解源代码末尾的 setRippleColor() 方法。如果有人知道它是如何工作的或者我应该寻找什么来理解它,我将不胜感激。

提前致谢。

更新:有人指出ripple-button 是可以在GitHub 元素上找到的CSS 文件的一部分。 我复制了 MaterialButton 类并将其粘贴到一个新元素中,因此它无法通过仅提及它来访问 ripple-button。然而,事实证明,如果我删除这行代码,按钮就会变窄。我可以通过任何方式更改“波纹按钮”,结果将是相同的,但线必须在那里。为什么会这样?

更新 2:我已经理解了 setRippleColor(Color color) 方法的作用:基本上它获得了圆的皮肤并获得了它的子级,然后它可以改变将矩形转换为 Shape 后的颜色。它被类型转换成一个形状,因为 Rectangle 扩展了 Shape。其实很简单。

最佳答案

有些问题可能会阐明您的困惑。

首先,这些东西不称为“ Controller ”,而是称为“控制”,这只是为了清楚起见,因为它可能很容易混淆。

getStyleSheets() 方法返回 String 类型的 ObservableList。此列表包含定义应用程序样式的 .css 文件的各种路径。在 Scene 上添加样式或 Parent 类型的 Control .有关详细信息,请查看链接的 JavaDoc 或这些文章:

样式表定义控件的样式。他们还提供了一些额外的样式类,可以在任何 Node 上设置。通过 getStyleClass(),它还返回类型为 StringObservableList,这次定义了样式类名称。呈现名称时,会在为该 Node 定义的样式表集中查找名称,然后应用。如果没有找到这样的样式类,它就会被忽略。 Node 是任何控件的基类。

createDefaultSkin() 方法不会覆盖默认皮肤,正如您在评论中提到的那样,但它定义了默认皮肤(嗯,您是部分正确的 CustomButton extends ButtonSkin 被覆盖)。通常,一个控件由一个“控件”类和一个“皮肤”类组成,至少在 JavaFX 版本 8 发生变化时是这样的。请参阅有关 the control architecture 的文章了解详情。

关于java - 了解 getStyleClass().add() 和几行代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37923411/

相关文章:

java - 本地开发设置

java - ActiveMQ的compositeTopic的选择性持久化

jquery - 使用 jquery 和复选框显示/隐藏多个 div

matlab - 如何实现面部 Action 编码系统(FACS)?

javascript - 对于某些浏览器,元素不会消失

java - One Play 2 Framework App - 同时使用 java 和 scala

java - web.xml 中的 contextConfigLocation 初始化参数

javascript - 自定义图表需要更多帮助

jquery - 手机网站。视口(viewport)宽度在返回纵向时卡住

android - GridView 行删除和动画 - Android