Home > Articles > Programming > Java

Like this article? We recommend

Like this article? We recommend

Basic Transitions

The javafx.animation.transition package provides classes for performing six common basic transitions. You can use these classes to animate a node's opacity variable, move a node along a path, do nothing for awhile and then perform an action, animate a node's rotate variable, animate the node's scaleX and scaleY variables, or animate its translateX and translateY variables.

Fade

The FadeTransition class fades a node by transitioning its node variable's opacity member from a starting value to an ending value over the duration specified by its duration variable. The starting and ending values are specified via FadeTransition variables and node's current opacity value:

  • The starting value is specified by fromValue (of type Number) or (if not present) the node's current opacity value.
  • The ending value is specified by toValue (of type Number) or (if not present) the starting value plus the value of variable byValue (of type Number). If both toValue and byValue are specified, toValue takes precedence.

The fade transition is commonly employed by slideshow applications for transitioning between successive slides. For example, I demonstrate FadeTransition in the slideshow application in my article "Deploying a JavaFX Application."

For this article, I've chosen something simpler—an application that animates a single image's opacity from opaque to transparent and back again three times over a 12-second duration when you click the image. Listing 1 presents the application's Main.fx source file (excerpted from a NetBeans IDE 6.5.1 FadeTDemo project).

Listing 1—Main.fx (from a FadeTDemo project).

/*
 * Main.fx
 */

package fadetdemo;

import javafx.animation.transition.FadeTransition;

import javafx.scene.Scene;

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.input.MouseEvent;

import javafx.stage.Stage;

def image = Image
{
    url: "{__DIR__}res/flowers.jpg"
}

Stage
{
    title: "FadeTransition Demo"

    scene: Scene
    {
        width: image.width
        height: image.height

        var iv: ImageView
        content: iv = ImageView
        {
            image: image

            def fadeT = FadeTransition
            {
                node: bind iv
                duration: 2s
                fromValue: 1.0
                toValue: 0.0
                repeatCount: 6
                autoReverse: true
            }

            onMouseClicked: function (me: MouseEvent): Void
            {
                fadeT.play ()
            }
        }
    }
}

The application loads an image into a javafx.scene.image.Image object and makes the stage's scene just large enough to present the image in its entirety. A FadeTransition is created, and the javafx.scene.image.ImageView node that will present the image is bound to the transition's node variable. The other variables accomplish the following tasks:

  • duration specifies 2s (2000 milliseconds) as the length of a forward transition (fromValue to toValue) or a reverse transition (toValue to fromValue) cycle.
  • fromValue specifies 1.0 (opaque) as the starting opacity for a forward transition cycle and the ending opacity for a reverse transition cycle.
  • toValue specifies 0.0 (transparent) as the ending opacity for a forward transition cycle and the starting opacity for a reverse transition cycle.
  • repeatCount specifies 6 as the number of transition cycles—three forward and three reverse.
  • autoReverse specifies true to indicate that each transition cycle runs in reverse to the previous cycle.

The ImageView node's onMouseClicked handler starts the FadeTransition (unless it's already running) whenever the mouse is clicked over the displayed image. The click initiates three transitions: opaque-to-transparent (where the white background is fully visible) followed by transparent-to-opaque (where the image is completely visible). Figure 1 shows one instance of this sequence.

Figure 1 Fading out a garden of flowers. (Image courtesy of Lydia Jacobs at Public Domain Pictures.)

Path

The PathTransition class moves a node along a geometric path by transitioning its node variable's translateX and translateY members along its path variable's AnimationPath over the duration assigned to its duration variable. The node's rotate member is also regularly updated if OrientationType.ORTHOGONAL_TO_TANGENT is assigned to PathTransition's orientation variable.

PathTransition works with two supporting classes:

  • AnimationPath provides three functions:
    • public createFromPath(path: Path): AnimationPath
      public createFromPath(svgPath: SVGPath): AnimationPath
      public createFromShape(shape: Shape): AnimationPath
  • Each of these functions returns an AnimationPath for its applicable argument:
    • javafx.scene.shape.Path
      javafx.scene.shape.SVGPath
      javafx.scene.shape.Shape
  • OrientationType specifies the upright orientation of PathTransition's node along its path. Assigning constant NONE to PathTransition's orientation variable results in unchanged node rotation as the node moves along the path, whereas assigning constant ORTHOGONAL_TO_TANGENT to orientation results in the node continually being rotated so that it remains perpendicular to the path's tangent.

I've chosen to demonstrate these classes in a simulation of a car moving over a road. Listing 2 shows this simulation's source code.

Listing 2—Main.fx (from a PathTDemo project).

/*
 * Main.fx
 */

package pathtdemo;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;

import javafx.animation.transition.AnimationPath;
import javafx.animation.transition.OrientationType;
import javafx.animation.transition.PathTransition;

