Windows Phone 7 – Tutorial XXIII–Touch Input III

En el anterior articulo vimos los dos primeros gestos Tap y Double-Tap, en este vamos a profundizar en los gestos  Pan y Flick.

Pan

clip_image004

 

Este gesto es totalmente diferente a los vistos anteriores el usuario mueve el dedo a través de la pantalla, es como cuando hemos realizado Drag & Drop en aplicaciones de PC, como si el dedo fuese el ratón y controlásemos el evento  MouseMove. Siguiendo la idea que hemos realizado con los dos anteriores gestos vamos a realizar un behaivor al que denominaremos PanAction que nos permita obtener este gesto del usuario y sea redirigido a un evento, la única diferencia con los anteriores es que lo delimitaremos al objeto Canvas  y moveremos todos los hijos del canvas en la dirección que mueva el dedo el usuario, en definitiva implementaremos un Drag&Drop donde el usuario podrá simular los movimientos que vemos en los siguientes dibujos

image

El código correspondiente a este behaivor sería el siguiente

 

1 public class PanAction : Behavior<Canvas> 2 { 3 protected Point? MouseDown; 4 protected UIElement SelectedItem { get; set; } 5 6 protected override void OnAttached() 7 { 8 base.OnAttached(); 9 10 this.AssociatedObject.MouseLeftButtonDown += AO_MouseLeftButtonDown; 11 this.AssociatedObject.MouseLeftButtonUp += AO_MouseLeftButtonUp; 12 this.AssociatedObject.MouseMove += AO_MouseMove; 13 } 14 15 protected override void OnDetaching() 16 { 17 this.AssociatedObject.MouseLeftButtonDown -= AO_MouseLeftButtonDown; 18 this.AssociatedObject.MouseLeftButtonUp -= AO_MouseLeftButtonUp; 19 this.AssociatedObject.MouseMove -= AO_MouseMove; 20 21 base.OnDetaching(); 22 } 23 24 private void AO_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 25 { 26 this.MouseDown = e.GetPosition(null); 27 this.SelectedItem = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this.AssociatedObject).FirstOrDefault(); 28 if (this.SelectedItem == this.AssociatedObject) 29 { 30 this.SelectedItem = null; 31 } 32 } 33 34 protected virtual void AO_MouseMove(object sender, MouseEventArgs e) 35 { 36 var pos = e.GetPosition(null); 37 var xdiff = pos.X - MouseDown.Value.X; 38 var ydiff = pos.Y - MouseDown.Value.Y; 39 if (MouseDown.HasValue) 40 { 41 MoveSelectedItems(xdiff, ydiff); 42 } 43 MouseDown = pos; 44 } 45 46 private void AO_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 47 { 48 this.SelectedItem = null; 49 this.MouseDown = null; 50 } 51 52 protected void MoveSelectedItems(double xdiff, double ydiff) 53 { 54 if (this.SelectedItem != null) 55 { 56 MoveItem(this.SelectedItem, xdiff, ydiff); 57 } 58 else 59 { 60 foreach (var child in this.AssociatedObject.Children) 61 { 62 MoveItem(child, xdiff, ydiff); 63 } 64 } 65 } 66 67 private void MoveItem(UIElement item, double xdiff, double ydiff) 68 { 69 var left = Canvas.GetLeft(item) + xdiff; 70 left = Math.Min(Math.Max(0, left), this.AssociatedObject.ActualWidth); 71 var top = Canvas.GetTop(item) + ydiff; 72 top = Math.Min(Math.Max(0, top), this.AssociatedObject.ActualHeight); 73 Canvas.SetLeft(item, left); 74 Canvas.SetTop(item, top); 75 } 76 }

Como podeis observar en el código hemos asociado el behaivor a los objetos Canvas y a la hora de”Atacharlo” nos subscribimos a los eventos MouseLeftButtonDown, MouseLeftButtonUp, MouseMove con estos tres eventos tenemos todo lo necesario para simular el Drag&Drop, lo importante esta en el evento MouseLeftButtonDown donde obtenemos los objetos hijos del Canvas a través del método

1 this.SelectedItem = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this.AssociatedObject).FirstOrDefault();


Una vez recogidos los objetos que el usuario ha “tocado” en el evento MouseMove solo deberemos moverlos. Para probar este behaivor vamos a extender el proyecto anterior para tener un Canvas con puntos verdes que se podrán mover con este behaivor.

 

