Windows Phone Applications - Gestures

Unlike desktop applications which use a keyboard and mouse, Windows Phone applications use touch screen gestures for input.  The most common gesture is the Tap, which is the same as a Click and uses the Click event handler.  Today, I'll be writing about four gestures: Pan, Flick, Pinch and Stretch, which are implemented in the ManipulationDelta and ManipulationCompleted event handlers.  To illustrate how they work, I'll use a modification of the previous slideshow sample and add the gestures handlers to change the Scale and Translate transform values of the image.

 

To briefly explain what these gestures are:

  • The Pan gesture is where the user touches the screen with a finger and moves the finger on the touch screen.  In today's sample, the Pan gesture is used to change the Translate transform value to move the image on the screen, and is implemented in image1_ManipulationDelta.
  • The Flick gesture is similar to the Pan gesture, but is inertial, where the user moves the finger quickly and lifts the finger.  The Flick gesture will be used to change the image source to the next or previous image, depending on the direction of the Flick.  This is implemented in image1_ManipulationCompleted.
  • The Pinch gesture uses two fingers to pinch the screen, this will be used to change the Scale transform value to resize the image smaller.  This is implemented in image1_ManipulationDelta.
  • The Stretch gesture, which is the opposite of the Pinch gesture, uses two fingers to stretch the screen - this will be used to resize the image larger.  Since Pinch is just the opposite of Stretch, they share the same code.

 

Since it is possible in this sample to move the image off the screen, or pinch it into a size that is too small to resize back, I've added a button to revert to default.  Below is what it looks like after a couple of Flicks to get to the Penguins, a Stretch to resize the image larger, and a Pan to move the image down and centered on the penguins:

 

 

Xaml:

 

    <Grid x:Name="LayoutRoot" Background="Transparent">

        <Grid.RowDefinitions>

            <RowDefinition Height="*"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

       

        <Canvas x:Name="ContentGrid">

            <Image x:Name="Image1" Source="Koala.jpg" ManipulationDelta="Image1_ManipulationDelta"  ManipulationCompleted="image1_ManipulationCompleted">

                <Image.RenderTransform>

                    <TransformGroup>

                        <ScaleTransform x:Name="scale" ScaleX="0.47" ScaleY="0.47" />

                        <TranslateTransform x:Name="translate" />

                    </TransformGroup>

                </Image.RenderTransform>

            </Image>

        </Canvas>

        <Button Content="Revert to Default" Grid.Row="1" Click="Button_Click" />

    </Grid>

 

Code:

 

    public partial class MainPage : PhoneApplicationPage

    {

        List<string> files = new List<string>() { "Koala.jpg", "Jellyfish.jpg", "Penguins.jpg" };

        List<BitmapImage> images = new List<BitmapImage>();

        int current = 0;

        // Constructor

        public MainPage()

        {

            InitializeComponent();

            foreach (string file in files)

            {

                BitmapImage image = new BitmapImage(new Uri(file, UriKind.Relative));

                images.Add(image);

            }

        }

        private void image1_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)

        {

            //if a pan or flick gesture, scale is 0

            if (e.TotalManipulation.Scale.X == 0 && e.TotalManipulation.Scale.Y == 0)

            {

                //if event occurs during inertia, it is a flick, so go to the previous or next image

                if (e.IsInertial)

                {

                    //use the X translation to determine whether to go to previous or next

                    double translation = e.TotalManipulation.Translation.X;

                    if (translation > 0)

               {

                        Previous();

                    }

                    else

                    {

                        Next();

                    }

                    //revert the translate from the ManipulationDelta event so that only the image source changes

                    translate.X -= e.TotalManipulation.Translation.X;

                    translate.Y -= e.TotalManipulation.Translation.Y;

                }

            }

        }

        private void Image1_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)

        {

            //handle stretch or pinch gestures by changing the scale

            if (e.DeltaManipulation.Scale.X != 0 && e.DeltaManipulation.Scale.Y != 0)

            {

                scale.ScaleX *= e.DeltaManipulation.Scale.X;

                scale.ScaleY *= e.DeltaManipulation.Scale.Y;

            }

            //handle pan gesture

            translate.X += e.DeltaManipulation.Translation.X;

            translate.Y += e.DeltaManipulation.Translation.Y;

        }

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            scale.ScaleX = 0.45;

            scale.ScaleY = 0.45;

            translate.X = 0;

            translate.Y = 0;

        }

        private void Previous()

        {

            current--;

            if (current < 0)

                current = files.Count - 1;

            Image1.Source = images[current];

        }

        private void Next()

        {

            current++;

            if (current >= files.Count)

                current = 0;

            Image1.Source = images[current];

        }

    }