import javafx.scene.Group;
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.shape.ArcTo;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;

import javafx.stage.Stage;

def car: ImageView = ImageView
{
    var carim: Image
    image: carim = Image { url: "{__DIR__}res/car.gif" }
    x: bind -carim.width/2
    y: bind 300-carim.height/2
    rotate: 90
}

def path =
[
    MoveTo { x: 0 y: 300 }
    ArcTo { x: 100 y: 400 radiusX: 100 radiusY: 100 }
    LineTo { x: 300 y: 400 }
    ArcTo { x: 400 y: 300 radiusX: 100 radiusY: 100 }
    LineTo { x: 400 y: 100 }
    ArcTo { x: 300 y: 0 radiusX: 100 radiusY: 100 }
    LineTo { x: 100 y: 0 }
    ArcTo { x: 0 y: 100 radiusX: 100 radiusY: 100 }
    LineTo { x: 0 y: 300 }
    ClosePath {}
];

def road = Path
{
    stroke: Color.BLACK
    strokeWidth: 75
    elements: path
}

def divider = Path
{
    stroke: Color.WHITE
    strokeWidth: 4
    strokeDashArray: [ 10, 10 ]
    elements: path
}

def anim = PathTransition
{
    node: car
    path: AnimationPath.createFromPath (road)
    orientation: OrientationType.ORTHOGONAL_TO_TANGENT
    interpolate: Interpolator.LINEAR
    duration: 6s
    repeatCount: Timeline.INDEFINITE
}

Stage
{
    title: "PathTransition Demo"
    width: 510
    height: 535

    scene: Scene
    {
        fill: Color.DARKGREEN

        content: Group
        {
            content: [ road, divider, car ]

            translateX: 50
            translateY: 50

            onMouseClicked: function (me: MouseEvent): Void
            {
                if (anim.running and not anim.paused)
                    anim.pause ()
                else
                    anim.play ()
            }
        }
    }
}

Listing 2 first creates an ImageView node that's initialized to the image of a car being animated. This node is given an appropriate starting position on the road, which happens to be centered over the divider line. It's also rotated 90 degrees to display the car image in a downward vertical position rather than in its default horizontal and right-facing position.

Moving on, a path sequence is constructed to describe the road and the divider line—they both share the same path instructions; the only difference between the two is that the divider line has a narrower stroke width. The sequence describes a square with rounded corners, and having a (0,0) origin (upper-left corner of the stage). The final ClosePath {} literal is required to close the path properly.

The listing next creates the road and divider line nodes, breaking the divider line into visible and transparent segments of equal length by initializing its strokeDashArray variable. The divider line will appear in the exact middle of the road because the road is essentially just a "fat" divider line, and they both share the same coordinates.

The PathTransition object connects the car and road nodes by assigning the car node to this object's node variable, and by indirectly assigning the road shape node to the variable path via AnimationPath's createFromPath() function. Rounding out the initialization are instructions to keep the car node perpendicular to its path, and to specify an indefinite number of six-second road tours.

The scene is specified via the group of road, divider, and car nodes; with the road being displayed first, the divider being displayed over the road (by virtue of the divider's appearing after the road in Group's content sequence), and the car (which is last in this sequence) being displayed over the road and the divider. The Group's translation members position this scene so that it's centered in its window.

Additionally, Group contains a mouse handler for playing or pausing the animation. To animate the car, simply click the mouse anywhere over the road, divider, or car. Click the mouse again to pause the animation; a third click restarts the animation, and so on. Figure 2 reveals the car turning a corner as it travels around the road.

Figure 2 The center of the node being animated (the car) serves as the animation's anchor point—it follows the path most closely.

As an aside, you can change the car's color via the javafx.scene.effect.ColorAdjust class. After instantiating this class, you'll typically only need to assign a color value to the instance's hue variable (of type Number). For example, the following code fragment, excerpted from Listing 2 (with additional code shown in boldface), changes the car's color to orange by assigning 0.15 to hue:

def car = ImageView
{
    var carim: Image
    image: carim = Image { url: "{__DIR__}res/car.gif" }
    x: bind -carim.width/2
    y: bind 300-carim.height/2
    rotate: 90
    effect: javafx.scene.effect.ColorAdjust
    {
        hue: 0.15
    }
}

Pause

The PauseTransition class waits for its duration value to expire and then executes its action variable's function. Although you can forget about the function if you're only interested in the pause, this function comes in handy when you're building a button component and simulating the javax.swing.AbstractButton class's public void doClick() method (see Listing 3).

Listing 3—Main.fx (from a PauseTDemo project).

/*
 * Main.fx
 */

package pausetdemo;

import javafx.animation.transition.PauseTransition;

import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;

import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;

import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Paint;
import javafx.scene.paint.Stop;

