Skip to content

Instantly share code, notes, and snippets.

@dlemmermann
Last active August 28, 2018 08:25
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save dlemmermann/f9bcf4f2a883fff5d0d64faff1b31b87 to your computer and use it in GitHub Desktop.
This code shows how you can use clipping in JavaFX in combination with the alpha channel to implement fade in / out effects (not animations).
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
public class MaskedView extends Control {
public MaskedView(Node content) {
setContent(content);
}
@Override
protected Skin<?> createDefaultSkin() {
return new MaskedViewSkin(this);
}
private final SimpleObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
public final Node getContent() {
return content.get();
}
public final SimpleObjectProperty<Node> contentProperty() {
return content;
}
public final void setContent(Node content) {
this.content.set(content);
}
private final DoubleProperty fadingSize = new SimpleDoubleProperty(this, "fadingSize", 120);
public final double getFadingSize() {
return fadingSize.get();
}
public final DoubleProperty fadingSizeProperty() {
return fadingSize;
}
public final void setFadingSize(double fadingSize) {
this.fadingSize.set(fadingSize);
}
}
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
public class MaskedViewSkin extends SkinBase<MaskedView> {
private final Rectangle leftClip;
private final Rectangle rightClip;
private final Rectangle centerClip;
private final Group group;
private final StackPane stackPane;
public MaskedViewSkin(MaskedView view) {
super(view);
leftClip = new Rectangle();
rightClip = new Rectangle();
centerClip = new Rectangle();
centerClip.setFill(Color.BLACK);
leftClip.setManaged(false);
centerClip.setManaged(false);
rightClip.setManaged(false);
group = new Group(leftClip, centerClip, rightClip);
stackPane = new StackPane();
stackPane.setManaged(false);
stackPane.setClip(group);
getChildren().add(stackPane);
view.contentProperty().addListener((observable, oldContent, newContent) -> buildView(oldContent, newContent));
buildView(null, view.getContent());
view.widthProperty().addListener(it -> updateClip());
view.fadingSizeProperty().addListener(it -> updateClip());
}
private final InvalidationListener translateXListener = it -> updateClip();
private final WeakInvalidationListener weakTranslateXListener = new WeakInvalidationListener(translateXListener);
private void buildView(Node oldContent, Node newContent) {
if (oldContent != null) {
stackPane.getChildren().clear();
oldContent.translateXProperty().removeListener(weakTranslateXListener);
}
if (newContent != null) {
stackPane.getChildren().setAll(newContent);
newContent.translateXProperty().addListener(weakTranslateXListener);
}
updateClip();
}
private void updateClip() {
final MaskedView view = getSkinnable();
Node content = view.getContent();
if (content != null) {
final double fadingSize = view.getFadingSize();
if (content.getTranslateX() < 0) {
leftClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.TRANSPARENT), new Stop(1, Color.BLACK)));
} else {
leftClip.setFill(Color.BLACK);
}
if (content.getTranslateX() + content.prefWidth(-1) > view.getWidth()) {
rightClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.BLACK), new Stop(1, Color.TRANSPARENT)));
} else {
rightClip.setFill(Color.BLACK);
}
}
view.requestLayout();
}
@Override
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
final double fadingSize = Math.min(contentWidth / 2, getSkinnable().getFadingSize());
stackPane.resizeRelocate(snapPosition(contentX), snapPosition(contentY), snapSpace(contentWidth), snapSpace(contentHeight));
resizeRelocate(leftClip, snapPosition(contentX), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
resizeRelocate(centerClip, snapPosition(contentX + fadingSize), snapPosition(contentY), snapSpace(contentWidth - 2 * fadingSize), snapSpace(contentHeight));
resizeRelocate(rightClip, snapPosition(contentX + contentWidth - fadingSize), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
}
private void resizeRelocate(Rectangle rect, double x, double y, double w, double h) {
rect.setLayoutX(x);
rect.setLayoutY(y);
rect.setWidth(w);
rect.setHeight(h);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment