Basic Transitions, Continued
Rotate
The RotateTransition class rotates a node around its center by transitioning its node variable's rotate member from a starting value to an ending value (both in degrees) over the duration specified by its duration variable. The starting and ending values are specified via RotateTransition variables and node's current rotate value:
- The starting value is specified by fromAngle (of type Number) or (if not present) the node's current rotate value.
- The ending value is specified by toAngle (of type Number) or (if not present) the starting value plus the value of the variable byAngle (of type Number). If both toAngle and byAngle are specified, toAngle takes precedence. /
While developing this article, I encountered a JavaFX example that demonstrates this class in a group-of-shape-nodes context. In contrast, my example rotates a "single line of text" node around its center. Listing 4 presents this example's source code.
Listing 4Main.fx (from a RotateTDemo project).
/* * Main.fx */ package rotatetdemo; import javafx.animation.Interpolator; import javafx.animation.Timeline; import javafx.animation.transition.RotateTransition; import javafx.scene.Scene; import javafx.scene.effect.Reflection; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.scene.text.TextOrigin; import javafx.stage.Stage; Stage { title: "RotateTransition Demo" width: 300 height: 300 var scene: Scene scene: scene = Scene { var text: Text def rt = RotateTransition { node: bind text fromAngle: 0 toAngle: 360 interpolate: Interpolator.LINEAR repeatCount: Timeline.INDEFINITE duration: 3s } content: [ Rectangle { x: 0 y: 0 width: bind scene.width height: bind scene.height fill: Color.ORANGE onMouseClicked: function (me: MouseEvent): Void { if (rt.running and not rt.paused) rt.pause () else rt.play () } } text = Text { content: "JavaFX RotateTransition Demo" font: Font { name: "Arial BOLD" size: 16 } textOrigin: TextOrigin.TOP translateX: bind (scene.width-text.layoutBounds.width)/2 translateY: bind (scene.height-text.layoutBounds.height)/2 effect: Reflection {} } ] } }
This example creates a scene (see Figure 4) consisting of a reflected javafx.scene.text.Text node centered on a javafx.scene.shape.Rectangle node. I've associated a mouse handler with the Rectangle node so that clicking anywhere on the scene results in the rotation being played or paused.
Figure 4 Rotating a reflected line of text around the scene's center point.
Scale
The ScaleTransition class scales a node up or down about its center by transitioning its node variable's scaleX and scaleY members from starting scaling values to ending scaling values over the duration specified by its duration variable. The starting and ending values are specified via ScaleTransition variables and node's current scaleX and scaleY values:
- The starting values are specified by fromX and fromY (both of type Number) or (if not present) the node's current scaleX and scaleY values.
- The ending values are specified by toX and toY (both of type Number) or (if not present) the starting values plus the values of variables byX and byY (both of type Number). If toX, toY, byX, and byY are specified, toX and toY take precedence.
ScaleTransition is especially useful in rich Internet applications that involve images. As the mouse moves over a thumbnail image, the image scales up to a larger size, revealing more detail. This scenario is demonstrated in the Main.fx source code in Listing 5, which is part of a NetBeans ScaleTDemo project.
Listing 5Main.fx (from a ScaleTDemo project).
/* * Main.fx */ package scaletdemo; import javafx.animation.transition.ScaleTransition; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; def BACKGROUND_PAINT = LinearGradient { startX: 0.0 startY: 0.0 endX: 1.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.YELLOW }, Stop { offset: 0.5 color: Color.ORANGE }, Stop { offset: 1.0 color: Color.PINK } ] } def NUMIMAGES = 5; Stage { title: "ScaleTransition Demo" width: 750 height: 400 var scene: Scene scene: scene = Scene { fill: BACKGROUND_PAINT var t: ThumbNail content: Group { content: for (i in [1..NUMIMAGES]) t = ThumbNail { translateX: bind (scene.width+10- NUMIMAGES*t.layoutBounds.width- 10*(NUMIMAGES-1))/2+ (i-1)*(t.layoutBounds.width+10) translateY: bind (scene.height- t.layoutBounds.height)/2 width: 100 height: 100 imageName: "photo{i}" } } } } class ThumbNail extends CustomNode { public var width: Number; public var height: Number; public var imageName: String; var view: ImageView; def stBig = ScaleTransition { node: this fromX: 1.0 fromY: 1.0 toX: 2.0 toY: 2.0 duration: 1s } def stSmall = ScaleTransition { node: this fromX: 2.0 fromY: 2.0 toX: 1.0 toY: 1.0 duration: 1s } public override function create (): Node { Group { content: [ Rectangle { x: 0 y: 0 width: bind width height: bind height arcWidth: 5 arcHeight: 5 strokeWidth: 4 stroke: Color.GRAY } view = ImageView { image: Image { url: "{__DIR__}res/{imageName}.jpg"} x: 4 y: 4 fitWidth: bind width-8 fitHeight: bind height-8 } ] blocksMouse: true onMouseEntered: function (me: MouseEvent): Void { if (stSmall.running) { stSmall.stop (); stBig.fromX = stSmall.node.scaleX; stBig.fromY = stSmall.node.scaleY } else { stBig.fromX = 1.0; stBig.fromY = 1.0 } stBig.toX = 2.0; stBig.toY = 2.0; stBig.node.toFront (); stBig.playFromStart () } onMouseExited: function (me: MouseEvent): Void { if (stBig.running) { stBig.stop (); stSmall.fromX = stBig.node.scaleX; stSmall.fromY = stBig.node.scaleY } else { stSmall.fromX = 2.0; stSmall.fromY = 2.0 } stSmall.toX = 1.0; stSmall.toY = 1.0; stSmall.playFromStart () } } } }
This example lays out five bordered thumbnail images in a row, separated by 10-pixel gaps. Further, these images are horizontally and vertically centered within the scene. Moving the mouse over a thumbnail results in that image scaling to twice its size (see Figure 5); moving the mouse off of the enlarged image results in the image reverting to its thumbnail size.
Figure 5 Scaling up the center image.
While developing this example, I discovered a use for Node's public toFront(): Void function, which moves a Node to the front of its sibling nodes in terms of Z-order. If this function isn't called before scaling up a node, the node to its immediate right partly covers the scaled-up node. (Nodes that appear later in a sequence have a higher Z-order.)
Translate
Finally, the TranslateTransition class moves a node along the x and y axes by transitioning its node variable's translateX and translateY members from starting translation values to ending translation values over the duration specified by its duration variable. The starting and ending values are specified via TranslateTransition variables and node's current translateX and translateY values:
- The starting values are specified by fromX and fromY (both of type Number) or (if not present) the node's current translateX and translateY values.
- The ending values are specified by toX and toY (both of type Number) or (if not present) the starting values plus the values of variables byX and byY (both of type Number). If toX, toY, byX, and byY are specified, toX and toY take precedence.
I've created an application that demonstrates TranslateTransition's usefulness in the context of a scrollable text component. Using this component, you can scroll a line of text horizontally (left to right or right to left), vertically (top to bottom or bottom to top), or both horizontally and vertically. Listing 6 presents this application's source code.
Listing 6Main.fx (from a TranslateTDemo project).
/* * Main.fx */ package translatetdemo; import javafx.animation.Interpolator; import javafx.animation.transition.TranslateTransition; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.effect.DropShadow; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; def BACKGROUND_PAINT = LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.BLUE }, Stop { offset: 1.0 color: Color.LIGHTSKYBLUE } ] } Stage { title: "TranslateTransition Demo" width: 400 height: 200 var scene: Scene scene: scene = Scene { fill: BACKGROUND_PAINT var r: Rectangle var st: ScrollableText content: Group { content: [ r = Rectangle { x: 0 y: 0 width: 200 height: 80 arcWidth: 10 arcHeight: 10 translateX: bind (scene.width-r.layoutBounds.width)/2 translateY: bind (scene.height-r.layoutBounds.height)/2 fill: Color.LIGHTYELLOW stroke: Color.BLACK strokeWidth: 5 onMouseClicked: function (me: MouseEvent): Void { st.play () } } st = ScrollableText { content: "TranslateTransition Demo" font: Font { name: "Times New Roman BOLD" size: 22 } fill: Color.MEDIUMBLUE effect: DropShadow { offsetX: 2 offsetY: 2 radius: 1 spread: 0.25 color: Color.GRAY } x: bind r.translateX+r.layoutBounds.width y: bind scene.height/2 toX: bind -st.layoutBounds.width toY: bind scene.height/2 duration: 5s resize: bind scene.width+scene.height } Rectangle { x: 0 y: 0 width: 200 height: 80 arcWidth: 10 arcHeight: 10 translateX: bind (scene.width-r.layoutBounds.width)/2 translateY: bind (scene.height-r.layoutBounds.height)/2 fill: null stroke: Color.BLACK strokeWidth: 5 } ] clip: Rectangle { x: 0 y: 0 width: 200 height: 80 arcWidth: 10 arcHeight: 10 translateX: bind (scene.width-r.layoutBounds.width)/2 translateY: bind (scene.height-r.layoutBounds.height)/2 fill: Color.LIGHTYELLOW stroke: Color.BLACK strokeWidth: 5 } } } } class ScrollableText extends Text { public var toX: Number; public var toY: Number; public var duration: Duration; public var resize: Number on replace { if (tt.running) tt.playFromStart () } public function play (): Void { tt.playFromStart () } def tt = TranslateTransition { node: this fromX: 0 fromY: 0 toX: bind toX-x toY: bind toY-y duration: bind duration interpolate: Interpolator.LINEAR } }
This application introduces the scrollable text component via the ScrollableText class. Basically, ScrollableText adds arbitrary scrolling behavior to a Text node. In addition to inheriting Text's variables and functions, ScrollableText introduces the following variables and solitary function:
- toX and toY specify the location of the text's upper-left corner when the scroll comes to an endthe inherited x and y variables specify this location at the start of the scroll.
- duration specifies the amount of time needed for the upper-left corner to transition from (x, y) to (toX, toY).
- resize restarts a scroll operation whenever a scroll is in progressthe idea is to restart the scroll when the scene is resized, so you should bind this variable to javafx.scene.Scene's width and height variables, as shown in Listing 6.
- play() automatically starts the scroll operation.
Most importantly, ScrollableText introduces an almost private TranslateTransition constantit would be private if I relocated ScrollableText to its own source file. Because TranslateTransition is interested only in translateX and translateY offsets, I subtract x from toX and y from toY to obtain the ending translation offsets.
You might wonder why I sandwich the ScrollableText node between two Rectangle nodes. My reason: aesthetics. If you take away the Rectangle that appears after ScrollableText, you'll notice that the text moves over the other Rectangle's black border. By covering that part of the lower Rectangle with the upper Rectangle, you no longer see the text scrolling over the border.
Figure 6 shows the ScrollableText component in action.
Figure 6 Scrolling text horizontally from right to left.