Nelle animazioni Silverlight può essere utile, in alcune occasioni, introdurre un comportamento di trascinamento di un oggetto nello stage. Abbiamo già visto come implemetare un drag&drop con Silverlight 1.0, ma vediamo come farlo con Silverlight 2 ed aggiungendo il rilascio con inerzia.
Per esaminare questa caratteristica, possiamo simulare il lancio di un oggetto, che continua a muoversi all'interno dello stage fino all'esaurimento della sua forza di inerzia.
Abbiamo preso spunto dal codice di questo articolo che mostra come trasformare una animazione flash in Silverlight.
Lo stage
Iniziamo la costruzione dello stage. Abbiamo uno UserControl
che contiene un Canvas
al quale, in seguito, aggiungeremo alcuni oggetti.
Canvas vuoto
<UserControl x:Class="HtmlInerziaOggetti.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300" Loaded="user_Loader"> <Canvas x:Name="LayoutRoot" Background="#570103"> </Canvas> </UserControl>
Abbiamo definito nome (LayoutRoot
), dimesioni (600x400
) e il colore di sfondo (#570103
) della tela. Ma cosa più importante è che abbiamo assegnato un gestore per l'evento Loaded
. Il metodo user_Loader()
sarà eseguito al termine del caricamento dell'animazione e ci servirà per effettuare alcune impostazioni sugli altri oggetti, come vedremo tra breve.
Continuiamo a lavorare sullo stage e inseriamo un "semplice" quadrato colorato, potremmo definire uno Square
nel Canvas
principale, ma scegliamo di creare un nuovo UserControl
. Questo ci permetterà di moltiplicare gli oggetti e di ritrovare per ciascuno di essi i gli stessi comportamenti. Applichiamo questo concetto in pratica.
Aggiungiamo alla nostra soluzione un nuovo UserControl
, che chiameremo Square.xaml
. La definizione del nostro oggetto non è molto diversa da quella vista per lo stage.
<UserControl x:Class="InerziaOggetti.square"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="50" Height="50"
Loaded="user_Loader">
<Canvas x:Name="LayoutRoot" Background="Chocolate"></Canvas>
</UserControl>
Il codice
A questo punto abbiamo sia lo stage di lavoro che il nostro oggetto. Non ci resta che definire il loro comportamento attraverso il Code Behind. Iniziamo a definire il comportamento dello stage al momento della sua esecuzione.
metodo "user_Loader" dello stage
public void user_Loader(object sender, RoutedEventArgs e) { // definiamo una nuova istanza del nostro oggetto Square Square dragSquare = new Square(); // lo aggiugiamo all'interno dell'array degli oggetti del LayoutRoot dragSquare.MouseLeftButtonDown += new MouseButtonEventHandler(s_MouseLeftButtonDown); dragSquare.MouseLeftButtonUp += new MouseButtonEventHandler(s_MouseLeftButtonUp); dragSquare.X = 300; dragSquare.Y = 300; LayoutRoot.Children.Add(dragSquare); }
Anzitutto istanziamo nello stage un oggetto di tipo Square
. Questo è l'oggetto al quale vogliamo assegnare il comportamento di drag'n'drop inerziale, per questo motivo dobbiamo conoscere la sua relazione con i comportamenti del mouse.
Assegnamo due delegati che prendono in gestione gli eventi MouseLeftButtonDown
e MouseLeftButtonUp
. In questo modo possiamo intervenire quando viene premuto e rilasciato il pulsante sinistro del mouse.
Definiamo una posizione iniziale dello square all'interno dello stage attraverso l'utilizzo di due proprietà X
e Y
che abbiamo definito all'interno della classe Square
.
Proprietà X di Square
public double X { get { return (double) this.GetValue(Canvas.LeftProperty); } set { this.SetValue(Canvas.LeftProperty, value); } }
Non ci resta che aggiungere l'oggetto dragSquare
allo stage (LayoutRoot.Children.Add
).
Passiamo al codice della classe Square
, oltre alle coordinate, abbiamo inserito nell'oggetto anche uno storyboard (sb
), le coordinate obiettivo (targetX
, targetY
) un "vettore velocità" (vx
,vy
), il flag isDrag
e un offset. Tutte cose che esamineremo utilizzandole.
Una volta aggiunto l'oggetto nello stage definiamo il suo comportamento iniziale:
metodo "user_Loader" di Square
public void user_Loader(object sender, RoutedEventArgs e)
{
// aggiunge lo storyboard vuoto (simula onEnterFrame)
sb = new Storyboard();
this.Resources.Add("sb", sb);
sb.Begin();
}
Il codice iniziale di Square
genera un nuovo StoryBoard
, un'animazione in Silverlight, e lo aggiunge alle sue risorse. Lo StoryBoard
creato è privo di ogni parametro iniziale, sia di durata che di animazione.
È una animazione vuota e ci serve per simulare un ciclo continuo. Grazie a questo escamotage, ogni frame dell'animazione (che di default è di 60 fps) fa sollevare l'evento storyBoard Completed
che ci consente di gestire ad ogni istante lo spostamento del nostro oggetto.
Nota: Questo comportamento simula efficacemente quello dell'evento onEnterFrame
di Flash.
Gestore dell'evento Completed
public void sb_Completed(object sender, EventArgs e) { if(this.isDrag) // Il tasto è premuto { // calcola la velocità eseguendo la differenza // tra vecchia e nuova posizione vx = ((this.targetX + this.offset.X) - this.X) * 0.2; vy = ((this.targetY + this.offset.Y) - this.Y) * 0.2; this.X += vx; this.Y += vy; } else // Il tasto è rilasciato { vx *= 0.96; // riduce proressivamente la velocità vy *= 0.96; this.X += vx; this.Y += vy; } // verifica delle collisioni if(this.X>(400-this.Width)) { this.X = 400 - this.Width; this.vx *=-1; } if(this.X<0) { this.X = 0; this.vx *=-1; } if(this.Y>(300-this.Height)) { this.Y = (300 - this.Height); this.vy *=-1; } if(this.Y<0) { this.Y = 0; this.vy *=-1; } // rilancia l'animazione sb.Begin(); }
Questa funzione ci permette di gestire il comportamento del nostro oggetto Square
sia in fase di trascinamento, sia in fase di rilascio, fasi che monitoriamo col flag isDrag
.
Se isDrag
è verificato, calcoliamo la nuova posizione: prendiamo il vettore spostamento (vx,vy
) come la differenza tra la posizione attuale (X,Y
) e la nuova (target
) con un fattore di riduzione del 20% (* 0.2
), poi sommiamo la posizione attuale allo spostamento istantaneo che abbiamo calcolato. L'offset tiene conto della posizione del mouse al momento del click sull'oggetto, dandoci la sensazione di aver agganciato un punto preciso della figura e di trascinarla sempre da lì.
In fase di rilascio (isDrag = false
) utilizziamo lo stesso criterio ma smorzando la velocità, Il che dà la sensazione dell'inerzia. In questo caso si è scelto un fattore moltiplicativo di 0.96
.
A questo punto non ci resta che eseguire alcuni controlli in cui che ci permettono di gestire il caso in cui l'oggetto venga a trovarsi nei bordi del nostro stage, quando arriviamo sul bordo dello stage invertiamo la direzione del moto, moltiplicando per -1 la componente orizzontale o verticale che genera la collisione.
Infine rilanciamo lo storyboard (sb.Begin()
) alimentando l'effetto di loop.
Il mouse
Non dobbiamo fare altro che gestire i due eventi che ci permettono di trascinare il nostro oggetto nello stage. Iniziamo con la definizione della pressione del mouse sull'oggetto:
Gestore della pressione del pulsante sinistro del mouse
private void s_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { _square = (Square) sender; _square.isDrag = true; _square.CaptureMouse(); _square.targetX = e.GetPosition(this).X; _square.targetY = e.GetPosition(this).Y; double offsetx = this._square.X - e.GetPosition(this).X; double offsety = this._square.Y - e.GetPosition(this).Y; _square.offset = new Point(offsetx, offsety); }
Quando rileviamo la pressione del mouse dobbiamo definire tutti i parametri utili allo spostamento. Anzitutto accendiamo il flag isDrag
, poi facciamo in modo che il nostro oggetto Square
riceva informazioni sul mouse anche quando il puntatore non è al suo interno (captureMouse()
), questo ci serve per poter gestire il rilascio del bottone quando rilasciamo il bottone col puntatore fuori dall'area dell'oggetto.
Non ci resta che registrare il vettore destinazione (target) e il vettore offeset
grazie a GetPosition
.
È importante sottolineare che teniamo traccia del nostro oggetto nella variabile _square
dello stage. Ci è utile saperlo, perché è il meccanismo che ci consente di riassegnare il vettore target
durante lo spostamento del mouse ed ottenere così l'effetto del trascinamento.
Gestore dello spostamento del mouse
private void Page_MouseMove(object sender, MouseEventArgs e) { if(_square != null) { _square.targetX = e.GetPosition(this).X; _square.targetY = e.GetPosition(this).Y; } }
Il rilascio del pulsante viene gestito in modo duale alla pressione: impostiamo isDrag
a false
e scolleghiamo il nostro oggetto dagli eventi del mouse, infine annulliamo il riferimento all'oggetto _square
della pagina per essere pronti ad un nuovo eventuale trascinamento.
Gestore del rilascio del pulsante
private void s_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { _square = (Square) sender; _square.ReleaseMouseCapture(); _square.isDrag = false; _square = null; }
Il codice completo del tutorial è disponibile in allegato, in versione c# e VB.NET.