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.
