A scientific fish story, published in 1901, reports that pike fish living in a tank were separated from minnows by a glass plate. After a while "a strike at the minnow had come to mean a bump on the nose... Instead of imputing their inability to the glass partition, they considered it some new and remarkable property of the minnows themselves." Consequently the pike stopped trying to strike the minnows, even after the partition was removed.
I first heard that story when a speaker was illustrating the idea that we often get used to "the way things are", and consequently quit pursuing our passions and dreams. But this isn't a motivational blog, so I'll stop the inspirational stuff right here and tie the fish story in with the subject of today's post :-)
If you've been developing in JavaFX for a while, you may have gotten used to the idea that dynamically changing values in Timeline key frames don't work as expected. I was in the same boat (tank?), and instead used the action event handler of a KeyFrame when this kind of dynamic capability was required. I was pleased to find out recently that this is no longer the case, by virtue of JIRA issue RT-2985 being addressed.
As shown in the screenshot below, I've modified the Metronome example to demonstrate this capability:
Taking a look at the code below, notice that clicking anywhere in the scene causes the values in the second KeyFrame of the Timeline to be assigned the coordinates of the mouse click. The net effect is that one end of the blue line (which is bound to the variables that are interpolated by the timeline) move to where you clicked the mouse. Here's the code listing for this example:
/*
* MetronomeDynVals.fx - A simple example of animation using a Timeline
* with dynamic KeyFrame time and values.
*
* Developed 2009 by James L. Weaver jim.weaver [at] javafxpert.com
* as a JavaFX Script SDK 1.2 example
*/
package projavafx.metronome1.ui;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
var durMs:Number = 1000;
var xVal:Number = 100;
var yVal:Number = 50;
var endXVal:Number = 300;
var endYVal:Number = 50;
var anim = Timeline {
keyFrames: [
KeyFrame {
time: 0ms
values: [
xVal => 100,
yVal => 50
]
},
KeyFrame {
time: bind 2000ms - (durMs * 1ms)
values: [
xVal => endXVal tween Interpolator.LINEAR,
yVal => endYVal tween Interpolator.LINEAR
]
},
KeyFrame {
time: bind (2000ms - (durMs * 1ms))*2
values: [
xVal => 100,
yVal => 50
]
}
]
repeatCount: Timeline.INDEFINITE
};
Stage {
title: "Metronome Dynamic Values"
visible: true
scene: Scene {
width: 400
height: 500
content: [
Rectangle {
width: 400
height: 500
fill: Color.TRANSPARENT
onMousePressed: function(me:MouseEvent):Void {
endXVal = me.x;
endYVal = me.y;
}
},
Line {
startX: bind xVal
startY: bind yVal
endX: 200
endY: 400
strokeWidth: 4
stroke: Color.BLUE
},
HBox {
layoutX: 60
layoutY: 420
spacing: 10
blocksMouse: true
content: [
Button {
text: "Start"
disable: bind anim.running
action: function():Void {
anim.playFromStart();
}
},
Button {
text: "Stop"
disable: bind not anim.running
action: function():Void {
anim.stop();
}
},
Slider {
min: 100
max: 2000
width: 500
value: bind durMs with inverse
},
]
}
]
}
}
Dynamically Altering the time Variable of a KeyFrame
In addition to dynamically changing the values of a KeyFrame, you can also alter the value of its time variable. To demonstrate this, the value of the Slider is bi-directionally bound to a variable from which the time value of the second KeyFrame is calculated. Consequently, moving the slider changes the speed of the animation.
By the way, I didn't supply a Web Start link, because I'd like to encourage you to compile and run the application. If you'd like a Web Start link, please leave a comment.
Regards,
Jim Weaver
P.S. Thanks to Eric M. Smith (who *does* have a motivational blog) for adding a third KeyFrame to this example (see his comment to this post). This addition makes the behavior more metronome-like, and if you click in the lower-right area the pendulum exhibits some John Travolta/Saturday Night Fever dance-action :-)
"I noticed that after I moved the slider fort and back, which changed the value of durMs, the action block was not executed regularly as expected."
Vichet,
I filed a related issue in JIRA (see RT-5002) previously, and have just now added the info that you supplied to it.
Thanks!
Jim Weaver
Posted by: Jim Weaver | June 26, 2009 at 08:59 AM
I found a strange behavior of the the Action block of the Keyframe. My experiment was putting an Action block in one of the Keyframe, for example:
KeyFrame {
time: 0ms
values: [
xVal => 100,
yVal => 50
]
action: function() {
count = count + 1;
}
},
I noticed that after I moved the slider fort and back, which changed the value of durMs, the action block was not executed regulary as expected.
I use Netbean 6.5.1 and JavaFX 1.2.
So may someone who has more experience with JavaFX help to proof whether this is really a bug to be reported.
Thanks,
Vichet
Posted by: Vichet Pornsinsiriruk | June 25, 2009 at 02:45 PM
I think this finally explains why I couldn't get my animations working properly under 1.0. Thanks.
Also, I truly appreciate your not putting the JWS link in your post. It's not until we actually do something with the code (instead of just running it) that we learn to do things in the language. Like adding a third KeyFrame (below) so the metronome bounces back and forth like a real metronome instead of starting back at (x,y) => (100,50) each time.
KeyFrame {
time: bind (2000ms - (durMs * 1ms))*2
values: [
xVal => 100,
yVal => 50
]
}
Great Job and Thank you.
Posted by: Eric M. Smith | June 22, 2009 at 12:44 PM