You are here
Extendable navigation pane in JavaFX
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:
import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ContentDisplay; import javafx.scene.effect.BlurType; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration; public class ExtendableNavigation extends Application { private static final int deltaXNavButton1 = 20; private static final int deltaXNavButton2 = 10; private static final int deltaXNavButton3 = -10; private static final int deltaXNavButton4 = -20; @FXML private AnchorPane extendableNavigationPane; @FXML @FXML @FXML @FXML private DropShadow dropShadowForSelectedPane; launch(args); } @Override Parent root = FXMLLoader.load(getClass().getResource("extendableNavigation.fxml")); Scene scene = new Scene(root, 600, 200); scene.getStylesheets().add("my.css"); stage.setTitle("Extendable navigation pane demo"); stage.setScene(scene); stage.show(); } @FXML void initialize() { clipRect.setWidth(extendableNavigationPane.getPrefWidth()); setIcon(navButton1, "16px-Asclepius_staff.svg.png"); setIcon(navButton2, "32px-Simple_crossed_circle.svg.png"); setIcon(navButton3, "32px-Sinnbild_Autobahnkreuz.svg.png"); setIcon(navButton4, "32px-Yin_yang.svg.png"); hidePane(); } button.setGraphic(new ImageView(image)); button.setContentDisplay(ContentDisplay.TOP); } @FXML private void showPane() { // Animation for showing the pane completely Timeline timelineDown = new Timeline(); final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), extendableNavigationPane.getHeight()); final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0); final KeyValue kvDwn3 = new KeyValue(extendableNavigationPane.translateYProperty(), 0); final KeyFrame kfDwn = new KeyFrame(Duration.millis(100), createBouncingEffect(extendableNavigationPane.getHeight()), kvDwn1, kvDwn2, kvDwn3); // Animation for moving button 1 final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), -deltaXNavButton1); final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1); // Animation for moving button 2 final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), -deltaXNavButton2); final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2); // Animation for moving button 3 final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), -deltaXNavButton3); final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3); // Animation for moving button 1 final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), -deltaXNavButton4); final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4); timelineDown.getKeyFrames().addAll(kfDwn, kfB1, kfB2, kfB3, kfB4); timelineDown.play(); } @FXML private void hidePane() { // Animation for hiding the pane.. Timeline timelineUp = new Timeline(); final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 55); final KeyValue kvUp2 = new KeyValue(extendableNavigationPane.translateYProperty(), 10); final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2); // Animation for moving button 1 final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), deltaXNavButton1); final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1); final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), deltaXNavButton2); final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2); final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), deltaXNavButton3); final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3); final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), deltaXNavButton4); final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4); timelineUp.getKeyFrames().addAll(kfUp, kfB1, kfB2, kfB3, kfB4); timelineUp.play(); } @FXML private void selectPane1() { deselectAllPanes(); navButton1.setEffect(dropShadowForSelectedPane); } @FXML private void selectPane2() { deselectAllPanes(); navButton2.setEffect(dropShadowForSelectedPane); } @FXML private void selectPane3() { deselectAllPanes(); navButton3.setEffect(dropShadowForSelectedPane); } @FXML private void selectPane4() { deselectAllPanes(); navButton4.setEffect(dropShadowForSelectedPane); } private void deselectAllPanes() { navButton1.setEffect(null); navButton2.setEffect(null); navButton3.setEffect(null); navButton4.setEffect(null); } private EventHandler<ActionEvent> createBouncingEffect(double height) { final Timeline timelineBounce = new Timeline(); timelineBounce.setCycleCount(2); timelineBounce.setAutoReverse(true); final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (height - 15)); final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15); final KeyValue kv3 = new KeyValue(extendableNavigationPane.translateYProperty(), -15); final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3); timelineBounce.getKeyFrames().add(kf1); // Event handler to call bouncing effect after the scroll down is // finished. EventHandler<ActionEvent> handler = new EventHandler<ActionEvent>() { @Override timelineBounce.play(); } }; return handler; } }
Styles are defined in my.css:
.pane{ -fx-background-color: white; } .navigation-button{ -fx-font-size: 0.9em; -fx-background-color: transparent; -fx-graphic-text-gap: 0px; -fx-text-fill: gray; } .navigation-button:pressed:hover{ -fx-text-fill: black; } .navigation-button:hover{ -fx-text-fill: red; }
Last but not least, extendableNavigation.fxml:
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.net.*?> <?import javafx.collections.*?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <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"> <children> <AnchorPane fx:id="extendableNavigationPane" layoutX="129.0" layoutY="136.0" minHeight="64.0" onMouseEntered="#showPane" onMouseExited="#hidePane" prefHeight="64.0" prefWidth="371.0"> <children> <Button fx:id="navButton1" layoutX="53.0" layoutY="19.0" mnemonicParsing="false" onAction="#selectPane1" style="" styleClass="navigation-button" text="Medic Stuff" /> <Button fx:id="navButton2" layoutX="114.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane2" styleClass="navigation-button" text="Cross Stuff" /> <Button fx:id="navButton3" layoutX="168.0" layoutY="30.0" mnemonicParsing="false" onAction="#selectPane3" styleClass="navigation-button" text="Highway Stuff" /> <Button fx:id="navButton4" layoutX="241.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane4" styleClass="navigation-button" text="Peace, man!" /> </children> </AnchorPane> </children> <stylesheets> <URL value="@my.css" /> </stylesheets> </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. ;)