JavaFX: MiniIconAnimationButton

Posted on August 5, 2012

A button with different effects to support the user experience.

In the last JavaFX blog post I showed how a button with an glowing effect can be used to catch the users attention. In this blog post I develop a button based on StackPane to have a normal button in the back and an optional animating image icon in the front.

1. UI-Mockup

MiniIconAnimationButton Mockup

MiniIconAnimationButton Mockup

2. The code

2.1 Constructor

We need for the button a text, an icon and a mini-icon. As an optional parameter we can set the animation type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
   * Extended button without an animation
   * @param text button text
   * @param graphic button icon
   * @param notifyImage mini icon
   */
  public MiniIconAnimationButton(String text, 
                                 Node graphic, 
                                 ImageView notifyImage) {
    this(text, graphic, notifyImage, AnimationType.NONE);
  }
 
  /**
   * Extended button with an animation
   * @param text button text
   * @param graphic button icon
   * @param notifyImage mini icon
   * @param type animation type for mini icon
   */
  public MiniIconAnimationButton(String text, 
                                 Node graphic, 
                                 ImageView notifyImage, 
                                 AnimationType type) { ... }

2.2 AnimationType

There are three animation types for this button.

  • NONE means that there is no animation. The mini-icon is just shown in the upper right corner.
  • JUMP means that the mini-icon moves up and down frequently.
  • BLINK means that the mini-icon changes the opacity from visible to invisible and back.
1
2
3
4
/**
   * Type of animation
   */
  public enum AnimationType { NONE, JUMP, BLINK };

2.3 add the controls to the stack

The button is an extension of the StackPane to position the button in the back and the mini-icon in the front. The mini-icon is placed on the upper right and 4 pixels away from the edges.

1
2
3
4
5
6
7
8
/**
  * add the button to the background and the mini-icon to the front
  */
 private void stackControls() {
   StackPane.setAlignment(imageView, Pos.TOP_RIGHT);
   StackPane.setMargin(imageView, new Insets(4, 4, 4, 4)) ;
   getChildren().addAll(button, imageView);
 }

2.4 bind the size of the mini-icon to the button size

The mini-icon should be smaller than the button self. I decided to make the mini-icon a quater of the size of the button. To resize the icon if the button is resized I add an ChangeListener to the binding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* bind the size of the mini-icon to the button size
*/
private void addImageViewSizeBindings() {
 final ReadOnlyDoubleProperty widthProperty = button.widthProperty();
 final ReadOnlyDoubleProperty heightProperty = button.heightProperty();
 final DoubleBinding widthBinding = widthProperty.divide(4.0);
 final DoubleBinding heightBinding = heightProperty.divide(4.0);
 imageView.setFitWidth(widthBinding.doubleValue());
 imageView.setFitHeight(heightBinding.doubleValue());
 widthBinding.addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue o, Object oldVal, Object newVal) {
     imageView.setFitWidth(widthBinding.doubleValue());
   }
 });
 heightBinding.addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue o, Object oldVal, Object newVal) {
     imageView.setFitHeight(heightBinding.doubleValue());
   }
 });
}

2.5 add the animation

Based on the type the animation method is choosen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
 * add the animation
 */
private void addAnimation() {
  switch (type) {
    case BLINK:
      addBlinkingAnimation();
      break;
    case JUMP:
      addJumpingAnimation();
      break;
    case NONE:
      // none is the default case
    default:
      // noting to animate
      break;
  }
}

2.5.1 JUMP

The jump is done with a transition. There is a start and an end value and the tranistion calculates the positions for each step.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/**
 * the jump animation changes the position of the mini-icon
 */
private void addJumpingAnimation() {
  final TranslateTransition translateTransition =  new TranslateTransition(Duration.millis(200), imageView);
  final double start = 0.0;
  final double end = start - 4.0;
  translateTransition.setFromY(start);
  translateTransition.setToY(end);
  translateTransition.setCycleCount(-1);
  translateTransition.setAutoReverse(true);
  translateTransition.setInterpolator(Interpolator.EASE_BOTH);
  translateTransition.play();
}

The blinking is done by changing the opacity of the mini-icon. This is done with a Timeline and a KeyFrame. The timeline changes the given property from the value at the beginnign to a new value and back.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/**
   * blinking animation changes the opacity of the mini-icon
   */
  private void addBlinkingAnimation() {
    final Timeline timeline = new Timeline();
    timeline.setCycleCount(Timeline.INDEFINITE);
    timeline.setAutoReverse(true);
    final KeyValue kv = new KeyValue(imageView.opacityProperty(), 0.0);
    final KeyFrame kf = new KeyFrame(Duration.millis(700), kv);
    timeline.getKeyFrames().add(kf);
    timeline.play();
  }

2.6 button delegation methods

The rest of the button code is simple delegation code which calls the button methods on the original button.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // delegation methods for button, this class should behave like a normal button                                     //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
  public void setDefaultButton(boolean value) {
    button.setDefaultButton(value);
  }
 
  public void setFont(Font value) {
    button.setFont(value);
  }
 
  public ObjectProperty<ContentDisplay> contentDisplayProperty() {
    return button.contentDisplayProperty();
  }
 
  // ...

3. The main class

 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
package de.billmann.javafx.demo;
 
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.TilePaneBuilder;
import javafx.stage.Stage;
 
/**
 * @author abi
 */
public class Main extends Application {
 
  private ImageView icon1 = new ImageView(new Image(getClass().getResourceAsStream("/images/kcmpartitions.png")));
  private ImageView icon2 = new ImageView(new Image(getClass().getResourceAsStream("/images/kcmpci.png")));
  private ImageView icon3 = new ImageView(new Image(getClass().getResourceAsStream("/images/personal.png")));
 
  private ImageView miniIcon1 = new ImageView(new Image(getClass().getResourceAsStream("/images/bell.png")));
  private ImageView miniIcon2 = new ImageView(new Image(getClass().getResourceAsStream("/images/important.png")));
  private ImageView miniIcon3 = new ImageView(new Image(getClass().getResourceAsStream("/images/kcmdrkonqi.png")));
 
  public static void main(final String[] args) {
    Application.launch(args);
  }
 
  public void start(final Stage stage) {
    final MiniIconAnimationButton b1 = new MiniIconAnimationButton("1. Button", icon1, miniIcon1, MiniIconAnimationButton.AnimationType.JUMP);
    final MiniIconAnimationButton b2 = new MiniIconAnimationButton("2. Button", icon2, miniIcon2, MiniIconAnimationButton.AnimationType.BLINK);
    final MiniIconAnimationButton b3 = new MiniIconAnimationButton("3. Button", icon3, miniIcon3, MiniIconAnimationButton.AnimationType.NONE);
 
    final TilePane rootPane = TilePaneBuilder
        .create()
        .children(b1, b2, b3)
        .padding(new Insets(4, 4, 4, 4))
        .hgap(10)
        .build();
 
    final Scene scene = SceneBuilder
        .create()
        .width(474)
        .height(160)
        .root(rootPane)
        .build();
 
    stage.setScene(scene);
 
    stage.show();
  }
}

4. Video of the effect

5. Source code