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