Implementing Custom Gesture in Xamarin Forms

In Mobile Development by Christian HissibiniLeave a Comment

Xamarin Forms only comes with a limited set of Gesture Recognizers, most notably the TapGestureRecognizer. Many times you will want more than just that and hence here is how you implement a custom gesture recognizer. In this example I will add the Pressed and Released events to a Xamarin Forms project.

Create Xamarin Forms Gesture Recognizers

In our common project, we need to create our gesture recognizer for use in Xamarin Forms. I have done 2 examples of a PressedGestureRecognizer and ReleasedGestureRecognizer.

 public class PressedGestureRecognizer : Element, IGestureRecognizer
    {
        public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(PressedGestureRecognizer), (object)null, BindingMode.OneWay, (BindableProperty.ValidateValueDelegate)null, (BindableProperty.BindingPropertyChangedDelegate)null, (BindableProperty.BindingPropertyChangingDelegate)null, (BindableProperty.CoerceValueDelegate)null, (BindableProperty.CreateDefaultValueDelegate)null);

        public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(PressedGestureRecognizer), (object)null, BindingMode.TwoWay, (BindableProperty.ValidateValueDelegate)null, (BindableProperty.BindingPropertyChangedDelegate)null, (BindableProperty.BindingPropertyChangingDelegate)null, (BindableProperty.CoerceValueDelegate)null, (BindableProperty.CreateDefaultValueDelegate)null);

        public ICommand Command
        {
            get { return (ICommand)this.GetValue(CommandProperty); }
            set { this.SetValue(CommandProperty, (object)value); }
        }

        public object CommandParameter
        {
            get { return this.GetValue(CommandParameterProperty); }
            set { this.SetValue(CommandParameterProperty, value); }
        }

    }

And the ReleasedGestureRecognizer the same again.

Add Custom Renderer

Next we need to a custom renderer for the control we want to add these gestures to. In this example I am using a Label.

UWP

[assembly: ExportRenderer(typeof(Label), typeof(LabelRender))]
namespace Mobile.UWP.Renderer
{

    public class LabelRender : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement == null)
            {
                if (!e.NewElement.GestureRecognizers.Any())
                    return;

                if (e.NewElement.GestureRecognizers.Any(x => x.GetType() == typeof(PressedGestureRecognizer)))
                    Control.PointerPressed += Control_PointerPressed;

                if (e.NewElement.GestureRecognizers.Any(x => x.GetType() == typeof(ReleasedGestureRecognizer)))
                    Control.PointerReleased += Control_PointerReleased;
            }
        }
        
        private void Control_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(PressedGestureRecognizer)))
            {
                var gesture = recognizer as PressedGestureRecognizer;
                if (gesture != null)
                    if (gesture.Command != null)
                        gesture.Command.Execute(gesture.CommandParameter);
            }
        }

        private void Control_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(ReleasedGestureRecognizer)))
            {
                var gesture = recognizer as ReleasedGestureRecognizer;
                if (gesture != null)
                    if (gesture.Command != null)
                        gesture.Command.Execute(gesture.CommandParameter);
            }
        }
    }
}

Android

[assembly: ExportRenderer(typeof(Label), typeof(LabelRender))]
namespace Mobile.Droid.Renderer
{
    public class LabelRender: LabelRenderer
    {

        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement == null)
            {
                if (!e.NewElement.GestureRecognizers.Any())
                    return;

                if (e.NewElement.GestureRecognizers.Any(x => x.GetType() == typeof(PressedGestureRecognizer) 
                                                            || x.GetType() == typeof(ReleasedGestureRecognizer)))
                    Control.Touch += Control_Touch;

            }
        }

        private void Control_Touch(object sender, TouchEventArgs e)
        {
            switch (e.Event.Action)
            {
                case MotionEventActions.Down:
                    foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(PressedGestureRecognizer)))
                    {
                        var gesture = recognizer as PressedGestureRecognizer;
                        if (gesture != null)
                            if (gesture.Command != null)
                                gesture.Command.Execute(gesture.CommandParameter);
                    }
                    break;

                case MotionEventActions.Up:
                    foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(ReleasedGestureRecognizer)))
                    {
                        var gesture = recognizer as ReleasedGestureRecognizer;
                        if (gesture != null)
                            if (gesture.Command != null)
                                gesture.Command.Execute(gesture.CommandParameter);
                    }
                    break;

                default:
                    break;
            }
        }
    }
}

iOS

[assembly: ExportRenderer(typeof(Label), typeof(LabelRender))]
namespace Mobile.iOS.Renderer
{
    public class LabelRender : LabelRenderer
    {
        private class TouchLabel : UILabel
        {
            Label Element = null;
            public TouchLabel(Label element)
            {
                Element = element;
                this.Text = element.Text;
            }

            public override void TouchesBegan(NSSet touches, UIEvent evt)
            {
                base.TouchesBegan(touches, evt);
                foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(PressedGestureRecognizer)))
                {
                    var gesture = recognizer as PressedGestureRecognizer;
                    if (gesture != null)
                        if (gesture.Command != null)
                            gesture.Command.Execute(gesture.CommandParameter);
                }
            }
            public override void TouchesCancelled(NSSet touches, UIEvent evt)
            {
                base.TouchesCancelled(touches, evt);
                foreach (var recognizer in this.Element.GestureRecognizers.Where(x => x.GetType() == typeof(ReleasedGestureRecognizer)))
                {
                    var gesture = recognizer as ReleasedGestureRecognizer;
                    if (gesture != null)
                        if (gesture.Command != null)
                            gesture.Command.Execute(gesture.CommandParameter);
                }
            }
         
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            if (Control == null)
                SetNativeControl(new TouchLabel(Element) { });

            base.OnElementChanged(e);

            if (e.OldElement == null)
            {
                if (!e.NewElement.GestureRecognizers.Any())
                    return;

                Control.UserInteractionEnabled = true;

            }
        }

    }
}

These renderers attach the native events to call the Command of the Gesture Recognizer.

Using Gesture Recognizer

Now that we have setup everything, we can now easily add the gesture recognizer to any labels.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:gesture="clr-namespace:Mobile.Gestures;assembly=Mobile"
             x:Class="Mobile.View.MainPage" BackgroundColor="Black">
  
  <Label Text="Press Me" WidthRequest="250" HeightRequest="100" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="{Binding TextColor}" BackgroundColor="{Binding BackgroundColor}" VerticalOptions="Center" HorizontalOptions="Center">    
    <Label.GestureRecognizers>     
      <gesture:PressedGestureRecognizer Command="{Binding PressedGestureCommand}" />
      <gesture:ReleasedGestureRecognizer Command="{Binding ReleasedGestureCommand}" />
    </Label.GestureRecognizers>
  </Label>
</ContentPage>

Ref
https://docs.microsoft.com – https://xamarinhelp.com – https://blog.xamarin.com/

Leave a Comment