Kikanye
Kikanye

Reputation: 1358

Xamarin.Android Tap Gesture and Long Press Gesture not working together

I have created a custom effect by subclassing RoutingEffect in order to allow LongPressGesture for both iOS and Android in my Xamarin project.

I am using this effect on an Image in in the XAML of my shared project, and this same Image is also using a TapGesture, see code below:

                <Image x:Name="TapRight" Grid.Row="4" Grid.Column="2"  Source="right64" 
                   VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
                   IsEnabled="{Binding RightEnabled}"
                   Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"
                   effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
                   effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}">
                <Image.GestureRecognizers>
                    <TapGestureRecognizer 
                        Command="{Binding TapGestureNavCommand}" 
                        NumberOfTapsRequired="1"
                        CommandParameter="{x:Static common:NavType.Right}"/>
                </Image.GestureRecognizers>
                <Image.Effects>
                        <effects:LongPressEffect></effects:LongPressEffect>
                </Image.Effects>
            </Image>

This works fine for iOS (I get separate functionality when I tap vs when I long press the image), however for Android, it only allows me to do Long Press, and does not execute the command for the TapGesture, any ideas on how to fix this?

NOTE: If I use a Button instead of an Image it works fine. However, I would really like to use an Image.

I have added more code below for reference:

Code for the effect in shared project:

using System.Windows.Input;
using Xamarin.Forms;


namespace MyApp.Effects
{

    public class LongPressEffect : RoutingEffect
    {
        public LongPressEffect() : base("Xamarin.LongPressEffect")
        {

        }

        public static readonly BindableProperty CommandProperty =
         BindableProperty.CreateAttached("Command",
             typeof(ICommand),
             typeof(LongPressEffect),
             (object)null,
             propertyChanged: OnCommandChanged);

        public static ICommand GetCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(CommandProperty);
        }

        public static void SetCommand(BindableObject view, ICommand value)
        {
            view.SetValue(CommandProperty, value);
        }

        static void OnCommandChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var view = bindable as View;
            if (view == null)
            {
                return;
            }
            ICommand command = (ICommand)newValue;
            if (command != null)
            {
                view.SetValue(CommandProperty, command);
            }

        }

        public static readonly BindableProperty CommandParameterProperty =
            BindableProperty.CreateAttached("CommandParameter",
                typeof(object),
                typeof(LongPressEffect),
                (object)null,
                propertyChanged: OnCommandParameterChanged);

        public static object GetCommandParameter(BindableObject view)
        {
            return (object)view.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(BindableObject view, object value)
        {
            view.SetValue(CommandParameterProperty, value);
        }

        static void OnCommandParameterChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var view = bindable as View;
            if (view == null)
            {
                return;
            }
            object commandParameter = (object)newValue;
            if (commandParameter != null)
            {
                view.SetValue(CommandParameterProperty, commandParameter);
            }
        }
    }
}

Code for effect in iOS:

using System;
using System.ComponentModel;
using MyApp.Effects;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ResolutionGroupName("Xamarin")]
[assembly:ExportEffect (typeof(MyApp.iOS.Effects.LongPressEffect), "LongPressEffect")]
namespace MyApp.iOS.Effects
{
    public class LongPressEffect : PlatformEffect
    {
        private readonly UILongPressGestureRecognizer _longPressGestureRecognizer;
        private bool _attached;
        public LongPressEffect()
        {
            _longPressGestureRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
            _attached = false;

        }

        protected override void OnAttached()
        {
            if (!_attached)
            {
                Container.AddGestureRecognizer(_longPressGestureRecognizer);
                _attached = true;
            }
        }

        private void HandleLongClick()
        {
            if (_longPressGestureRecognizer.State == UIGestureRecognizerState.Ended)
                // Only execute when the press is ended.
            {
                var command = MyApp.Effects.LongPressEffect.GetCommand(Element);
                command?.Execute(MyApp.Effects.LongPressEffect.GetCommandParameter(Element));
            }
        }

        protected override void OnDetached()
        {
            if (_attached)
            {
                Container.RemoveGestureRecognizer(_longPressGestureRecognizer);
                _attached = false;
            }
        }

        protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(args);
        }
    }
}

Code for effect in Android:

using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(MyApp.Droid.Effects.LongPressEffect), "LongPressEffect")]
namespace MyApp.Droid.Effects
{
    public class LongPressEffect: PlatformEffect
    {
        private bool _attached;
        public static void Initialize() { }
        public LongPressEffect()
        {
            _attached = false;

        }

        protected override void OnAttached()
        {
            Console.WriteLine("Invoking long click command...");
            //throw new NotImplementedException();
            if (!_attached) {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick += HandleLongClick;
                }
                _attached = true;
            }
        }

        private void HandleLongClick(object sender, Android.Views.View.LongClickEventArgs e) {
            Console.WriteLine("Invoking long click command...");
            var command = MyApp.Effects.LongPressEffect.GetCommand(Element);
            command?.Execute(MyApp.Effects.LongPressEffect.GetCommandParameter(Element));

        }

        protected override void OnDetached()
        {
            //throw new NotImplementedException();
            if (_attached) {
                if (Control != null) {
                    Control.LongClickable = true;
                    Control.LongClick -= HandleLongClick;
                }
                _attached = false;
            }
        }

        protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(args);
        }
    }
}

Upvotes: 5

Views: 3338

Answers (1)

Kikanye
Kikanye

Reputation: 1358

This is a bug in Xamarin, more details can be found here

As a workaround I have used an ImageButton or Android and Image for IOS and made the visibility platform dependent. my XAML now looks like this:

<ImageButton x:Name="Tap" Grid.Row="4" Grid.Column="2" Source="right64" 
       IsEnabled="{Binding RightEnabled}"
       Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"     
       effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
       effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}"
       Command="{Binding TapGestureNavCommand}"
       CommandParameter="{x:Static common:NavType.Right}">
       <ImageButton.IsVisible>
            <OnPlatform x:TypeArguments="x:Boolean"
                        iOS="False"
                        Android="True"/>
       </ImageButton.IsVisible>
       <ImageButton.Effects>
           <effects:LongPressEffect></effects:LongPressEffect>
       </ImageButton.Effects>
</ImageButton>

<!--Due to different behaviour on platform(s) different views were needed for different platforms.-->

<Image x:Name="TapIOS" Grid.Row="4" Grid.Column="2"  Source="right64" 
        IsEnabled="{Binding RightEnabled}"
        Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"    
        effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
        effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}">
    <Image.IsVisible>
        <OnPlatform x:TypeArguments="x:Boolean"
                    iOS="True"
                    Android="False"/>
    </Image.IsVisible>
    <Image.GestureRecognizers>
         <TapGestureRecognizer
             NumberOfTapsRequired="1"
             Command="{Binding TapGestureNavCommand}"
             CommandParameter="{x:Static common:NavType.Right}"/>
    </Image.GestureRecognizers>
    <Image.Effects>
          <effects:LongPressEffect></effects:LongPressEffect>
    </Image.Effects>
</Image>

Upvotes: 3

Related Questions