Home > Articles > Programming > Java

  • Print
  • + Share This
From the author of

From the author of

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 4—Main.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 5—Main.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 6—Main.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 end—the 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 progress—the 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 constant—it 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.

  • + Share This
  • 🔖 Save To Your Account