October 1st, 2009

Trident 1.1 – interpolating properties

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.