कुछ दिनों पहले मैं एक साधारण बटन बनाकर और setStyle() विधि का उपयोग करके String ऑब्जेक्ट्स (जिसका मान इस पर निर्भर करता है कि बटन क्लिक किया गया था या नहीं, के आधार पर इसकी शैली को संशोधित करके) JavaFX के साथ एक कस्टम बटन बनाने में कामयाब रहा। ) पैरामीटर के रूप में।

मुझे नहीं पता था कि उस अनुकूलित बटन को एक class में कैसे बदला जाए जिसे मैं हर बार जब भी उपयोग करना चाहता हूं आयात कर सकता हूं, इसलिए मैं शोध कर रहा हूं और मुझे यह प्रोजेक्ट, जिसमें मटीरियल डिज़ाइन के साथ अनुकूलित कई 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.
    }

}

चूंकि मैं जावाएफएक्स के लिए काफी नया हूं, मैं पूरी गिटहब परियोजना को सोने की खान के रूप में देखता हूं, क्योंकि न केवल मेरे पास ऐसे उदाहरणों तक पहुंच है जो दिखाते हैं कि एक वर्ग के रूप में एक कस्टम नियंत्रक कैसे बनाया जाए जिसे दूसरे से आयात किया जा सकता है, लेकिन यह भी दिखाता है कई अन्य नियंत्रकों को भी कैसे अनुकूलित करें।

समस्या यह है कि कोड की कुछ पंक्तियाँ हैं जो मुझे समझ में नहीं आती हैं (जैसा कि आप देखेंगे यदि आप मेरे द्वारा स्रोत कोड पर की गई टिप्पणियों को पढ़ते हैं)।

उदाहरण के तौर पर, कई बार getStyleClass().add("something") का उपयोग किया जाता है। मुझे पता है कि getStylesheets().add() कैसे काम करता है, लेकिन यह अलग है; मैं कहूंगा कि "शैली" वर्ग एक सीएसएस फ़ाइल से अलग है।

अगर ऐसा है तो यह कैसे काम करता है? जहां तक ​​मैं समझता हूं, getStyleClass().add() विधि के लिए पैरामीटर के रूप में उपयोग किए गए String का उपयोग यह निर्धारित करने में सक्षम होने के लिए किया जाता है कि यह "शैली" वर्ग के अंदर if() के साथ है या नहीं। बाद में बयान; लेकिन वास्तव में यह वर्ग क्या है? मैंने इंटरनेट पर इसके बारे में कोई दस्तावेज नहीं देखा है।

मुझे स्रोत कोड के अंत में setRippleColor() विधि को समझने में भी समस्या है। अगर किसी को इस बात का अंदाजा है कि यह कैसे काम करता है या इसे समझने के लिए मुझे क्या देखना चाहिए, तो मैं इसकी सराहना करता हूं।

अग्रिम में धन्यवाद।

अद्यतन करें: किसी ने बताया कि ripple-button एक CSS फ़ाइल का हिस्सा है जिसे GitHub प्रोजेक्ट पर पाया जा सकता है। मैंने MaterialButton वर्ग की प्रतिलिपि बनाई और उसे एक नए प्रोजेक्ट में चिपकाया, ताकि वह केवल उसका उल्लेख करके ripple-button तक नहीं पहुंच सके। फिर भी, यह पता चला है कि अगर मैं कोड की इस पंक्ति को हटा देता हूं, तो बटन संकुचित हो जाता है। मैं किसी भी चीज़ से "लहर-बटन" बदल सकता हूं और नतीजा वही होगा, लेकिन लाइन वहां होनी चाहिए। ऐसा क्यों होता है?

अद्यतन 2: मैं पहले से ही समझ गया था कि setRippleColor(Color color) विधि क्या करती है: मूल रूप से यह सर्कल की त्वचा प्राप्त करती है और उसके बच्चे प्राप्त करती है ताकि यह आयत का रंग बदल सके एक बार इसे आकृति में कास्ट करने के बाद। इसे एक आकार में ढाला गया है क्योंकि आयताकार आकार को विस्तृत करता है। यह वास्तव में काफी सरल है।

0
SpaceCore186 20 जून 2016, 16:06
सीएसएस फ़ाइल की जाँच करें; मुझे उम्मीद है कि आपको ripple-button वर्ग के लिए कुछ शैली परिभाषाएं मिलेंगी। जहाँ तक setRippleColor का प्रश्न है, क्या समझ में नहीं आता? यह त्वचा के पहले बच्चे को Shape पर कास्ट कर रहा है और setFill(...) को कॉल कर रहा है।
 – 
