JavaFX中的毛玻璃效果是什么?

16

我正在制作一个iOS7风格的JavaFX2/FXML项目,想知道如何让矩形对象具有类似于iOS7的毛玻璃效果。

我还希望它具有一个小阴影。这很棘手,因为您可能能够在半透明对象后面看到阴影。我只希望它存在于边缘周围。

这种效果可行吗?下面是显示所需效果的图片(不包括小的投影):

我希望它看起来像这样

更新:这里是问题的续篇。 这将会看起来很棒:D。

1个回答

23

样例解决方案

frost

运行下面的程序,向上滚动或滑动以显示玻璃窗格。
该程序的目的仅是示例涉及的技术,并不作为霜效应的通用库。
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.*;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

// slides a frost pane in on scroll or swipe up; slides it out on scroll or swipe down.
public class Frosty extends Application {

    private static final double W = 330;
    private static final double H = 590;

    private static final double BLUR_AMOUNT = 60;
    private static final Duration SLIDE_DURATION = Duration.seconds(0.4);

    private static final double UPPER_SLIDE_POSITION = 100;

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

    @Override public void start(Stage stage) {
        DoubleProperty y = new SimpleDoubleProperty(H);

        Node background = createBackground();
        Node frost      = freeze(background, y);
        Node content    = createContent();
        content.setVisible(false);

        Scene scene = new Scene(
                new StackPane(
                        background,
                        frost,
                        content
                )
        );

        stage.setScene(scene);
        stage.show();

        addSlideHandlers(y, content, scene);
    }

    // create a background node to be frozen over.
    private Node createBackground() {
        Image backgroundImage = new Image(
                getClass().getResourceAsStream("ios-screenshot.png")
        );
        ImageView background = new ImageView(backgroundImage);
        Rectangle2D viewport = new Rectangle2D(0, 0, W, H);
        background.setViewport(viewport);

        return background;
    }

    // create some content to be displayed on top of the frozen glass panel.
    private Label createContent() {
        Label label = new Label("The overlaid text is clear and the background below is frosty.");

        label.setStyle("-fx-font-size: 25px; -fx-text-fill: midnightblue;");
        label.setEffect(new Glow());
        label.setMaxWidth(W - 20);
        label.setWrapText(true);

        return label;
    }

    // add handlers to slide the glass panel in and out.
    private void addSlideHandlers(DoubleProperty y, Node content, Scene scene) {
        Timeline slideIn = new Timeline(
                new KeyFrame(
                        SLIDE_DURATION,
                        new KeyValue(
                                y,
                                UPPER_SLIDE_POSITION
                        )
                )
        );

        slideIn.setOnFinished(e -> content.setVisible(true));

        Timeline slideOut = new Timeline(
                new KeyFrame(
                        SLIDE_DURATION,
                        new KeyValue(
                                y,
                                H
                        )
                )
        );

        scene.setOnSwipeUp(e -> {
            slideOut.stop();
            slideIn.play();
        });

        scene.setOnSwipeDown(e -> {
            slideIn.stop();
            slideOut.play();
            content.setVisible(false);
        });

        // scroll handler isn't necessary if you have a touch screen.
        scene.setOnScroll((ScrollEvent e) -> {
            if (e.getDeltaY() < 0) {
                slideOut.stop();
                slideIn.play();
            } else {
                slideIn.stop();
                slideOut.play();
                content.setVisible(false);
            }
        });
    }

    // create a frosty pane from a background node.
    private StackPane freeze(Node background, DoubleProperty y) {
        Image frostImage = background.snapshot(
                new SnapshotParameters(),
                null
        );
        ImageView frost = new ImageView(frostImage);

        Rectangle filler = new Rectangle(0, 0, W, H);
        filler.setFill(Color.AZURE);

        Pane frostPane = new Pane(frost);
        frostPane.setEffect(frostEffect);

        StackPane frostView = new StackPane(
                filler,
                frostPane
        );

        Rectangle clipShape = new Rectangle(0, y.get(), W, H);
        frostView.setClip(clipShape);

        clipShape.yProperty().bind(y);

        return frostView;
    }

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

源图像

将此图像与Java源文件平行保存为名为ios-screenshot.png的文件,并让您的构建系统将其复制到构建的二进制输出目录。

ios-screenshot

回答其他问题

"JDK 8",这是必须的吗?

上面的示例代码是针对JDK 8编写的。通过用匿名内部类替换lambda调用将其移植回JDK 7非常简单。

一般来说,Java 7对于JavaFX工作而言已经过时了。我建议您尽快升级到Java 8最低版本以进行工作。

使用参数初始化您的Panes

对于大多数父节点而言,更方便的构造函数是Java 8特性。您可以轻松地将Java 8格式转换为:

 StackPane stack = new StackPane(child1, child2);

到Java 7:

 StackPane stack = new StackPane();
 stack.getChildren().setAll(child1, child2);

如果桌面在霜冻玻璃后面,这个能用吗?

不能直接使用,您可以创建一个新的问题来解决。

更新:相关问题

用户创建了JavaFX对背景的影响,以允许模糊效果应用于覆盖桌面背景的窗口。

另一个用户创建了如何在JavaFX中创建仅在边框上具有阴影的透明舞台?,以在此窗口周围应用光晕阴影效果。


2
我无言以对...那个效果看起来很美!它看起来比我使用的图像更加冰冷。@GGrec说“JDK 8”,这是否是此要求的必备条件? - Taconut
@Taconut 在上面的例子中使用了lambda表达式。你可以简单地将它们替换为JDK 7进行编译。 - Georgian
我已经让它在没有lambda的情况下工作了,但你正在使用参数初始化你的Panes。这是怎么回事?另外,如果桌面在一个有霜的窗格后面,这个会工作吗? - Taconut
更新答案以回答额外的问题。 - jewelsea
它似乎按照我设计的方式运作,哈哈。它可能不会像iOS 7一样完全工作,因为我从未使用过iOS 7。这只是一个概念验证。您可以使用编辑按钮进行修改,以进行任何所需更改。 - jewelsea
不幸的是,如果背景发生变化,玻璃效果将不会重新绘制。否则,看起来很棒。 - dejuknow

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