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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
| @Override
protected String getUserAgentStylesheet() {
return getClass().getResource("/jfxtras/labs/internal/scene/control/" +
this.getClass().getSimpleName() + ".css").toString();
}```
This stylesheet is responsible for the look and the connection to a skinning class. It is very important that the control sets a unique styleClass.
```java
getStyleClass().add("mini-icon-button");```
This styleClass is used to declare the connection.
```css
.mini-icon-button {
-fx-skin: "jfxtras.labs.internal.scene.control.skin.MiniIconButtonSkin";
}```
### 2.3 Skin
In the MiniIconButtonSkin class happens all the UI stuff. Create internal components, draw all the things. The MiniIconButton extends Button, so the MiniIconButtonSkin extends ButtonSkin. Most of the work is done in this class and I benefit from extending it.
All the Skin classes extend StackPane. In the last version of the MiniIconAnimationButton, I have extended the StackPane in my button. That makes the refactoring easy. The code for adding the mini-icon to the stackpane is already there. It was just in the wrong class.
```java
public MiniIconButtonSkin(final MiniIconButton miniIconButton) {
super(miniIconButton);
setMiniIcon(miniIconButton.getMiniIcon());
positionMiniIcon(miniIconButton);
calculateAndSetNewMiniIconSize(miniIconButton);
defaultConfigJumpingAnimation();
defaultConfigBlinkingAnimation();
configureJumping(miniIconButton);
configureBlinking(miniIconButton);
startAnimation(miniIconButton);
addImageViewSizeBindings();
addChangeListeners();
}```
And these are the implementations of the methods.
```java
/**
* sets the given mini icon
* @param miniIcon the mini icon
*/
private void setMiniIcon(final ImageView miniIcon) {
getChildren().add(miniIcon);
}
/**
* positions the mini icon on the given {@link Pos}
* @param miniIconButton
*/
private void positionMiniIcon(final MiniIconButton miniIconButton) {
final ImageView miniIcon = miniIconButton.getMiniIcon();
StackPane.setAlignment(miniIcon, miniIconButton.getMiniIconPosition());
StackPane.setMargin(miniIcon, new Insets(MARGIN, MARGIN, MARGIN, MARGIN));
}```
Another change to the first version are the listeners added to properties. Without that, the button can never change after creation (except for the inheritat properties).
```java
/**
* adds the change listeners to the {@link MiniIconButton} properties
*/
private void addChangeListeners() {
final MiniIconButton miniIconButton = (MiniIconButton)getSkinnable();
miniIconButton.animationDurationProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(final ObservableValue<? extends Number> observableValue,
final Number oldDuraction,
final Number newDuration) {
stopAnimation(miniIconButton);
configureJumping(miniIconButton);
configureBlinking(miniIconButton);
startAnimation(miniIconButton);
requestLayout();
}
});
miniIconButton.animationTypeProperty().addListener(new ChangeListener<MiniIconButton.AnimationType>() {
@Override
public void changed(final ObservableValue<? extends MiniIconButton.AnimationType> observableValue,
final MiniIconButton.AnimationType oldAnimationType,
final MiniIconButton.AnimationType newAnimationType) {
startAnimation(miniIconButton);
requestLayout();
}
});
miniIconButton.miniIconPositionProperty().addListener(new ChangeListener<Pos>() {
@Override
public void changed(final ObservableValue<? extends Pos> observableValue,
final Pos oldPosition,
final Pos newPosition) {
StackPane.setAlignment(miniIconButton.getMiniIcon(), newPosition);
requestLayout();
}
});
miniIconButton.miniIconProperty().addListener(new ChangeListener<ImageView>() {
@Override
public void changed(final ObservableValue<? extends ImageView> observableValue,
final ImageView oldMiniIcon,
final ImageView newMiniIcon) {
stopAnimation(miniIconButton);
changeMiniIcon(oldMiniIcon, newMiniIcon);
positionMiniIcon(miniIconButton);
configureJumping(miniIconButton);
configureBlinking(miniIconButton);
calculateAndSetNewMiniIconSize(miniIconButton);
startAnimation(miniIconButton);
requestLayout();
}
});
miniIconButton.miniIconRatioProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(final ObservableValue<? extends Number> observableValue,
final Number oldNumber,
final Number newNumber) {
stopAnimation(miniIconButton);
calculateAndSetNewMiniIconSize(miniIconButton);
startAnimation(miniIconButton);
requestLayout();
}
});
}```
These possible changes are the reason why I decided to have the two animations as members in the skinning class. I need the possibility to start, stop and change them as a reaction to a property change. They could have been created lazy, but this is for the next version.
```java
/**
* transition for the mini-icon jump
*/
private final TranslateTransition jumpTransition = new TranslateTransition();
/**
* the timline for blinking
*/
private final Timeline blinkTimeline = new Timeline();
/**
* configure the blinking has some steps to do
* <ol>
* <li>remove the old {@link KeyFrame} from the timeline</li>
* <li>create a new {@link KeyValue}</li>
* <li>create a new {@link KeyFrame}</li>
* <li>add new {@link KeyFrame} to the timeline</li>
* </ol>
* @param miniIconButton the mini icon button
*/
private void configureBlinking(final MiniIconButton miniIconButton) {
blinkTimeline.getKeyFrames().remove(kf);
final KeyValue kv = new KeyValue(miniIconButton.getMiniIcon().opacityProperty(), MINIMUM_OPACITY_FOR_BLINKING);
kf = new KeyFrame(Duration.millis(miniIconButton.getAnimationDuration()), kv);
blinkTimeline.getKeyFrames().add(kf);
}
/**
* configure the jumping animation
* @param miniIconButton the mini icon button
*/
private void configureJumping(final MiniIconButton miniIconButton) {
final ImageView miniIcon = miniIconButton.getMiniIcon();
jumpTransition.setNode(miniIcon);
jumpTransition.setDuration(Duration.millis(miniIconButton.getAnimationDuration()));
}
/**
* Add the animation based on the animation type.
* @param miniIconButton the mini icon button
*/
private void startAnimation(final MiniIconButton miniIconButton) {
switch (miniIconButton.getAnimationType()) {
case BLINK:
jumpTransition.stop();
blinkTimeline.play();
break;
case JUMP:
blinkTimeline.stop();
jumpTransition.play();
break;
case NONE:
default:
blinkTimeline.stop();
jumpTransition.stop();
break;
}
}
/**
* Stops the animation and resets the start values
* @param miniIconButton the mini icon button
*/
private void stopAnimation(final MiniIconButton miniIconButton) {
final ImageView miniIcon = miniIconButton.getMiniIcon();
jumpTransition.stop();
blinkTimeline.stop();
miniIcon.setOpacity(1.0);
miniIcon.setTranslateY(0.0);
}
/**
* The jump animation changes the position of the mini-icon.
*/
private void defaultConfigJumpingAnimation() {
final double start = 0.0;
final double end = -JUMP_DISTANCE;
jumpTransition.setFromY(start);
jumpTransition.setToY(end);
jumpTransition.setCycleCount(-1);
jumpTransition.setAutoReverse(true);
jumpTransition.setInterpolator(Interpolator.EASE_BOTH);
}
/**
* Blinking animation changes the opacity of the mini-icon.
*/
private void defaultConfigBlinkingAnimation() {
blinkTimeline.setCycleCount(Timeline.INDEFINITE);
blinkTimeline.setAutoReverse(true);
}```
### 2.4 Behavior
The behavior classes are responsible for key and mouse handling. The MiniIconButton has no additional functionality and so no extra behavior class is needed.
### 2.5 Example
In the next Ensemble of the JFXtras Project, there is an example page for the MiniIconButton.
```java
// default values
// ----------------------------
// ratio: 0.25
// Pos.TOP_RIGHT
// animation duration: 500 ms
final MiniIconButton b1 = new MiniIconButton(new ImageView(bigIcon), new ImageView(smallIcon));
b1.setAnimationType(MiniIconButton.AnimationType.BLINK);
// default values
// ----------------------------
// ratio: 0.25
// Pos.TOP_RIGHT
// animation duration: 500 ms
final MiniIconButton b2 = new MiniIconButton("2. Button", new ImageView(bigIcon), new ImageView(smallIcon));
b2.setAnimationType(MiniIconButton.AnimationType.JUMP);
// default values
// ----------------------------
// AnimationType.NONE
// Pos.TOP_RIGHT
final MiniIconButton b3 = new MiniIconButton("3. Button", new ImageView(bigIcon), new ImageView(smallIcon));
b3.setMiniIconRatio(0.1);
// default values
// ----------------------------
// AnimationType.NONE
// Pos.TOP_RIGHT
final MiniIconButton b4 = new MiniIconButton("4. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b4.setMiniIconRatio(1.0);
// default values
// ----------------------------
// AnimationType.NONE
// Pos.TOP_RIGHT
final MiniIconButton b5 = new MiniIconButton("5. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b5.setMiniIconRatio(0.25); // default
// default values
// ----------------------------
// AnimationType.NONE
// Pos.TOP_RIGHT
final MiniIconButton b6 = new MiniIconButton("6. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b6.setMiniIconRatio(0.1);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b7 = new MiniIconButton("7. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b7.setMiniIconPosition(Pos.TOP_LEFT);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b8 = new MiniIconButton("8. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b8.setMiniIconPosition(Pos.TOP_CENTER);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b9 = new MiniIconButton("9. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b9.setMiniIconPosition(Pos.TOP_RIGHT);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b10 = new MiniIconButton("10. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b10.setMiniIconPosition(Pos.CENTER_LEFT);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b11 = new MiniIconButton("11. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b11.setMiniIconPosition(Pos.CENTER);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b12 = new MiniIconButton("12. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b12.setMiniIconPosition(Pos.CENTER_RIGHT);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b13 = new MiniIconButton("13. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b13.setMiniIconPosition(Pos.BOTTOM_LEFT);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b14 = new MiniIconButton("14. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b14.setMiniIconPosition(Pos.BOTTOM_CENTER);
// default values
// ----------------------------
// AnimationType.NONE
// ratio: 0.25
final MiniIconButton b15 = new MiniIconButton("14. Button", new ImageView(bigIcon), new ImageView(mediumIcon));
b15.setMiniIconPosition(Pos.BOTTOM_RIGHT);
// default values
// ----------------------------
// Pos.>
// ratio: 0.25
final MiniIconButton b16 = new MiniIconButton(new ImageView(bigIcon), new ImageView(mediumIcon));
b16.setAnimationDuration(800);
b16.setAnimationType(MiniIconButton.AnimationType.BLINK);
// default values
// ----------------------------
// Pos.TOP_RIGHT
// ratio: 0.25
final MiniIconButton b17 = new MiniIconButton(new ImageView(bigIcon), new ImageView(mediumIcon));
b17.setAnimationDuration(200);
b17.setAnimationType(MiniIconButton.AnimationType.JUMP);
final FlowPane rootPane = FlowPaneBuilder
.create()
.children(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, button)
.hgap(20).vgap(20)
.build();```
## 3. Demo
<iframe width="740" height="555" src="https://www.youtube.com/embed/oJJ3OO02aYo" frameborder="0" allowfullscreen></iframe>
## 4. Sources
The Sources are part of the [JFXtras Project](http://jfxtras.org/ "JFXtras 2").
|