Wednesday 18 January 2012

Sliding in JavaFX (It’s all about clipping)


JavaFX animation package provides many amazing animation/transition effects. Let’s look into another effect, “Sliding” effect of node (relative to the parent node).  The term ‘relative’ refers to, like the node should hide relatively under the parent node by sliding. If you are familiar with “Titled Pane”, I am speaking about the same effect how the content of the pane is shown and hide. I am trying to achieve this with some simple logic and using some properties of a node.

Let’s look into an example, how we can hide/show our custom nodes by implementing this sliding effect. In brief, it is all about clipping our node for some duration of time. There are some amazing blogs/articles available in internet explaining the “clip” functionality of a node. I am going to use this clip feature, to achieve this effect.

Below are the screenshots of the example to which the sliding effect is implemented. On click of “Slide Down”, the top pane (blue color) content is shown and on click of “Slide Up” the top pane content is hidden.


Before going into the actual java code, let me explain the logic pictorially.  In the below image, the red color dotted box is the node that is to be shown/hidden by sliding.  The grey shaded region is the Rectangle that is used to clip the node (Means the visible part of node after clipping). And the black border box is the parent node.

 
To get this effect we need to handle three parameters (A, B & C as shown in picture)
                A – Start Point of the Node
                B – Start point of the clip rectangle
                C – Height of the clip rectangle.

Considering the above scenario the code to slide up (hide) the node(topPane) is as below.

Rectangle2D boxBounds = new Rectangle2D(100, 100, 250, 250);
Rectangle clipRect = new Rectangle();
clipRect.setWidth(boxBounds.getWidth());

StackPane topPane = new StackPane();
topPane.setPrefSize( boxBounds.getWidth(),boxBounds.getHeight());  topPane.setClip(clipRect);
                
/* Animation for scroll up. */
Timeline timelineUp = new Timeline();
timelineUp.setCycleCount(1); 
timelineUp.setAutoReverse(true);

final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), boxBounds.getHeight());
final KeyValue kvUp3 = new KeyValue(topPane.translateYProperty(), -boxBounds.getHeight());
final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3);

timelineUp.getKeyFrames().add(kfUp);


In the above code, for the given amount of duration,
  • Clip rectangle heightProperty is decreased to 0,
  • translateYProperty of clip Rectangle is moved down to amount of height of the node and
  • translateYProperty of node is moved up to the amount of height of the node.

Similarly the code to slide down (show) the node (topPane) is as below

/* Animation for scroll down. */
Timeline timelineDown = new Timeline();
timelineDown.setCycleCount(1);
timelineDown.setAutoReverse(true);

final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), boxBounds.getHeight());
final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
final KeyValue kvDwn3 = new KeyValue(topPane.translateYProperty(), 0);
final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), kvDwn1, kvDwn2, kvDwn3);

timelineDown.getKeyFrames().add(kfDwn);

In the above code, for the given amount of duration,
  • Clip rectangle heightProperty is to the height of the node, 
  • translateYProperty of clip Rectangle is moved up to 0 and 
  • translateYProperty of node is moved down to 0. 