James_D
20 जून 2016, 16:15
मैंने पहले यही सोचा था, लेकिन कोई CSS फ़ाइल नहीं है। वास्तव में, मैं ripple-color को batman से भी बदल सकता था और प्रोग्राम पूरी तरह से चलेगा, लेकिन अगर मैं getStyleClass().add() विधि को हटा देता हूं, तो प्रोग्राम बटन संकुचित हो जाएगा।
 – 
SpaceCore186
20 जून 2016, 16:28
निश्चित रूप से एक सीएसएस फ़ाइल है: github.com/Kairos-Project/kairos-material-components/blob/…
 – 
James_D
20 जून 2016, 16:29
यह पूरी परियोजना है जिसे मैंने बनाया है ताकि मैं बटन का परीक्षण कर सकूं: i.stack.imgur.com/ JsI1q.png
 – 
SpaceCore186
20 जून 2016, 16:35
1
ठीक है, यह कुछ बहुत ही अजीब कोड है, लेकिन क्या आपने मूल रूप से यह नहीं पहचाना है कि यह कैसे काम करता है? getStyleClass() (जैसा कि आप दस्तावेज़ीकरण) एक ObservableList<String> लौटाता है, इसलिए जब भी आप टिप्पणी करते हैं कि सूची से कुछ जोड़ा या हटाया जाता है, तो श्रोता को बुलाया जाता है और एनीमेशन अपडेट किया जाता है। आप एनिमेशन को बदलने के लिए setFlated(...) या toggled(...) विधियों को कॉल कर सकते हैं (उसी प्रक्रिया से)। यदि आप getStyleClass().add(...) कॉल हटाते हैं, तो सूची कभी नहीं बदलेगी।
 – 
James_D
20 जून 2016, 18:38

1 उत्तर

सबसे बढ़िया उत्तर

कुछ मुद्दे हैं जो आपके भ्रम पर कुछ प्रकाश डाल सकते हैं।

पहले चीजों को 'नियंत्रक' नहीं बल्कि 'नियंत्रण' कहा जाता है, यह सिर्फ स्पष्टता के लिए है क्योंकि यह आसानी से भ्रमित हो सकता है।

विधि getStyleSheets() एक ObservableList प्रकार का String लौटाती है। इस सूची में एप्लिकेशन की शैली को परिभाषित करने वाली .css फाइलों के विभिन्न पथ शामिल हैं। शैलियों को Scene या Control प्रकार का Parent. अधिक जानकारी के लिए लिंक किए गए JavaDoc या इन लेखों की जाँच करें:

शैली पत्रक नियंत्रणों की शैली को परिभाषित करते हैं। वे कुछ अतिरिक्त शैली वर्ग भी प्रदान करते हैं जिन्हें किसी भी पर सेट किया जा सकता है। Node से getStyleClass() तक, जो ObservableList प्रकार का String भी लौटाता है, इस बार शैली वर्ग के नामों को परिभाषित करता है। नाम प्रस्तुत करते समय उस Node के लिए परिभाषित स्टाइल शीट के सेट में देखा जाता है और फिर लागू किया जाता है। यदि ऐसा कोई स्टाइल क्लास नहीं मिलता है तो इसे केवल अनदेखा कर दिया जाता है। A Node किसी भी नियंत्रण के लिए आधार वर्ग है।

विधि createDefaultSkin() डिफ़ॉल्ट त्वचा को ओवरराइड नहीं करती है, जैसा कि आपने अपनी टिप्पणी में उल्लेख किया है, लेकिन यह डिफ़ॉल्ट त्वचा को परिभाषित करती है (वैसे आप आंशिक रूप से सही हैं क्योंकि CustomButton Button का विस्तार करते हैं जो कि Skin ओवरराइड है)। आम तौर पर एक नियंत्रण 'नियंत्रण' वर्ग और 'त्वचा' वर्ग से बना होता है, कम से कम यह जावाएफएक्स के मामले में संस्करण 8 तक था, जब यह बदल गया। पूर्ण विवरण के लिए कंट्रोल आर्किटेक्चर पर आलेख देखें।

1
hotzst 20 जून 2016, 19:22
चीजों को बहुत स्पष्ट करने के लिए धन्यवाद। मैं उन लेखों को पढ़ने जा रहा हूँ जिनका आपने उल्लेख किया है।
 – 
SpaceCore186
20 जून 2016, 19:44