Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about setting a custom pulse source to drive the Trident timelines.
Timeline pulse source
By default, Trident timelines are driven by a dedicated thread that wakes up every 40ms and updates all the timelines. When the CPU is not heavily used this results in 25 frames-per-second refresh rate for Trident-driven UI animations – consistent with the frame rate of theatrical films and non-interlaced PAL television standard.
Applications that require custom pulse behavior – higher frame rate, lower frame rate or dynamic frame rate – should use the TridentConfig.setPulseSource(PulseSource)
API. The PulseSource
interface is:
public interface PulseSource {
public void waitUntilNextPulse();
}
The implementation of waitUntilNextPulse()
is expected to be a blocking call that returns on the next target pulse.
Sample usage
The following class installs a custom pulse source that fires timeline pulses every 100 milliseconds:
public class CustomPulseSource {
private float value;
public void setValue(float newValue) {
SimpleDateFormat sdf = new SimpleDateFormat("mm:SSS");
System.out.println(sdf.format(new Date()) + " : " + this.value + " - "
+ newValue);
this.value = newValue;
}
public static void main(String[] args) {
TridentConfig.getInstance().setPulseSource(
new TridentConfig.PulseSource() {
@Override
public void waitUntilNextPulse() {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
});
CustomPulseSource helloWorld = new CustomPulseSource();
Timeline timeline = new Timeline(helloWorld);
timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
timeline.play();
try {
Thread.sleep(3000);
} catch (Exception exc) {
}
}
}
This example uses the blocking Thread.sleep() API. Sample output of running this class is:
40:362 : 0.0 - 0.0
40:362 : 0.0 - 0.0
40:456 : 0.0 - 0.2
40:550 : 0.2 - 0.746
40:659 : 0.746 - 0.946
40:753 : 0.946 - 1.0
40:753 : 1.0 - 1.0
Discarding the first and last lines – that correspond to the start and the end of the timeline – the custom pulse source is indeed firing on average every 100 ms as expected. Applications that have a higher-precision timer implementation – perhaps using native APIs – can provide the matching PulseSource
implementation to achieve a higher pulse rate.
Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about extending the core functionality of Trident to address custom needs of the specific applications.
Core functionality
The core functionality of the Trident library can be extended to address custom needs of the specific applications. Out of the box Trident supports:
- The timeline pulses being fired by a dedicated thread that wakes up every 40ms and updates all the timelines
- Interpolating float and integer fields of any Java object that provides the matching public setter methods.
- Swing and SWT UI toolkits, respecting the threading rules and providing interpolators for the custom graphic classes
Interested applications can use Trident plugins and Trident configuration APIs to:
- Provide a custom pulse source to drive the timeline updates
- Provide additional property interpolators for custom application classes
- Support additional Java-based UI toolkits
Configuration APIs
The org.pushingpixels.trident.TridentConfig
class contains the published configuration APIs. The TridentConfig.getInstance()
API retrieves an instance of this class which can then be used for:
Plugins
A Trident plugin is specified by the META-INF/trident-plugin.properties
file that should be placed in the runtime classpath. Note that you can have multiple plugins in the same runtime environment – if each one is coming from a different classpath jar, for example.
The format of trident-plugin.properties
is simple. Each line in this file should be of the following format:
Key=FullyQualifiedClassName
There supported keys are:
The core Trident library contains a plugin that supports Swing and SWT UI toolkits, as well as property interpolators for a few core classes. The plugin descriptor is META-INF/trident-plugin.properties
in the trident.jar
UIToolkitHandler=org.pushingpixels.trident.swing.SwingToolkitHandler
PropertyInterpolatorSource=org.pushingpixels.trident.swing.AWTPropertyInterpolators
UIToolkitHandler=org.pushingpixels.trident.swt.SWTToolkitHandler
PropertyInterpolatorSource=org.pushingpixels.trident.swt.SWTPropertyInterpolators
PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about the most basic capability of Trident – interpolating properties of Java objects. While the first part is the same as for version 1.0, the subsequent sections describe new functionality in version 1.1.
Interpolating field values
A timeline allows changing field values of the associated object. For example, in a fade-in animation the timeline will interpolate the value of alpha
field from 0.0
to 1.0
. There are two steps involved in setting up such timeline.
The first step is to create a Timeline
instance passing the main object. The timeline is then configured to interpolate one or more fields of this main object. Let’s see a simple example:
public class HelloWorld {
private float value;
public void setValue(float newValue) {
System.out.println(this.value + " -> " + newValue);
this.value = newValue;
}
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorld();
Timeline timeline = new Timeline(helloWorld);
timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
timeline.play();
try {
Thread.sleep(3000);
} catch (Exception exc) {
}
}
}
Here, the timeline has the associated HelloWorld
instance; this timeline is instructed to interpolate the value
field of that instance from 0.0
to 1.0
over the duration of the timeline. There is an important assumption that the application code must honor. Each field added with the addPropertyToInterpolate
must have the matching public setter.
A timeline can interpolate multiple fields. In the following example the timeline will change values of three fields at each timeline pulse:
Timeline timeline = new Timeline(circle);
timeline.addPropertyToInterpolate("x", initX, finalX);
timeline.addPropertyToInterpolate("y", initY, finalY);
timeline.addPropertyToInterpolate("opacity", 1.0f, 0.0f);
Configuring interpolation properties
The examples shown above interpolate the specified field from given start value to given end value. Application code that requires finer control over the field interpolation will use the following Timeline
APIs:
- The static
Timeline.property(String)
method. This returns a TimelinePropertyBuilder
object that is used to configure the different aspects of field interpolation – see below.
- Once the
TimelinePropertyBuilder
has been fully configured, pass it to the Timeline.addPropertyToInterpolateTo(TimelinePropertyBuilder)
API.
Here is a code snippet that illustrates property builders in action:
Timeline pulseCenters = new Timeline();
pulseCenters.addPropertyToInterpolate(
Timeline. property("opacity").on(this.center1).from(0.0f).to(1.0f));
pulseCenters.addPropertyToInterpolate(
Timeline. property("opacity").on(this.center2).from(0.0f).to(1.0f));
pulseCenters.addPropertyToInterpolate(
Timeline. property("opacity").on(this.center3).from(0.0f).to(1.0f));
pulseCenters.setDuration(750);
pulseCenters.setEase(new Spline(0.9f));
pulseCenters.playLoop(RepeatBehavior.REVERSE);
The first line creates a new timeline not associated with any object. The next lines use three property builders to interpolate the opacity
field on three separate objects (center1
, center2
, center3
) from 0.0
to 1.0
. Finally, we configure the timeline duration and ease, and play it in a reverse loop.
The following APIs are available on the TimelinePropertyBuilder
class:
-
from(startValue)
and to(endValue)
specify the start and end field values for the interpolation.
-
fromCurrent()
specifies that the field will be interpolated from its current value.
-
on(object)
passes the object whose field will be interpolated. The example above uses this API to interpolate fields of three different objects in one timeline.
-
goingThrough(key frames)
specifies the key frames to be used for multi-value interpolation sequence.
-
interpolatedWith(property interpolator)
specifies the property interpolator (see below) for field types not supported by the core library.
-
setWith(property setter)
specifies the property setter (see below) to be called on every timeline pulse.
Here is another example of using the TimelinePropertyBuilder
to interpolate the specific field from its current value to the set end value:
this.scrollTimeline = new Timeline(this);
this.scrollTimeline.addPropertyToInterpolate(
Timeline. property("leadingPosition").
fromCurrent().to(this.targetLeadingPosition));
this.scrollTimeline.setDuration(250);
Custom property interpolators
Trident supports interpolation of primitive values – such as integers, floats and point / color / rectangle classes of supported UI toolkits. Application code that needs to interpolate fields of these types does not need to explicitly state how the field value is interpolated between the start / current and end value. In order to use a custom property interpolator, configure your TimelinePropertyBuilder
with the call to interpolatedWith(PropertyInterpolator)
API. The org.pushingpixels.trident.interpolator.PropertyInterpolator
interface is:
public interface PropertyInterpolator {
public Class getBasePropertyClass();
public T interpolate(T from, T to, float timelinePosition);
}
The interpolate
method is used at each timeline pulse to compute the interpolated value. Note that if the TimelinePropertyBuilder
is not configured with a custom property setter (see below), the application is responsible to make sure that the object containing the interpolated field (either the main timeline object, or the one passed to TimelinePropertyBuilder.on()
API) has a public setter accepting the interpolated value returned by the interpolate
implementation of this property interpolator.
The PropertyInterpolator.getBasePropertyClass
is not used when the application code explicitly sets a property interpolator on the timeline property builder – and it is safe to return any value (including null
) from it. This method is used only during dynamic lookup of custom property interpolators.
Custom property setters
The default mechanism to update the interpolated field is to use reflection to look up and call the matching published setter. Application code that does not wish to expose these setters should use the TimelinePropertyBuilder.setWith(PropertySetter)
API. The org.pushingpixels.trident.TimelinePropertyBuilder.PropertySetter
interface is:
public static interface PropertySetter {
public void set(Object obj, String fieldName, T value);
}
If the timeline property builder is configured with a custom property setter, this setter will be called at every timeline pulse passing the object, the name of the field and the new value to set on the field. Here is a sample usage of this API:
public class CustomSetter {
private float value;
public static void main(String[] args) {
final CustomSetter helloWorld = new CustomSetter();
Timeline timeline = new Timeline(helloWorld);
PropertySetter propertySetter = new PropertySetter() {
@Override
public void set(Object obj, String fieldName, Float value) {
SimpleDateFormat sdf = new SimpleDateFormat("mm:SSS");
float oldValue = helloWorld.value;
System.out.println(sdf.format(new Date()) + " : " + oldValue
+ " - " + value);
helloWorld.value = value;
}
};
timeline.addPropertyToInterpolate(Timeline. property("value")
.from(0.0f).to(1.0f).setWith(propertySetter));
timeline.play();
try {
Thread.sleep(3000);
} catch (Exception exc) {
}
}
}
Here, the CustomSetter
class does not wish to expose the value
field via a public setter. Instead, we use custom property setter to be called on every timeline pulse. Note that while this sample code does update the matching object field, it is not a strict requirement. Your custom property setter can do anything it wants in the set
implementation – update a key-value map, update multiple fields or even ignore some of the values altogether.
Deprecated APIs
Version 1.1 deprecates most of the addPropertyToInterpolate
and all of the addPropertyToInterpolateTo
APIs found in version 1.0. While it is still safe to call them, the deprecated APIs will be removed in the next major release. Application code that is using the deprecated API should be migrated to use the new addPropertyToInterpolate(TimelinePropertyBuilder)
API as described in this entry.