JavaFX对背景的影响

28

我正在使用这个来制作一个具有毛玻璃效果的iOS主题JavaFX2(Java7)应用程序。问题在于这段代码将其效果应用于ImageView上。我希望它可以在窗口后面的任何东西上应用它的效果,就像这样:

是否有任何方法可以实现这一点?我还想要看到上面图像周围的那个小投影效果。

明确一点,我不想要那个滑块或其他任何东西,只是想要窗口透明并且边缘有轻微的阴影效果。我希望使用这个iOS7-ish效果,而不是Aero。

这可能很重要:我正在使用经过修改的Undecorator版本。

4个回答

28

frosty

import javafx.animation.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

public class FrostyTech extends Application {

    private static final double BLUR_AMOUNT = 10;

    private static final Effect frostEffect =
        new BoxBlur(BLUR_AMOUNT, BLUR_AMOUNT, 3);

    private static final ImageView background = new ImageView();
    private static final StackPane layout = new StackPane();

    @Override public void start(Stage stage) {
        layout.getChildren().setAll(background, createContent());
        layout.setStyle("-fx-background-color: null");

        Scene scene = new Scene(
                layout,
                200, 300,
                Color.TRANSPARENT
        );

        Platform.setImplicitExit(false);

        scene.setOnMouseClicked(event -> {
                if (event.getClickCount() == 2) Platform.exit();
        });
        makeSmoke(stage);

        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setScene(scene);
        stage.show();

        background.setImage(copyBackground(stage));
        background.setEffect(frostEffect);

        makeDraggable(stage, layout);
    }

    // copy a background node to be frozen over.
    private Image copyBackground(Stage stage) {
        final int X = (int) stage.getX();
        final int Y = (int) stage.getY();
        final int W = (int) stage.getWidth();
        final int H = (int) stage.getHeight();

        try {
            java.awt.Robot robot = new java.awt.Robot();
            java.awt.image.BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(X, Y, W, H));

            return SwingFXUtils.toFXImage(image, null);
        } catch (java.awt.AWTException e) {
            System.out.println("The robot of doom strikes!");
            e.printStackTrace();

            return null;
        }
    }

    // create some content to be displayed on top of the frozen glass panel.
    private Label createContent() {
        Label label = new Label("Create a new question for drop shadow effects.\n\nDrag to move\n\nDouble click to close");
        label.setPadding(new Insets(10));

        label.setStyle("-fx-font-size: 15px; -fx-text-fill: green;");
        label.setMaxWidth(250);
        label.setWrapText(true);

        return label;
    }

    // makes a stage draggable using a given node.
    public void makeDraggable(final Stage stage, final Node byNode) {
        final Delta dragDelta = new Delta();
        byNode.setOnMousePressed(mouseEvent -> {
            // record a delta distance for the drag and drop operation.
            dragDelta.x = stage.getX() - mouseEvent.getScreenX();
            dragDelta.y = stage.getY() - mouseEvent.getScreenY();
            byNode.setCursor(Cursor.MOVE);
        });
        final BooleanProperty inDrag = new SimpleBooleanProperty(false);

        byNode.setOnMouseReleased(mouseEvent -> {
            byNode.setCursor(Cursor.HAND);

            if (inDrag.get()) {
                stage.hide();

                Timeline pause = new Timeline(new KeyFrame(Duration.millis(50), event -> {
                    background.setImage(copyBackground(stage));
                    layout.getChildren().set(
                            0,
                            background
                    );
                    stage.show();
                }));
                pause.play();
            }

            inDrag.set(false);
        });
        byNode.setOnMouseDragged(mouseEvent -> {
            stage.setX(mouseEvent.getScreenX() + dragDelta.x);
            stage.setY(mouseEvent.getScreenY() + dragDelta.y);

            layout.getChildren().set(
                    0,
                    makeSmoke(stage)
            );

            inDrag.set(true);
        });
        byNode.setOnMouseEntered(mouseEvent -> {
            if (!mouseEvent.isPrimaryButtonDown()) {
                byNode.setCursor(Cursor.HAND);
            }
        });
        byNode.setOnMouseExited(mouseEvent -> {
            if (!mouseEvent.isPrimaryButtonDown()) {
                byNode.setCursor(Cursor.DEFAULT);
            }
        });
    }

    private javafx.scene.shape.Rectangle makeSmoke(Stage stage) {
        return new javafx.scene.shape.Rectangle(
                stage.getWidth(),
                stage.getHeight(),
                Color.WHITESMOKE.deriveColor(
                        0, 1, 1, 0.08
                )
        );
    }

    /** records relative x and y co-ordinates. */
    private static class Delta {
        double x, y;
    }

    public static void main(String[] args) {
        launch(args);
    }
}  

相关问题


哈哈,你又做到了:D。幸好我在赏金结束前几秒钟就给你授予了奖励。我当时真的很紧张。 - Taconut
嗨! 我做了一个功能,使您可以在FrostyPane上加载任何节点,并在窗口不处于焦点状态时关闭模糊效果(使其始终保持一致)。我有几个问题:1.我删除了暂停所有内容50ms的代码片段,但它仍然可以正常工作。我漏掉了什么吗?2.拖动窗口会重新启动正在进行的任何函数。例如,当我移动窗口时,我的闪屏界面会重新开始。是否有任何解决方法?此外,有没有简单的方式来实现调整大小?我想我可以处理它,但我只是想知道您是否知道任何快捷方式。 - Taconut
1
不幸的是,如果背景发生变化,玻璃效果将不会重新绘制。否则,看起来很棒。 - dejuknow
@jewelsea 第69行 "毁灭之机器袭来!" xaxaxaxa +100 - GOXR3PLUS

