15

I'm making an iOS7-themed JavaFX2/FXML project and I was wondering how I could make a Rectangle object have a iOS7-like frosted glass effect.

I'd also like it to have a small shadow. This is tricky, since you might be able to see the shadow behind the semi-transparent object. I'd just like it to be present around the edges.

Is this possible? Here's a picture showing the desired effect (not including the small drop-shadow):

I'd like it to look like this

UPDATE: Here's a continuation of the issue. This is going to look amazing :D.

Community
  • 1
  • 1
Taconut
  • 951
  • 4
  • 10
  • 29

1 Answers1

22

Sample Solution

frost

Run the program below and scroll or swipe up to show the glass pane.

The purpose of the program is just to sample the techniques involved not to act as a general purpose library for the frost effect.

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); }
}  

Source image

Save this image parallel to the Java source as a file named ios-screenshot.png and have your build system copy it to the target directory for the binary output of the build.

ios-screenshot

Answers to additional questions

"JDK 8," would that happen to be a requirement of this?

The sample code above is written against JDK 8. Porting it back to JDK 7 by replacing the lambda calls with anonymous inner classes is pretty trivial.

In general, Java 7 is pretty dated for JavaFX work. I advise upgrading at your earliest convenience to work with a Java 8 minimum version.

initiating your Panes with arguments

More convenient constructors for most parent nodes is a Java 8 feature. You can easily convert the Java 8 format:

 StackPane stack = new StackPane(child1, child2);

To Java 7:

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

will this working if the desktop is behind a frosty pane?

Not directly, you can create a new question for that.

Update: Related Questions

User created: JavaFX effect on background to allow the frosted effect to apply to a window over a desktop background.

Another user created: How do I create a JavaFX transparent stage with shadows on only the border? to apply a halo shadow effect around this window.

Community
  • 1
  • 1
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 2
    I'm speechless… That effect looks beautiful! It looks even more frosty than the image I used. @GGrec said "JDK 8," would that happen to be a requirement of this? – Taconut Mar 25 '14 at 11:48
  • @Taconut He used lambda expressions in the example above. You could simply replace them to compile with JDK 7. – Georgian Mar 25 '14 at 11:55
  • I got it working without the lambdas, but you're initiating your Panes with arguments. What's with that? Also, will this working if the desktop is behind a frosty pane? – Taconut Mar 25 '14 at 19:16
  • Updated answer to address additional questions. – jewelsea Mar 25 '14 at 19:48
  • It seems to be working as I designed it lawl. It may not work exactly like iOS 7 because I have never used iOS 7. It is just a proof of concept. You can use the edit button to modify it to make any changes you wish. – jewelsea Apr 09 '14 at 11:24
  • Unfortunately, if the background changes, the glass effect won't redraw. Otherwise, looks great. – dejuknow Aug 26 '14 at 05:03