Combining all the logic together, below is the final SSCCE (Short, Self Contained, Correct Example) code of the above example.
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.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SlideEffectDemo extends Application {

 private Rectangle2D boxBounds = new Rectangle2D(100, 100, 250, 200);
 
 private StackPane bottomPane;
 private StackPane topPane;
 private Rectangle clipRect;
 private Timeline timelineUp;
 private Timeline timelineDown;
 
 public static void main(String[] args) {
  Application.launch(args);
 }
 
 @Override
 public void start(Stage stage) throws Exception {
  VBox root = new VBox();
  root.setAlignment(Pos.CENTER);
  root.autosize();
  Scene scene = new Scene(root);
  stage.setTitle("Slide Effect Demo");
  stage.setWidth(350);
     stage.setHeight(300);
     stage.setScene(scene);
     stage.show();
     
  configureBox(root);
 }

 private void configureBox(VBox root) {
  StackPane container = new StackPane();
  container.setPrefHeight(250);
  container.setPrefSize(boxBounds.getWidth(), boxBounds.getHeight());
  container.setStyle("-fx-border-width:1px;-fx-border-style:solid;-fx-border-color:#999999;");
  
  // BOTTOM PANE 
  Stop[] stops = new Stop[] { new Stop(0, Color.web("#F89C8C")), new Stop(1, Color.web("#BE250A"))};
  LinearGradient lg = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops);
  bottomPane = new StackPane();
  bottomPane.getChildren().addAll(RectangleBuilder.create().width(boxBounds.getWidth()).height(boxBounds.getHeight()).fill(lg).build(), 
     TextBuilder.create().text("Click the above \"Slide Down\" button to see the top pane content...").wrappingWidth(200).font(Font.font("Arial", 22)).build());
  
  // TOP PANE 
  Stop[] stops2 = new Stop[] { new Stop(0, Color.web("#FFFFFF")), new Stop(1, Color.web("#50AABC"))};
  LinearGradient lg2 = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops2);
  StackPane sp1 = new StackPane();
  sp1.getChildren().add(TextBuilder.create().text("Click the below \"Slide Up\" button to see the bottom pane content...").wrappingWidth(200).font(Font.font("Arial", 22)).build());
  
  topPane = new StackPane();
  topPane.getChildren().addAll(RectangleBuilder.create().width(boxBounds.getWidth()).height(boxBounds.getHeight()).fill(lg2).build(), sp1);
  container.getChildren().addAll(bottomPane,topPane);
  
  setAnimation();
  
  Group gp = new Group();
  gp.getChildren().add(container);
  root.getChildren().addAll(getActionPane(),gp);
 }

 private void setAnimation(){
  // Initially hiding the Top Pane
  clipRect = new Rectangle();
        clipRect.setWidth(boxBounds.getWidth());
  clipRect.setHeight(0);
  clipRect.translateYProperty().set(boxBounds.getHeight());
  topPane.setClip(clipRect);
  topPane.translateYProperty().set(-boxBounds.getHeight());
  
  // Animation for bouncing effect.
  final Timeline timelineBounce = new Timeline();
  timelineBounce.setCycleCount(2);
  timelineBounce.setAutoReverse(true);
  final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (boxBounds.getHeight()-15));
  final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
  final KeyValue kv3 = new KeyValue(topPane.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 onFinished = new EventHandler() {
            public void handle(ActionEvent t) {
             timelineBounce.play();
            }
        };
        
        timelineDown = new Timeline();
        timelineUp = new Timeline();
        
        // Animation for scroll down.
  timelineDown.setCycleCount(1);
  timelineDown.setAutoReverse(true);
  final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), boxBounds.getHeight());
  final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
  final KeyValue kvDwn3 = new KeyValue(topPane.translateYProperty(), 0);
  final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), onFinished, kvDwn1, kvDwn2, kvDwn3);
  timelineDown.getKeyFrames().add(kfDwn);
  
  // Animation for scroll up.
  timelineUp.setCycleCount(1); 
  timelineUp.setAutoReverse(true);
  final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
  final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), boxBounds.getHeight());
  final KeyValue kvUp3 = new KeyValue(topPane.translateYProperty(), -boxBounds.getHeight());
  final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3);
  timelineUp.getKeyFrames().add(kfUp);
 }
 
 private HBox getActionPane(){
  HBox hb = new HBox();
  hb.setAlignment(Pos.CENTER);
  hb.setSpacing(10);
  hb.setPrefHeight(40);
  Button upBtn = new Button("Slide Up");
  upBtn.setOnAction(new EventHandler() {
   @Override
   public void handle(ActionEvent arg0) {
    timelineUp.play();
   }
  });
  Button downBtn = new Button("Slide Down");
  downBtn.setOnAction(new EventHandler() {
   @Override
   public void handle(ActionEvent arg0) {
    timelineDown.play();
   }
  });
  hb.getChildren().addAll(downBtn,upBtn);
  return hb;
 }
}
If you observe in the above code, an extra timelineBounce is added to show the bounce effect when the top pane slides down.

A more realistic example of this slide effect can be found here.
Below are the screenshots of this example. The search form slides downwards and upwards on click of the “Search” label.


I hope you can relate the similar logic to slide the node towards LEFT and RIGHT, by using the widthProperty and translateXProperty of the node.

Happy Coding!!

5 comments:

  1. hi employee demo source code where to find ...

    ReplyDelete
    Replies
    1. It is in the word "here" :)
      Anyway below is the link.
      https://gist.github.com/95accfa4d8de8b9b3310

      Delete
  2. thx it's vr usefull

    from here i would build an easy lib for manage scrollable panels like this http://liquidslider.com/, do you have any idea where can i search same example or doc? thanks

    ReplyDelete