Extendable navigation pane in JavaFX


Posted by Steven

This week, I implemented a navigation pane in JavaFX that can be used to navigate within an application. It has four buttons with icons. At first, only the icon of each button is shown. At MouseOver, the pane expands and shows the button texts. One button can then be choosen and is highlighted with a drop shadow.

Here's the code as a SSCCE.

This is the controller ExtendableNavigation.java:

  1. import javafx.animation.KeyFrame;
  2. import javafx.animation.KeyValue;
  3. import javafx.animation.Timeline;
  4. import javafx.application.Application;
  5. import javafx.event.ActionEvent;
  6. import javafx.event.EventHandler;
  7. import javafx.fxml.FXML;
  8. import javafx.fxml.FXMLLoader;
  9. import javafx.scene.Parent;
  10. import javafx.scene.Scene;
  11. import javafx.scene.control.Button;
  12. import javafx.scene.control.ContentDisplay;
  13. import javafx.scene.effect.BlurType;
  14. import javafx.scene.effect.DropShadow;
  15. import javafx.scene.image.Image;
  16. import javafx.scene.image.ImageView;
  17. import javafx.scene.layout.AnchorPane;
  18. import javafx.scene.paint.Color;
  19. import javafx.scene.shape.Rectangle;
  20. import javafx.stage.Stage;
  21. import javafx.util.Duration;
  22.  
  23. public class ExtendableNavigation extends Application {
  24.  
  25. private static final int deltaXNavButton1 = 20;
  26. private static final int deltaXNavButton2 = 10;
  27. private static final int deltaXNavButton3 = -10;
  28. private static final int deltaXNavButton4 = -20;
  29.  
  30. @FXML
  31. private AnchorPane extendableNavigationPane;
  32.  
  33. @FXML
  34. private Button navButton1;
  35.  
  36. @FXML
  37. private Button navButton2;
  38.  
  39. @FXML
  40. private Button navButton3;
  41.  
  42. @FXML
  43. private Button navButton4;
  44.  
  45. private Rectangle clipRect;
  46.  
  47. private DropShadow dropShadowForSelectedPane;
  48.  
  49. public static void main(String[] args) {
  50. launch(args);
  51. }
  52.  
  53. @Override
  54. public void start(Stage stage) throws Exception {
  55. Parent root = FXMLLoader.load(getClass().getResource("extendableNavigation.fxml"));
  56.  
  57. Scene scene = new Scene(root, 600, 200);
  58. scene.getStylesheets().add("my.css");
  59.  
  60. stage.setTitle("Extendable navigation pane demo");
  61. stage.setScene(scene);
  62. stage.show();
  63. }
  64.  
  65. @FXML
  66. void initialize() {
  67. clipRect = new Rectangle();
  68. clipRect.setWidth(extendableNavigationPane.getPrefWidth());
  69. setIcon(navButton1, "16px-Asclepius_staff.svg.png");
  70. setIcon(navButton2, "32px-Simple_crossed_circle.svg.png");
  71. setIcon(navButton3, "32px-Sinnbild_Autobahnkreuz.svg.png");
  72. setIcon(navButton4, "32px-Yin_yang.svg.png");
  73. hidePane();
  74.  
  75. dropShadowForSelectedPane = new DropShadow(BlurType.THREE_PASS_BOX, Color.RED, 7, 0.2, 0, 1);
  76. }
  77.  
  78. private void setIcon(Button button, String name) {
  79. Image image = new Image(getClass().getResourceAsStream(name));
  80. button.setGraphic(new ImageView(image));
  81. button.setContentDisplay(ContentDisplay.TOP);
  82. }
  83.  
  84. @FXML
  85. private void showPane() {
  86. System.out.println("Showing pane ... ");
  87.  
  88. // Animation for showing the pane completely
  89. Timeline timelineDown = new Timeline();
  90.  
  91. final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), extendableNavigationPane.getHeight());
  92. final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
  93. final KeyValue kvDwn3 = new KeyValue(extendableNavigationPane.translateYProperty(), 0);
  94. final KeyFrame kfDwn = new KeyFrame(Duration.millis(100), createBouncingEffect(extendableNavigationPane.getHeight()), kvDwn1, kvDwn2,
  95. kvDwn3);
  96.  
  97. // Animation for moving button 1
  98. final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), -deltaXNavButton1);
  99. final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1);
  100.  
  101. // Animation for moving button 2
  102. final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), -deltaXNavButton2);
  103. final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2);
  104.  
  105. // Animation for moving button 3
  106. final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), -deltaXNavButton3);
  107. final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3);
  108.  
  109. // Animation for moving button 1
  110. final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), -deltaXNavButton4);
  111. final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4);
  112.  
  113. timelineDown.getKeyFrames().addAll(kfDwn, kfB1, kfB2, kfB3, kfB4);
  114. timelineDown.play();
  115. }
  116.  
  117. @FXML
  118. private void hidePane() {
  119. System.out.println("Hding pane ... ");
  120.  
  121. // Animation for hiding the pane..
  122. Timeline timelineUp = new Timeline();
  123.  
  124. final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 55);
  125. final KeyValue kvUp2 = new KeyValue(extendableNavigationPane.translateYProperty(), 10);
  126. final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2);
  127.  
  128. // Animation for moving button 1
  129. final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), deltaXNavButton1);
  130. final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1);
  131.  
  132. final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), deltaXNavButton2);
  133. final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2);
  134.  
  135. final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), deltaXNavButton3);
  136. final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3);
  137.  
  138. final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), deltaXNavButton4);
  139. final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4);
  140.  
  141. timelineUp.getKeyFrames().addAll(kfUp, kfB1, kfB2, kfB3, kfB4);
  142. timelineUp.play();
  143. }
  144.  
  145. @FXML
  146. private void selectPane1() {
  147. System.out.println("Selecting pane 1");
  148. deselectAllPanes();
  149. navButton1.setEffect(dropShadowForSelectedPane);
  150. }
  151.  
  152. @FXML
  153. private void selectPane2() {
  154. System.out.println("Selecting pane 2");
  155. deselectAllPanes();
  156. navButton2.setEffect(dropShadowForSelectedPane);
  157. }
  158.  
  159. @FXML
  160. private void selectPane3() {
  161. System.out.println("Selecting pane 3");
  162. deselectAllPanes();
  163. navButton3.setEffect(dropShadowForSelectedPane);
  164. }
  165.  
  166. @FXML
  167. private void selectPane4() {
  168. System.out.println("Selecting pane 4");
  169. deselectAllPanes();
  170. navButton4.setEffect(dropShadowForSelectedPane);
  171. }
  172.  
  173. private void deselectAllPanes() {
  174. navButton1.setEffect(null);
  175. navButton2.setEffect(null);
  176. navButton3.setEffect(null);
  177. navButton4.setEffect(null);
  178. }
  179.  
  180. private EventHandler<ActionEvent> createBouncingEffect(double height) {
  181. final Timeline timelineBounce = new Timeline();
  182. timelineBounce.setCycleCount(2);
  183. timelineBounce.setAutoReverse(true);
  184. final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (height - 15));
  185. final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
  186. final KeyValue kv3 = new KeyValue(extendableNavigationPane.translateYProperty(), -15);
  187. final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3);
  188. timelineBounce.getKeyFrames().add(kf1);
  189.  
  190. // Event handler to call bouncing effect after the scroll down is
  191. // finished.
  192. EventHandler<ActionEvent> handler = new EventHandler<ActionEvent>() {
  193. @Override
  194. public void handle(ActionEvent event) {
  195. timelineBounce.play();
  196. }
  197. };
  198. return handler;
  199. }
  200. }

