Oggetti animati

Lezione numero sette del corso introduttivo SDN (Sun Developer Network) sulla costruzione di applicazioni GUI con JavaFX.

Le librerie JavaFX forniscono un supporto integrato alla creazione di animazioni. In questa lezione si mostra come costruire un oggetto UI e animarlo usando l'interpolazione lineare. E' un esempio piuttosto completo, usando la sintassi dichiarativa, il data binding, grafica e funzionalità specifiche dei nodi.

Iniziamo creando un applicazione per il common profile, aggiungeremo poi funzionalità specifiche dell'ambiente desktop.

Common Profile

L'applicazione che creiamo mostrerà una nuvoletta che si muove su un fondale stabilito, rimbalzando sui bordi.

La finestra dell'applicazione con un immagine sullo sfondo

Come al solito, partiamo definendo stage e scena. La nostra scena ha come colore di sfondo il bianco e come contenuto iniziale l'immagine di sfondo che
abbiamo scaricato qua dal sito Sun e messo nella nostra directory locale del progetto:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

Stage {
title: "Cloud"
scene: Scene {
fill: Color.WHITE
content: [
ImageView {
image: Image { url: "{__DIR__}sun.jpg" }
} // imageView
] // content
} // scene
} // stage

Non avendo specificato la dimensione della nostra scena, questa si adatterà al contenuto, e sarà quindi determinata dalla dimensione dell'immagine.

La nuvola

Per disegnare la nuvola usiamo la classe Path, che ci permette di creare una forma custom, e la coloriamo dandole sfumature d'azzurro, usando la classe LinearGrandient.

import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
...
// nel content della scene, dopo l'immagine di sfondo:
Path {
stroke: Color.DODGERBLUE
fill: LinearGradient {
proportional: false, startX:60, startY:10, endX:10, endY:80
stops: [
Stop { offset: 0.0, color: Color.DODGERBLUE },
Stop { offset: 0.5, color: Color.LIGHTSKYBLUE },
Stop { offset: 1.0, color: Color.WHITE }
]
}
elements: [
MoveTo {x: 15, y: 15 },
ArcTo {x: 50, y: 10, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 70, y: 20, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 50, y: 60, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 20, y: 50, radiusX: 10, radiusY: 5, sweepFlag: true},
ArcTo {x: 15, y: 15, radiusX: 10, radiusY: 10, sweepFlag: true},
]
} // path

La nostra nuvola ha un bordo (stroke) dodgerblue, é colorata in tre colori, da dodgerblue a bianco, a partire da (60, 10) a (10, 80).
Il bordo é costruito a partire dal punto (15,15) (la classe MoveTo crea il punto iniziale di un Path) per proseguire a (50, 10) tirando un arco che va verso l'esterno della superficie determinata (sweepFlag é true, se fosse false ci sarebbe come un "morso" nella nuvola)

Movimento orizzontale

Diamo movimento alla nuvola, per cominciare orizzontalmente. Per far ciò assegnamo una traslazione al nostro oggetto Path che definisce la nuvola fatta così:

// nel content della scena
...
Path {
translateX: bind x, translateY: 100
...
}

Dunque, la traslazione sull'asse y é fissata a 100, mentre sull'asse x é legata alla variabile x, che dovremmo definire prima.

In JavaFX viene supportato il concetto di animazione key frame. Lo stato di animazione di un oggetto viene definito dichiarando i key frame della scena in certi momenti. Specificato questo, il sistema può eseguire automaticamente l'animazione.

Vogliamo muovere la nuvola dall'ascissa 0 a 158. Il punto finale é determinato dalla dimensione del fondale (largo 241 pixel) e della nostra nuvola (che ha dimensioni 83x64), e 241 - 83 = 158.

Creiamo dunque un oggetto Timeline, che definisce al suo interno due KeyFrame, che saranno legati fra loro mediante interpolazione lineare.
Definiamo inoltre la variabile x, che verrà utilizzata nell'oggetto Path per permettere la traslazione della nostra nuvola.

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

var x: Number;

