Animazione al contrario

L'ultimo articolo correntemente pubblicato nella sezione Animation della pagina Learn di JavaFX, é intitolato Reverse Animation Easily Using the Rate Trick ed é stato scritto da Nancy Hildebrandt.

Lo scopo é quello di mostrarci come si possa usare la variabile rate della classe Timeline per ottenere molto facilmente animazioni al contrario.

Al solito, applicherò qualche piccola variazione al codice proposto nell'articolo, ad esempio uso bottoni standard, invece di crearli dalle forme geometriche di base, e cerco in genere di semplificare il più possibile il codice.

Si può vedere l'applicazione in esecuzione in questa pagina.

In pratica il campo rate della timeline agisce da moltiplicatore per la velocità del nodo legato. E quindi, assegnandogli un valore negativo otteniamo una inversione del moto.

La timeline

Dopo aver definito due costanti, startX e endX, che definiscono il perrcorso del nostro oggetto, e due variabili che tengono traccia della sua posizione corrente e della sua direzione di movimento.

Definiamo allora una timeline il cui rate é legato alla direzione del moto, su cui agiremo con i bottoni:

import javafx.animation.Interpolator;
import javafx.animation.Timeline;

def startX = 100.0;
def endX = 250.0;

var currX = startX;
var forward = true;

def timeline = Timeline { //Timeline for animation of circle
rate: bind (if(forward) 1 else -1)
keyFrames: at(3s) { currX => endX tween Interpolator.LINEAR }
};

Il bottone "go"

Il bottone "go" chiede di muovere l'oggetto in avanti. La direzione in avanti forward é perciò "vera" e si chiama il metodo play() per la timeline. Nel caso ci si trovi a fine corsa, si riporta l'oggetto all'inizio della sua traiettoria e si riparte.

import javafx.scene.control.Button;
...
def go = Button {
translateX: 80, translateY: 140, width: 80
text: "Go"
action: function() {
forward = true;
if(currX == endX)
timeline.playFromStart()
else
timeline.play();
}
}

Il bottone "back"

Il bottone "back" chiede di muovere l'oggetto indietro. Normalmente assegneremo a forward il valore false. Ma c'é una eccezione: quando siamo a inizio traiettoria. In questo caso non possiamo, come nel caso "go", portare l'oggetto dall'altra parte e dare corso al comando richiesto, dato che playFromStart() non permette questo comportamento, ci conviene invece interpretare il comando "back" come se fosse un "go".

Ma c'é un'altra piccola complicazione. Probabilmente a causa di un bug, quando si alternano inversioni di direzione può accadere che il termine del movimento via "back" non porti esattamente alla posizione iniziale, ma a una frazione di unità da essa.

Per questo motivo non si controlla che la posizione corrente sia uguale a quella iniziale ma che la loro differenza sia inferiore all'unità.

def back = Button {
translateX: 180, translateY: 140, width: 80
text: "Back"
action: function() {
forward = if(currX - startX > 1) true else false;
timeline.play();
}
}

Ci resta da definire l'oggetto che vogliamo muovere, un cerchio, e metterlo, insieme ai bottoni che ne regolano il movimento, sulla scena.

La cosa principale da notare nel codice che definisce il cerchio e che il suo centro é legato a currX, la variabile modificata dalla timeline.

Vediamo quindi qui a seguire il codice completo dello script:
package reverseanimation;

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.control.Button;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.effect.DropShadow;

def startX = 100.0;
def endX = 250.0;

var currX = startX; // current x for the circle
var forward = true; // current movement direction

def timeline = Timeline { //Timeline for animation of circle
rate: bind (if(forward) 1 else -1)
keyFrames: at(3s) { currX => endX tween Interpolator.LINEAR }
};

def go = Button {
translateX: 80, translateY: 140, width: 80
text: "Go"
action: function() {
forward = true;
if(currX == endX)
timeline.playFromStart()
else
timeline.play();
}
}

def back = Button {
translateX: 180, translateY: 140, width: 80
text: "Back"
action: function() {
forward = if(currX - startX > 1) true else false;
timeline.play();
}
}

def mover = Circle {
centerX: bind currX, centerY: 80, radius: 20
fill: Color.GREEN
effect: DropShadow { offsetX: 3, offsetY: 3 }
}

Stage {
title: "Simple Animation"
width: 350, height: 220
scene: Scene {
content: [ mover, go, back ]
}
}

Nessun commento:

Posta un commento