New in Trident 1.2

March 8th, 2010

The Trident animation library was born in February 2009 out of the internal animation layer used in Substance look-and-feel over the last three years. A year after, it is nearing its third official release which focuses mainly on stabilizing the API and ironing out the existing bugs.

The major milestone for this release is moving Substance 6.0 to use Trident – along with validating the library performance and flexibility to support a wide variety of UI animations. Trident 1.2 has also added a few new APIs to address a few common application requirements.

Custom property accessors

The default mechanism to retrieve and update the interpolated field is to use reflection to look up and call the matching published getter and setter. Application code that does not wish to expose these methods should use the following APIs:

  • TimelinePropertyBuilder.getWith(PropertyGetter)new in 1.2
  • TimelinePropertyBuilder.setWith(PropertySetter)
  • TimelinePropertyBuilder.accessWith(PropertyAccessor)new in 1.2

Where the relevant interfaces are defined in the TimelinePropertyBuilder class as follows:

If the timeline property builder is configured with a custom property setter / accessor, the set() will be called at every timeline pulse passing the object, the name of the field and the new value to set on the field. If the timeline property builder is configured with a custom property getter / accessor, the get() will be called when the current value of the field is needed. For example, when the builder is configured with fromCurrent(), the get() will be called to get the current field value when the timeline starts playing.

The following sample shows usage of a custom property setter:

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("ss.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, a custom property setter is provided 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.

The following sample shows usage of a custom property accessor backed up by a key-value map:

public class CustomAccessor {
   private Map values = new HashMap();

   public static void main(String[] args) {
      final CustomAccessor helloWorld = new CustomAccessor();
      Timeline timeline = new Timeline(helloWorld);

      PropertyAccessor propertyAccessor = new PropertyAccessor() {
         @Override
         public Float get(Object obj, String fieldName) {
            return helloWorld.values.get("value");
         }

         @Override
         public void set(Object obj, String fieldName, Float value) {
            SimpleDateFormat sdf = new SimpleDateFormat("ss.SSS");
            float oldValue = helloWorld.values.get("value");
            System.out.println(sdf.format(new Date()) + " : " + oldValue
                  + " -> " + value);
            helloWorld.values.put("value", value);
         }
      };
      helloWorld.values.put("value", 50f);

      timeline.addPropertyToInterpolate(Timeline. property("value")
            .fromCurrent().to(100.0f).accessWith(propertyAccessor));
      timeline.setDuration(300);
      timeline.play();

      try {
         Thread.sleep(1000);
      } catch (Exception exc) {
      }
   }
}

Note that both examples assume that the setter / accessor is used only for one specific field (“value”). You can use the same getter / setter / accessor implementation to access multiple fields – using the fieldName parameter passed to the get() and set() methods.

Stopping a timeline

In some cases you will want to stop a running timeline. There are three different APIs that you can use, each with its own semantics:

  • Timeline.abort(). The timeline transitions to the idle state. No application callbacks or field interpolations are done.
  • Timeline.end()new in 1.2. The timeline transitions to the done state, with the timeline position set to 0.0 or 1.0 – based on the direction of the timeline. After application callbacks and field interpolations are done on the done state, the timeline transitions to the idle state. Application callbacks and field interpolations are done on this state as well.
  • Timeline.cancel(). The timeline transitions to the cancelled state, preserving its current timeline position. After application callbacks and field interpolations are done on the cancelled state, the timeline transitions to the idle state. Application callbacks and field interpolations are done on this state as well.

In addition, there is a method to indicate that a looping timeline should stop once it reaches the end of the loop. For example, suppose that you have a pulsating animation of system tray icon to indicate unread messages. Once the message is read, this animation is canceled in the application code. However, immediate cancellation of the pulsating animation may result in jarring visuals, especially if it is done at the “peak” of the pulsation cycle. Calling Timeline.cancelAtCycleBreak() method will indicate that the looping animation should stop once it reaches the end of the loop.