import javafx.scene.shape.Rectangle;

import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;

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.BLACK },
        Stop { offset: 1.0 color: Color.BLUEVIOLET }
    ]
}

Stage
{
    title: "PauseTransition Demo"
    width: 300
    height: 200

    var scene: Scene
    scene: scene = Scene
    {
        fill: BACKGROUND_PAINT

        var button: Button;
        content: button = Button
        {
            x: bind (scene.width-100)/2
            y: bind (scene.height-40)/2
            width: 100
            height: 40
            arcWidth: 10
            arcHeight: 10

            text: "OK"
            font: Font
            {
                name: "Arial BOLD"
                size: 16
            }

            fill: Color.DARKBLUE
            fillRollover: Color.MEDIUMBLUE
            fillPressed: Color.LIGHTBLUE
            borderColor: Color.WHITE
            textColor: Color.WHITE

            action: function (): Void
            {
                button.text = if (button.text == "OK")
                                  "OKAY"
                              else
                                  "OK";

                button.fillRollover = if (button.fill == Color.DARKBLUE)
                                          Color.MEDIUMVIOLETRED
                                      else
                                          Color.MEDIUMBLUE;

                button.fillPressed = if (button.fill == Color.DARKBLUE)
                                         Color.PALEVIOLETRED
                                     else
                                         Color.LIGHTBLUE;

                button.fill = if (button.fill == Color.DARKBLUE)
                                  Color.BLUEVIOLET
                              else
                                  Color.DARKBLUE
            }
        }
    }
}

class Button extends CustomNode
{
    public var text: String;
    public var font: Font;

    public var x: Number;
    public var y: Number;
    public var width: Number;
    public var height: Number;
    public var arcWidth: Number;
    public var arcHeight: Number;

    public var fill: Paint on replace oldFill
    {
        if (curFill == oldFill) curFill = fill
    }

    public var fillPressed: Paint on replace oldFillPressed
    {
        if (curFill == oldFillPressed) curFill = fillPressed
    }

    public var fillRollover: Paint on replace oldFillRollover
    {
        if (curFill == oldFillRollover) curFill = fillRollover
    }

    public var borderColor: Paint;
    public var textColor: Paint;

    public var action: function (): Void;

    var curFill: Paint;

    public override function create (): Node
    {
        Group
        {
            var r: Rectangle on replace { if (r != null) r.requestFocus () }
            var t: Text
            content:
            [
                r = Rectangle
                {
                    x: bind x
                    y: bind y
                    width: bind width
                    height: bind height
                    arcWidth: bind arcWidth
                    arcHeight: bind arcHeight
                    fill: bind curFill
                    stroke: bind borderColor

                    onMouseEntered: function (me: MouseEvent): Void
                    {
                        curFill = fillRollover
                    }

                    onMouseExited: function (me: MouseEvent): Void
                    {
                        curFill = fill
                    }

                    onMousePressed: function (me: MouseEvent): Void
                    {
                        curFill = fillPressed
                    }

                    onMouseReleased: function (me: MouseEvent): Void
                    {
                        curFill = if (r.contains (me.x, me.y))
                                      fillRollover
                                  else
                                      fill
                    }

                    onMouseClicked: function (e: MouseEvent): Void
                    {
                        action ()
                    }

                    onKeyPressed: function (e: KeyEvent): Void
                    {
                        if (e.code != KeyCode.VK_SPACE)
                            return;

                        def pause = PauseTransition
                        {
                            duration: 68ms
                            action: function (): Void
                            {
                                curFill = fill;
                                action ()
                            }
                        }
                        curFill = fillPressed;
                        pause.play ()
                    }
                }
                t = Text
                {
                    content: bind text
                    font: bind font
                    textOrigin: TextOrigin.TOP
                    translateX: bind x+(r.layoutBounds.width-
                                        t.layoutBounds.width)/2
                    translateY: bind y+(r.layoutBounds.height-
                                        t.layoutBounds.height)/2
                    fill: bind textColor
                }
            ]
        }
    }
}

Listing 3 creates and demonstrates a highly configurable button component. The key part of this listing is the code within Button's onKeyPressed handler function. In response to a key being pressed (the single button node's rectangle component requests keyboard focus), this handler function accomplishes the following tasks:

  • Verifies that the spacebar key has been pressed. Pressing the spacebar is equivalent to clicking the mouse button over the button node.
  • Creates a PauseTransition. For consistency with doClick(), this transition's duration member is set to 68ms.
  • Sets the button's current-fill to the pressed-fill setting and plays the transition. When the transition completes, it resets the current fill to the default fill setting and invokes the button's action member.

Figure 3 reveals the button's state after the spacebar has been pressed once.

Figure 3 Both the button's text and fill colors change each time the spacebar is pressed or the mouse is clicked over the button.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020