Last active
August 28, 2018 08:25
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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