12

您想要的基于操作系统的窗口装饰视觉效果,只能通过操作系统提供的API实现。因此,下面的StageStyle.TRANSPARENT将其消除。

对于JavaFX内容本身,您可以控制stage > scene > root pane层次结构的外观。舞台和场景不支持高级样式,并且也不旨在支持,因此将其设置为透明以消除它们。

@Override
public void start(Stage primaryStage) {
    StackPane root = new StackPane();
    root.setStyle("-fx-background-color: null;");
    root.setPadding(new Insets(10));

    DoubleProperty doubleProperty = new SimpleDoubleProperty(0);

    Region region = new Region();
    region.styleProperty().bind(Bindings
            .concat("-fx-background-radius:20; -fx-background-color: rgba(56, 176, 209, ")
            .concat(doubleProperty)
            .concat(");"));
    region.setEffect(new DropShadow(10, Color.GREY));

    Slider slider = new Slider(0, 1, .3);
    doubleProperty.bind(slider.valueProperty());

    root.getChildren().addAll(region, slider);

    primaryStage.initStyle(StageStyle.TRANSPARENT);
    Scene scene = new Scene(root, 300, 250);
    scene.setFill(Color.TRANSPARENT);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

然而,投影效果与背景颜色的透明度值不兼容。您可以通过将阴影颜色更改为另一个对比度较高的颜色来观察到这一点。

输出:
enter image description here


这个答案也是正确的。我该怎么样回答两个问题都是“是”呢...? - Taconut

4
为了进一步解释Jewlsea的回答...并且仅使用JavaFX的上面的例子...
虽然这些类不是公共API,但它完全避开了AWT堆栈。以下是一个非公共示例:
// copy a background node to be frozen over.
    private Image copyBackground(Stage stage) {
        final int X = (int) stage.getX();
        final int Y = (int) stage.getY();
        final int W = (int) stage.getWidth();
        final int H = (int) stage.getHeight();
        final Screen screen = Screen.getPrimary();
        try {

            Robot rbt = com.sun.glass.ui.Application.GetApplication().createRobot();
            Pixels p = rbt.getScreenCapture(
                (int)screen.getBounds().getMinX(),
                (int)screen.getBounds().getMinY(), 
                (int)screen.getBounds().getWidth(), 
                (int)screen.getBounds().getHeight(), 
                true
            );

            WritableImage dskTop = new WritableImage((int)screen.getBounds().getWidth(), (int)screen.getBounds().getHeight());
            dskTop.getPixelWriter().setPixels(
                (int)screen.getBounds().getMinX(),
                (int)screen.getBounds().getMinY(),
                (int)screen.getBounds().getWidth(),
                (int)screen.getBounds().getHeight(),
                PixelFormat.getByteBgraPreInstance(),
                p.asByteBuffer(), 
                (int)(screen.getBounds().getWidth() * 4)
            );

            WritableImage image = new WritableImage(W,H);
            image.getPixelWriter().setPixels(0, 0, W, H, dskTop.getPixelReader(), X, Y);

            return image;
        } catch (Exception e) {
            System.out.println("The robot of doom strikes!");
            e.printStackTrace();

            return null;
        }
    }

添加小的阴影效果后的结果:

    DropShadow shdw = new DropShadow();
    shdw.setBlurType(BlurType.GAUSSIAN);
    shdw.setColor(Color.GAINSBORO);
    shdw.setRadius(10);
    shdw.setSpread(0.12);
    shdw.setHeight(10);
    shdw.setWidth(10);
    layout.setEffect(shdw);

fxScreenCapture


0

不透明度是Node的一个属性,它是JavaFX中在屏幕上显示的东西的父类。http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#opacityProperty

因此,您可以在要淡出的对象上设置不透明度。然后,您必须添加某种方式来更改所需对象的不透明度。使用图像中的滑块是一种方法,但还有其他方法。

使用DropShadow效果可以实现投影... http://docs.oracle.com/javafx/2/api/javafx/scene/effect/DropShadow.html。我从未使用过它。这有点高级,但如果评论中有后续问题,我可以帮助回答。


那张图片与我需要的代码无关。它只是展示了我所说的“窗口后面”的意思。我的意思是,我想要从这个答案中得到的效果(https://dev59.com/UWEh5IYBdhLWcg3wRBj5#22630754)应用于Java窗口后面的任何东西。 - Taconut
此外,DropShadow 将应用于窗口后面。如果窗口是不透明的,您将能够看到其后面的阴影。 - Taconut
啊,我明白了。所以Java窗口后面的对象不受JavaFX控制,实际上只是操作系统桌面的一部分? - Jose Martinez
是的!我不知道该怎么办! - Taconut
当我创建JavaFX应用程序时,会得到一个窗口,在该窗口中显示我的程序。我可以对窗口内显示的所有内容进行不透明度和投影处理。但是,我无法控制窗口本身的不透明度,也无法看到窗口后面的内容。我不会说这是不可能的,因为有些非常聪明的人在那里,但这超出了我的专业知识范围。我会密切关注任何解决方案并报告回来。 - Jose Martinez
1
你可以控制窗口的不透明度。我已经做到了。只是在边缘添加阴影有点棘手。此外,我放置的链接是通过对图像应用效果来实现的。我需要知道如何在动态背景上实现这一点。 - Taconut

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接