1 <Border HorizontalAlignment="Left" Height="368" Margin="19,231,0,0" VerticalAlignment="Top" Width="441" BorderBrush="White" BorderThickness="2" Background="#FF313131"> 2 <Canvas Background="Transparent"> 3 <Canvas.Resources> 4 <Style x:Key="RoundedBorder" TargetType="Border"> 5 <Setter Property="BorderThickness" Value="3"/> 6 <Setter Property="CornerRadius" Value="20"/> 7 <Setter Property="Background" Value="#FF287E3D"/> 8 <Setter Property="BorderBrush" Value="#FF0F451C"/> 9 <Setter Property="Width" Value="25"/> 10 <Setter Property="Height" Value="25"/> 11 </Style> 12 </Canvas.Resources> 13 <i:Interaction.Behaviors> 14 <local:PanAction /> 15 16 </i:Interaction.Behaviors> 17 <Border Canvas.Left="43" Canvas.Top="137" Style="{StaticResource RoundedBorder}" > 18 </Border> 19 <Border Canvas.Left="122" Canvas.Top="83" Style="{StaticResource RoundedBorder}" > 20 </Border> 21 <Border Canvas.Left="294" Canvas.Top="301" Style="{StaticResource RoundedBorder}" > 22 </Border> 23 <Border Canvas.Left="273" Canvas.Top="51" Style="{StaticResource RoundedBorder}" > 24 </Border> 25 <Border Canvas.Left="74" Canvas.Top="242" Style="{StaticResource RoundedBorder}" > 26 </Border> 27 <Border Canvas.Left="236" Canvas.Top="167" Style="{StaticResource RoundedBorder}" > 28 </Border> 29 </Canvas> 30 </Border>

 

Obtendríamos la siguiente pantalla

 

image

 

Flick

clip_image005

 

Conceptualmente hay dos tipos de Flick uno empieza cuando el usuario toca la pantalla y el otro ocurre al final de un Pan, sin embargo si trsladamos esto a código podemos pensar que ambos ocurren al final de un pan, para el primero es un pan con distancia cero, por esta razón voy a construir el behaivor para el gesto Flick a partir del behaivor Pan.

Le vamos a denominar FlickAction y para detectar esta acción deberemos saber la velocidad con la que el dedo del usuario estaba moviéndose en el momento que soltó el dedo de la pantalla.  Los eventos de Mouse que hemos ido utilizando no nos sirven ya que no nos ofrecen este detalle. Windows Phone expone otros eventos diferentes a los que tiene Silverlight 3, estos eventos nos proporciona información asociada a los gestos del usuario ManipulationStarted , ManipulationDelta , y ManipulationCompleted . ManipulationStarted es lanzado cuando comienza un evento de “manipulación”, es decir, el usuario toca la pantalla en uno o mas puntos, cuando el usuario cambia de posición el dedo se lanza el evento ManipulationDelta, por último cuando se suelta uno o mas dedos de la pantalla se lanza el evento  ManipulationCompleted 

Para nuestro behaivor deberemos de implementar el evento ManipulationCompleted porque expone la propiedad FinalVelocities que nos indica la velocidad (en unidades de pantalla por segundo) que el usuario soltó el dedo de la pantalla. Para este behaivor vamos a simular un drift effect muy sencillo sin implementar fisica.

Este behaivor expondrá la propiedad FlickDurationInMilliseconds que sera la cantidad de tiempo que el control continuara moviéndose despues de que el usuario suelte el dedo.

 

1 public class FlickAction : PanAction 2 { 3 private const int Increments = 10; 4 private const double Deceleration = 0.4; 5 6 DispatcherTimer timer = new DispatcherTimer(); 7 8 public int Counter { get; set; } 9 10 private Point ReleaseVelocity { get; set; } 11 12 public int FlickDurationInMilliseconds 13 { 14 get { return (int)GetValue(FlickDurationInMillisecondsProperty); } 15 set { SetValue(FlickDurationInMillisecondsProperty, value); } 16 } 17 18 public static readonly DependencyProperty FlickDurationInMillisecondsProperty = 19 DependencyProperty.Register("FlickDurationInMilliseconds", 20 typeof(int), typeof(FlickAction), 21 new PropertyMetadata(500)); 22 23 public FlickAction() 24 { 25 timer.Tick += new EventHandler(timer_Tick); 26 } 27 28 protected override void OnAttached() 29 { 30 base.OnAttached(); 31 this.AssociatedObject.ManipulationStarted += AO_ManipulationStarted; 32 this.AssociatedObject.ManipulationCompleted += AO_ManipulationCompleted; 33 } 34 35 protected override void OnDetaching() 36 { 37 this.AssociatedObject.ManipulationStarted -= AO_ManipulationStarted; 38 this.AssociatedObject.ManipulationCompleted -= AO_ManipulationCompleted; 39 base.OnDetaching(); 40 } 41 42 void timer_Tick(object sender, EventArgs e) 43 { 44 Counter--; 45 if (Counter == 0) 46 { 47 timer.Stop(); 48 } 49 50 MoveSelectedItems(ReleaseVelocity.X / 100, ReleaseVelocity.Y / 100); 51 52 ReleaseVelocity = new Point(ReleaseVelocity.X * Deceleration, 53 ReleaseVelocity.Y * Deceleration); 54 } 55 56 void AO_ManipulationStarted(object sender, ManipulationStartedEventArgs e) 57 { 58 timer.Stop(); 59 } 60 61 void AO_ManipulationCompleted(object sender, 62 ManipulationCompletedEventArgs e) 63 { 64 ReleaseVelocity = e.FinalVelocities.LinearVelocity; 65 timer.Interval = new TimeSpan(0, 0, 0, 0, 66 FlickDurationInMilliseconds / Increments); 67 Counter = Increments; 68 timer.Start(); 69 } 70 }

El XAML en este caso es el mismo que el anterior solo que cambiamos el behaivor PanAction por FlickAction

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *