理解getStyleClass()。add()和几行代码 [英] Understanding getStyleClass().add() and a few lines of code

查看:1588
本文介绍了理解getStyleClass()。add()和几行代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几天前,我设法使用JavaFX创建一个自定义按钮,创建一个简单的按钮并使用 setStyle()方法修改它的样式 String 对象(其值取决于是否单击按钮)作为参数。

A few days ago I managed to create a custom button with JavaFX by creating a simple button and modifying it's style using the setStyle() method with String objects (whose values vary depending on if the button was clicked or not) as parameters.

我不知道如何将自定义按钮转换为,每次我都可以导入想要使用它,所以我一直在研究,我找到了这个项目,其中包括使用Material Design定制的多个JavaFX控制器。我现在感兴趣的控制器是 MaterialButton ,其源代码如下:

I didn't know how to convert that customized button into a class that I can import each time that I want to use it, so I've been researching and I found this project, which includes several JavaFX controllers customized with Material Design. The controller in which I'm interested right now is MaterialButton, whose source code is the following:

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项目视为金矿,因为我不仅可以访问示例,了解如何创建自定义控制器作为可以从另一个导入的类,还可以显示如何自定义其他几个控制器。

As I'm pretty new to JavaFX, I see the whole GitHub project as a gold mine, since not only I have access to examples that show how to create a custom controller as a class that can be imported from another, but also it shows how to customize several other controllers too.

问题是有一些我不理解的代码行(如果您阅读我对源代码所做的评论,您将会这样做) 。

The problem is that there are some lines of code that I don't understand (as you'll se if you read the comments I made on the source code).

例如,有几次 getStyleClass()。add(something)是用过的。
我知道 getStylesheets()。add()是如何工作的,但这是不同的;我会说Style类与CSS文件不同。

As an example, there are several times in which getStyleClass().add("something") is used. I know how getStylesheets().add() works, but this is different; I would say that the "Style" class is different from a CSS file.

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

If that's the case, how does it work? For as far as I understand, the Strings used as a parameter for the getStyleClass().add() method are used for being able to determine if it is inside of the "Style" class with an if() statement later; but what exactly is this class? I haven't seend any documentation about it on the internet.

我在理解 setRippleColor()方法时遇到问题在源代码的最后也是。如果有人知道它是如何工作的,或者我应该注意什么来理解它,我会很感激。

I have problems understanding the setRippleColor() method at the end of the source code, too. If somebody has an idea of how it works or what should I look up for for understanding it, I'd appreciate it.

提前致谢。

UPDATE :有人指出 ripple-button 是CSS文件的一部分,可以找到GitHub项目。
我复制了 MaterialButton 类并将其粘贴到一个新项目中,因此它无法访问 ripple-button 只提一下。然而,事实证明,如果我删除这行代码,按钮会缩小。我可以用任何东西改变波纹按钮,结果将是相同的,但线必须在那里。为什么会发生这种情况?

UPDATE: Somebody pointed out that ripple-button is part of a CSS file that can be found on the GitHub project. I copied the MaterialButton class and pasted it in a new project, so it can't access to ripple-button by just mentioning it. Nevertheless, it turns out that if I delete this line of code, the button is narrowed. I can change "ripple-button" by anything and the result is going to be the same, but the line has to be there. Why does this happen?

UPDATE 2 :我已经明白 setRippleColor(颜色)方法确实:基本上它获取了圆形的皮肤并获得了它的子项,因此它可以在将其转换为 Shape 后更改矩形的颜色。它被铸造成一个形状因为 Rectangle extends Shape 。实际上这很简单。

UPDATE 2: I already understood what the setRippleColor(Color color) method does: basically it gets the skin of the circle and gets its children so then it can change the rectangle's color once it's casted into a Shape. It's casted into a shape because Rectangle extends Shape. It's pretty simple actually.

推荐答案

有些问题可能会让你感到困惑。

There are some issues that might shed some light on your confusion.

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

First the things are not called 'controllers' but 'controls', this just for clarity as it might be easily confused.

方法 getStyleSheets()返回 String 类型的 ObservableList 。此列表包含定义应用程序样式的 .css 文件的各种路径。样式添加在 场景中 或类型 Control api / javafx / scene / Parent.htmlrel =nofollow> Parent 。有关更多详细信息,请查看链接的JavaDoc或这些文章:

The method getStyleSheets() returns an ObservableList of type String. This list contains the various path to the .css files defining the style of the application. Styles are added either on a Scene or a Control of type Parent. For more details check the linked JavaDoc or these articles:

  • http://docs.oracle.com/javafx/2/css_tutorial/jfxpub-css_tutorial.htm
  • https://blog.idrsolutions.com/2014/04/use-external-css-files-javafx/

样式表定义控件的样式。它们还提供了一些可以在任何上设置的其他样式类。 节点 getStyleClass(),它还会返回 ObservableList 类型 String ,这次定义样式类名称。渲染名称时,在为该节点定义的样式表集中查找,然后应用。如果没有找到这样的样式类,则会被忽略。 节点是任何控件的基类。

The style sheets define the style of the controls. They also provide some additional style classes that can be set on any Node through getStyleClass(), which also returns an ObservableList of type String, this time defining the style class names. When rendering the name is looked up in the set of style sheets defined for that Node and then applied. If no such style class is found it is just ignored. A Node is the base class for any control.

方法 createDefaultSkin()不会覆盖默认皮肤,正如您在评论中提到的那样,但它定义了默认皮肤(嗯,您部分正确,因为 CustomButton extends 按钮其中皮肤被覆盖)。一般来说,一个控件由一个'control'类和一个'skin'类组成,至少在JavaFX到版本8时就是这种情况,当它发生变化时。有关详细信息,请参阅有关控制架构的文章。

The method createDefaultSkin() does not override the default skin, as you mentioned in your comment, but it defines the default skin (Well you are partially correct as CustomButton extends Button which's Skin is overriden). Generally a control is made up of a 'control' class and a 'skin' class, at least this was the case with JavaFX up till version 8, when it changed. See the article on the control architecture for full detail.

这篇关于理解getStyleClass()。add()和几行代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