Timeline {
keyFrames: [
at (0s) {x => 0.0},
at (4s) {x => 158.0 tween Interpolator.LINEAR}
]

repeatCount: Timeline.INDEFINITE
autoReverse: true
}.play();

L'oggetto Timeline che abbiamo costruito definisce la nostra animazione. Abbiamo dichiarato nella sua componente keyFrames due KeyFrame, usando la sintassi specifica JavaFX per questo tipo di oggetti, il primo al tempo 0s (dove s sta per secondi) associa alla variabile x il valore 0 (si noti l'operatore => che indica la chiamata ad un costruttore per una lista di valori), il secondo al tempo 4s associa a x il valore 158. Inoltre viene specificato mediante l'operatore tween che tipo di interpolazione andrà fatta tra i due KeyFrame definiti, in questo caso, una interpolazione lineare.

Inoltre specifichiamo che l'animazione va ripetuta indefinitivamente e che si applica l'autoreverse.

Nota che sull'oggetto costruito é applicato il metodo play(), per far partire immediatamente l'animazione.

Ci sono diversi tipi di interpolazione, a seconda dell'effetto che di vuole ottenere, ad esempio Interpolator.EASEBOTH, causa un simpatico rallentamento dell'azione avvicinandosi al KeyFrame.

Movimento verticale

E' semplice ora aggiungere movimento verticale, dovremmo semplicemente modificare il codice aggiungendo una variabile numerica (y) che sarà gestita da un'altro oggetto Timeline e che sarà legata alla proprietà translateY dell'oggetto Path:

var y: Number;
...
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {y => 0.0},
at (7s) {y => 258.0 tween Interpolator.LINEAR},
]
}.play();
...
// nel content della scena
Path {
...
translateY: bind y
...
}

A seguire, il codice completo per lo script JavaFX realizzato fino a questo momento:

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.paint.LinearGradient; // cloud
import javafx.scene.paint.Stop; // cloud
import javafx.scene.shape.ArcTo; // cloud
import javafx.scene.shape.MoveTo; // cloud
import javafx.scene.shape.Path; // cloud

import javafx.animation.Interpolator; // key frame animation
import javafx.animation.Timeline; // key frame animation

// key frame animation
var x: Number;
var y: Number;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true

keyFrames: [
at (0s) {x => 0.0},
at (4s) {x => 158.0 tween Interpolator.LINEAR }
]
}.play();

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {y => 0.0},
at (7s) {y => 258.0 tween Interpolator.LINEAR},
]
}.play();
// key frame animation done

Stage {
title: "Cloud"
scene: Scene {
fill: Color.WHITE
content: [
ImageView {
image: Image { url: "{__DIR__}sun.jpg" }
} // imageView

Path {
// animation
translateX: bind x, translateY: bind y

stroke: Color.DODGERBLUE
fill: LinearGradient {
proportional: false, startX:60, startY:10, endX:10, endY:80
stops: [
Stop { offset: 0.0, color: Color.DODGERBLUE },
Stop { offset: 0.5, color: Color.LIGHTSKYBLUE },
Stop { offset: 1.0, color: Color.WHITE }
]
}
elements: [
MoveTo {x: 15, y: 15 },
ArcTo {x: 50, y: 10, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 70, y: 20, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 50, y: 60, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 20, y: 50, radiusX: 10, radiusY: 5, sweepFlag: true},
ArcTo {x: 15, y: 15, radiusX: 10, radiusY: 10, sweepFlag: true},
]
} // path
] // content
} // scene
} // stage

Desktop Profile

Approfittiamo dei migliori effetti messi a disposizione dal profilo desktop per rendere la nostra applicazione un poco più piacevole.

import javafx.scene.effect.Lighting;
import javafx.scene.effect.light.DistantLight;
...
// nel content della scena
Path {
// desktop enhancement
effect: Lighting{ light: DistantLight{azimuth: 90} }
...
}

L'effetto di una fonte luminosa distante dà una prospettiva alla nostra nuvola che risulta così più realistica.

Nessun commento:

Posta un commento