Implementing a Shake event on Windows Phone 8.1

I was working in our MSDN Forums and found a post from a user trying to detect shake events for Windows Phone 8.1.  I quickly realized this event is not built into the SDK, so I set out to create a helper class to raise a Shake event for Windows Phone 8.1.

The Accelerometer class in Windows Phone 8.1 has an event you can subscribe to that will be raised each time new data is available.  This event is raised quite  frequently to provide near real time data from the Accelerometer.  I chose to poll for Accelerometer data because reading data in real time is not needed to detect a Shake event.  The interval can be changed, but after trial and error I settled on  200ms.  The class is written so you can configure the polling Interval, however  I would not go greater than 200ms for the interval.  The class also exposes a Threshold property which can be modified to configure the type of Shake motion needed to raise the event.

The below code is the class I implemented to detect and Raise Shake events.

Copy Code:

 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Devices.Sensors;

namespace PhoneShake
{
    public class ShakeDetector
    {
        public event EventHandler Shaken;
        Timer _timer;
        protected static ShakeDetector _instance = new ShakeDetector();
        double _lastX;
        double _lastY;
        double _lastZ;
        double _currentX;
        double _currentY;
        double _currentZ;
        
        bool _bFirstUpdate;
        bool _bShake;

        protected void OnShaken(EventArgs e)
        {

            if (Shaken != null)
            {
                Shaken(this, e);
            }
        }

        protected ShakeDetector()
        {
            Interval = 200;
            Threshold = 3.00f;
            _bShake = false;
            _bFirstUpdate = true;
        }

        public static ShakeDetector Instance
        {
            get { return _instance; }
        }

        /// <summary>
        /// Interval that we will use for shake.  Should probably not be > 200ms
        /// </summary>
        public int Interval { get; set; }
        /// <summary>
        /// Force threshold to trigger Shaken event off of 
        /// </summary>
        public double Threshold { get; set; }

        /// <summary>
        /// Called to start monitoring for Shake Events.  Attach an event handler to shaken event before calling
        /// </summary>
        public void Start()
        {
            //  Set up timer for interval
            _timer = new Timer(IntervalTimer_Task, null, 0, Interval);
   
            
        }

        /// <summary>
        /// Called to stop monitoring and Firing Shake Events.
        /// </summary>
        public void Stop()
        {
            //  Stop the timer
            _timer.Dispose();
            _timer = null;

            _bFirstUpdate = true;
        }

        private void UpdateAccelerometerValues(double x, double y, double z)
        {
            if(_bFirstUpdate)
            {
                _lastX = x;
                _lastY = y;
                _lastZ = z;
                _bFirstUpdate = false;
            }
            else
            {
                _lastX = _currentX;
                _lastY = _currentY;
                _lastZ = _currentZ;
            }

            _currentX = x;
            _currentY = y;
            _currentZ = z;

        }

        private bool CheckAccelerationChanged()
        {
            double xChange = Math.Abs(_lastX - _currentX);
            double yChange = Math.Abs(_lastY - _currentY);
            double zChange = Math.Abs(_lastZ - _currentZ);

            // Debug.WriteLine("Accel: {0}", xChange+yChange+zChange);

            if (xChange + yChange + zChange > Threshold)
                return true;
            
            return false;

        }

        private void IntervalTimer_Task(object state)
        {
            AccelerometerReading current = Accelerometer.GetDefault().GetCurrentReading();         
            DateTimeOffset now = current.Timestamp;

            UpdateAccelerometerValues(current.AccelerationX, current.AccelerationY, current.AccelerationZ);

            //   Check and raise Shake
            if ((!_bShake) && CheckAccelerationChanged())
            {                                     
                _bShake = true;
            }
            else if ((_bShake) && CheckAccelerationChanged())
            {
                _bFirstUpdate = true;  //  Reset Detection
                OnShaken(new EventArgs());
            }
            else if ((_bShake) && (!CheckAccelerationChanged()))
            {                           
                _bShake = false;
            }

        }

    }
}

The following code is a simple XAML Page that starts/stops the ShakeDetector, and logs the Date/Time of the event to the UI.

Copy Code:

 <Page
    x:Class="PhoneShake.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PhoneShake"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel>
        <Button x:Name="btnStart" Content="Start" Click="btnStart_Click"/>
        <Button x:Name="btnStop" Content="Stop" Click="btnStop_Click"/>
        <TextBlock x:Name="txtEvent" />
    </StackPanel>
</Page>

Copy Code:

 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=391641

namespace PhoneShake
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.NavigationCacheMode = NavigationCacheMode.Required;
            btnStop.IsEnabled = false;
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.
        /// This parameter is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // TODO: Prepare page for display here.

            // TODO: If your application contains multiple pages, ensure that you are
            // handling the hardware Back button by registering for the
            // Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
            // If you are using the NavigationHelper provided by some templates,
            // this event is handled for you.
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            ShakeDetector.Instance.Start();
            ShakeDetector.Instance.Shaken += Instance_Shaken;
            btnStop.IsEnabled = true;
            btnStart.IsEnabled = false;
        }

        void Instance_Shaken(object sender, EventArgs e)
        {
            Debug.WriteLine("Device Shook");
            Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                txtEvent.Text = string.Format("Shake Detected at {0}", DateTime.UtcNow);
            });
            
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            ShakeDetector.Instance.Stop();
            ShakeDetector.Instance.Shaken -= Instance_Shaken;
            btnStop.IsEnabled = false;
            btnStart.IsEnabled = true;
        }
    }
}

I hope this article helps those developers writing for Windows Phone 8.1 to use the Shake gesture for something fun.  Until next time have fun coding!

Don’t forget to follow the Windows Store Developer Solutions team on Twitter @wsdevsol. Comments are welcome, both below and on twitter.

- Bret Bentzinger(Microsoft) @awehellyeah