XAML Behaviors implementing Multi-Touch Manipulation (Gestures) and Inertia.
Xamarin Forms Behavior uses code from original Xamarin.Forms samples: https://github.com/xamarin/xamarin-forms-samples/tree/master/WorkingWithGestures
Archived projects targeting Silverlight, WPF and Windows Phone 7.x, 8.x available on CodePlex: http://multitouch.codeplex.com/.
Recently I've blogged about Xamarin.Forms and how to create a XAML Behavior for enabling Multi-Touch gestures to generic elements and implementing a scale / pinch functionality.
Fortunately the framework provides three types of recognizer that greatly simplify the implementation:
- PinchGestureRecognizer allows user interactions for zoom / scale functionalities;
- PanGestureRecognizer enables pan / translate transformations;
- TapGestureRecognizer detects tap events.
Yesterday I decided to try the PanGestureRecognizer for extending the capabilities of the Behavior described in the previous post.
First of all, I added two Bindable properties in order to permit activation / deactivation of individual gestures (Bindable properties are equivalent to Dependency ones in UWP XAML)
#region IsScaleEnabled property /// <summary> /// Identifies the <see cref="IsScaleEnabledProperty" /> property. /// </summary> public static readonly BindableProperty IsScaleEnabledProperty = BindableProperty.Create<MultiTouchBehavior, bool>(w => w.IsScaleEnabled, default(bool)); /// <summary> /// Identifies the <see cref="IsScaleEnabled" /> dependency / bindable property. /// </summary> public bool IsScaleEnabled { get { return (bool)GetValue(IsScaleEnabledProperty); } set { SetValue(IsScaleEnabledProperty, value); } } #endregion #region IsTranslateEnabled property /// <summary> /// Identifies the <see cref="IsTranslateEnabledProperty" /> property. /// </summary> public static readonly BindableProperty IsTranslateEnabledProperty = BindableProperty.Create<MultiTouchBehavior, bool>(w => w.IsTranslateEnabled, default(bool)); /// <summary> /// Identifies the <see cref="IsTranslateEnabled" /> dependency / bindable property. /// </summary> public bool IsTranslateEnabled { get { return (bool)GetValue(IsTranslateEnabledProperty); } set { SetValue(IsTranslateEnabledProperty, value); } } #endregion
In this way we can specify in our XAML what gestures are enabled:
... <Image.Behaviors> <behaviors:MultiTouchBehavior IsScaleEnabled="True" IsTranslateEnabled="True" /> </Image.Behaviors> ...
Then I initialised the GestureRecognizers adding a new PanGestureRecognizer to the recognizers list:
/// <summary> /// Initialise the Gesture Recognizers. /// </summary> private void InitialiseRecognizers() { _panGestureRecognizer = new PanGestureRecognizer(); } /// <summary> /// Occurs when BindingContext is changed: used to initialise the Gesture Recognizers. /// </summary> /// <param name="sender">The sender object.</param> /// <param name="e">The event parameters.</param> private void AssociatedObjectBindingContextChanged(object sender, EventArgs e) { _parent = _associatedObject.Parent as ContentView; _parent?.GestureRecognizers.Remove(_panGestureRecognizer); _parent?.GestureRecognizers.Add(_panGestureRecognizer); }
And subscribed to the PanUpdated event in order to apply the translate transform:
/// <summary> /// Initialise the events. /// </summary> private void InitializeEvents() { CleanupEvents(); _panGestureRecognizer.PanUpdated += OnPanUpdated; }
The implementation of this event handler permits to update the X and Y coordinates of the element when a Pan gesture is detected:
/// <summary> /// Implements Pan/Translate. /// </summary> /// <param name="sender">The sender object.</param> /// <param name="e">The event parameters.</param> private void OnPanUpdated(object sender, PanUpdatedEventArgs e) { if (_parent == null) { return; } if (!IsTranslateEnabled) { return; } switch (e.StatusType) { case GestureStatus.Running: _parent.Content.TranslationX = _xOffset + e.TotalX; _parent.Content.TranslationY = _yOffset + e.TotalY; break; case GestureStatus.Completed: _xOffset = _parent.Content.TranslationX; _yOffset = _parent.Content.TranslationY; break; } }
Here we go: the sample app can now be deployed to the emulators and iOS / Android / Windows devices.
Just a couple of notes:
- deployment to iOS required this workaround to work properly since the new sample app uses different assemblies;
- Project has been updated to Visual Studio 2019 and Xamarin.Forms 3.6.0.