JavaFX button with ripple effect

I watched material design principle from google and I wanted to try implement some of it in JavaFX.
At first I tried to create ripple effect. I picked button class, learn internal code from JavaFX and this is result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class MaterialDesignButton extends Button {
private Circle circleRipple;
private Rectangle rippleClip = new Rectangle();
private Duration rippleDuration = Duration.millis(250);
private double lastRippleHeight = 0;
private double lastRippleWidth = 0;
private Color rippleColor = new Color(0, 0, 0, 0.11);
public MaterialDesignButton(String text) {
super(text);
getStyleClass().addAll("md-button");
createRippleEffect();
}
@Override
protected Skin<?> createDefaultSkin() {
final ButtonSkin buttonSkin = new ButtonSkin(this);
// Adding circleRipple as fist node of button nodes to be on the bottom
this.getChildren().add(0, circleRipple);
return buttonSkin;
}
private void createRippleEffect() {
circleRipple = new Circle(0.1, rippleColor);
circleRipple.setOpacity(0.0);
// Optional box blur on ripple - smoother ripple effect
//circleRipple.setEffect(new BoxBlur(3, 3, 2));
// Fade effect bit longer to show edges on the end of animation
final FadeTransition fadeTransition = new FadeTransition(rippleDuration, circleRipple);
fadeTransition.setInterpolator(Interpolator.EASE_OUT);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
final Timeline scaleRippleTimeline = new Timeline();
final SequentialTransition parallelTransition = new SequentialTransition();
parallelTransition.getChildren().addAll(
scaleRippleTimeline,
fadeTransition
);
// When ripple transition is finished then reset circleRipple to starting point
parallelTransition.setOnFinished(event -> {
circleRipple.setOpacity(0.0);
circleRipple.setRadius(0.1);
});
this.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
parallelTransition.stop();
// Manually fire finish event
parallelTransition.getOnFinished().handle(null);
circleRipple.setCenterX(event.getX());
circleRipple.setCenterY(event.getY());
// Recalculate ripple size if size of button from last time was changed
if (getWidth() != lastRippleWidth || getHeight() != lastRippleHeight)
{
lastRippleWidth = getWidth();
lastRippleHeight = getHeight();
rippleClip.setWidth(lastRippleWidth);
rippleClip.setHeight(lastRippleHeight);
// try block because of possible null of Background, fills ...
try {
rippleClip.setArcHeight(this.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
rippleClip.setArcWidth(this.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
circleRipple.setClip(rippleClip);
} catch (Exception e) {
}
// Getting 45% of longest button's length, because we want edge of ripple effect always visible
double circleRippleRadius = Math.max(getHeight(), getWidth()) * 0.45;
final KeyValue keyValue = new KeyValue(circleRipple.radiusProperty(), circleRippleRadius, Interpolator.EASE_OUT);
final KeyFrame keyFrame = new KeyFrame(rippleDuration, keyValue);
scaleRippleTimeline.getKeyFrames().clear();
scaleRippleTimeline.getKeyFrames().add(keyFrame);
}
parallelTransition.playFromStart();
});
}
public void setRippleColor(Color color) {
circleRipple.setFill(color);
}
}

I don’t describe the code because of comments that you can find in more important fragments.
This is only test case class, not production ready.

You can also watch how it look in practise:

I inserted sample application from video on github -> https://github.com/nonameplum/md-button-fx-sample

Have fun.