quinta-feira, 28 de junho de 2012

JavaFX Animation and Media Synchronization

I've been experiencing JavaFX 2.1 lately and I've created pieces of code to work some concepts out. One of those is how to synchronize animation and media (audio, more precisely). My fisrt shot was to create instances of a Timeline, a MediaPlayer and then invoke the play() method of both instances sequentially. That solution seems to work rather nice since you have just one anination and one audio to play.  Although, what if you have a sequence of anination and audio to play?

An instance of SequentialTransition fits perfectly if you want to play a row of aninations, one right after the other. And an instance of ParallelTransition plays animations at the same time, or at least concurrently. To get things more interesting, you can create instances of ParallelTransition and add them to an instance of SequentialTransition, or the other way around. Both types accepts instances of any type that extends Animation, which is an abstract class of JavaFX. Timeline, SequentialTransition and ParallelTransition themselves, and other transitions all extend Animation, but MediaPlayer don't! So I had to figure out how to synchronize animation and media simultaneously and then play them in a sequence of animations and medias.

Animation

First, let's create a simple animation using Timeline that changes colors of a certain shape. The execution  of such animation takes 5 seconds and color changes smoothly. The piece of code below just creates a rectangle and other instances to represent each color using the RGB color system (Red, Green and Blue). An anonimous class is also created and instanciated which implements the ChangeListener interface; here is where the color of the shape changed. Then, the listener instance is added to each color variable instance.

final Rectangle anyShape = new Rectangle(250, 200);
               
final DoubleProperty redColor = new SimpleDoubleProperty();
final DoubleProperty greenColor = new SimpleDoubleProperty();
final DoubleProperty blueColor = new SimpleDoubleProperty();
        
ChangeListener colorListener = new ChangeListener() {

    @Override
    public void changed(ObservableValue arg0, Object arg1, Object arg2) {
        anyShape.setFill(Color.color(redColor.doubleValue(), greenColor.doubleValue(), blueColor.doubleValue()));  
    }
};
        
redColor.addListener(colorListener);
greenColor.addListener(colorListener);
blueColor.addListener(colorListener);

Now, the color variable instances have to change and that's the animation we're talking about. Here, the Timeline comes into play.

Two colors is picked up to make the transition. Then, an instance of Timeline is created and a sequence of KeyFrame instances is added to it. Pay attention to the pairs of KeyFrame instances: each pair handle a color within a period of time, i.e., Duration instances. Besides Duration instance, the KeyFrame contructor demands a KeyValue instance as well. That one takes in a color variable instance and a start color. So, one KeyFrame instance defines that a variable is set to a certain value at a certain period of time and along another KeyFrame instance, such value is interpolated to another one until another period of time.

Color startColor = Color.BLUEVIOLET;
Color endColor = Color.YELLOWGREEN;

Timeline animation = new Timeline();
animation.getKeyFrames().addAll(new KeyFrame(new Duration(0.0), new KeyValue(redColor, startColor.getRed())),
                                new KeyFrame(new Duration(5000.0), new KeyValue(redColor, endColor.getRed())),
                                new KeyFrame(new Duration(0.0), new KeyValue(greenColor, startColor.getGreen())),
                                new KeyFrame(new Duration(5000.0), new KeyValue(greenColor, endColor.getGreen())),
                                new KeyFrame(new Duration(0.0), new KeyValue(blueColor, startColor.getBlue())),
                                new KeyFrame(new Duration(5000.0), new KeyValue(blueColor, endColor.getBlue())));

animation.play();

Audio

Time to load an audio file and make it play too. The piece of code below does so. The audio file here is inside the JAR file. First, the URL to the audio file is retrieved and then the toExternalForm() method is invoked to get the real path, otherwise, it will not work when the application is executed by the JAR file itself (it will work only within the development IDE).

URL soundURL = this.getClass().getResource("/resource/audio.wav");
final MediaPlayer mediaPlayer = new MediaPlayer(new Media(soundURL.toExternalForm()));
mediaPlayer.play();

Synchronizing Animation and Audio

Since ParallelTransition does not accept a MediaPlayer instance (which makes sense), animation and media synchronization has to have its own way. Actually, Timeline works it out by itself, along KeyFrame and EventHandler instances. The KeyFrame constructor, besides a Duration instance, also accepts an instance of EventHandler. So, at start time, an EventHandler instance must be set, as well as at stop time. The piece of code below shows how to create instances of EventHandler as anonymous classes. Next, KeyFrame instances are created which take in the EventHandler instances at start and stop durations and are right away added to the animation, i.e., the Timeline instance.

EventHandler<ActionEvent> startAudio = new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent arg0) {
        mediaPlayer.play();
    }
};

EventHandler<ActionEvent> stopAudio = new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent arg0) {
        mediaPlayer.stop();
    }
};

animation.getKeyFrames().add(new KeyFrame(new Duration(0.0), startAudio));
animation.getKeyFrames().add(new KeyFrame(new Duration(5000.0), stopAudio));

animation.play();

Conclusion

The main intention of this post is to show how to synchronize animation and media in JavaFX 2.1, using Timeline, KeyFrame and EventHandler classes. Alongside, a simple animation and the load of an audio file are also described and used as basis to the main subject.

Nenhum comentário:

Postar um comentário