Styles are defined in my.css:

  1. .pane{
  2. -fx-background-color: white;
  3. }
  4.  
  5. .navigation-button{
  6. -fx-font-size: 0.9em;
  7. -fx-background-color: transparent;
  8. -fx-graphic-text-gap: 0px;
  9. -fx-text-fill: gray;
  10. }
  11.  
  12. .navigation-button:pressed:hover{
  13. -fx-text-fill: black;
  14. }
  15.  
  16. .navigation-button:hover{
  17. -fx-text-fill: red;
  18. }

Last but not least, extendableNavigation.fxml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2.  
  3. <?import java.lang.*?>
  4. <?import java.net.*?>
  5. <?import javafx.collections.*?>
  6. <?import javafx.geometry.*?>
  7. <?import javafx.scene.control.*?>
  8. <?import javafx.scene.layout.*?>
  9.  
  10. <AnchorPane minHeight="200.0" prefHeight="200.0" prefWidth="600.0" styleClass="pane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="ExtendableNavigation">
  11. <children>
  12. <AnchorPane fx:id="extendableNavigationPane" layoutX="129.0" layoutY="136.0" minHeight="64.0" onMouseEntered="#showPane" onMouseExited="#hidePane" prefHeight="64.0" prefWidth="371.0">
  13. <children>
  14. <Button fx:id="navButton1" layoutX="53.0" layoutY="19.0" mnemonicParsing="false" onAction="#selectPane1" style="" styleClass="navigation-button" text="Medic Stuff" />
  15. <Button fx:id="navButton2" layoutX="114.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane2" styleClass="navigation-button" text="Cross Stuff" />
  16. <Button fx:id="navButton3" layoutX="168.0" layoutY="30.0" mnemonicParsing="false" onAction="#selectPane3" styleClass="navigation-button" text="Highway Stuff" />
  17. <Button fx:id="navButton4" layoutX="241.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane4" styleClass="navigation-button" text="Peace, man!" />
  18. </children>
  19. </AnchorPane>
  20. </children>
  21. <stylesheets>
  22. <URL value="@my.css" />
  23. </stylesheets>
  24. </AnchorPane>

 

Get the complete code (with the missing four images) from Github. As I stated there: This project is just a prototype and doesn't have the best code styling and architecture. Just copy what you need and don't laugh. ;)

Category: