Search code examples
xamarin.formslong-press

Xamarin Forms Custom GridView Tap and Long Tap Not Working Together


I want to use tap and long tap together with Custom Gridview.

Tap is working but long tap not working.

it also works when one tap turns off the long tap.

Please help me.

Thank you.

public class GridView : Grid
{
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GridView), default(IList), BindingMode.TwoWay);
    public static readonly BindableProperty ItemTappedCommandProperty = BindableProperty.Create(nameof(ItemTappedCommand), typeof(ICommand), typeof(GridView), null);

    public static readonly BindableProperty ItemLongTappedCommandProperty = BindableProperty.Create(nameof(ItemLongTappedCommand), typeof(ICommand), typeof(GridView), null);

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GridView), default(DataTemplate));
    public static readonly BindableProperty MaxColumnsProperty = BindableProperty.Create(nameof(MaxColumns), typeof(int), typeof(GridView), 2);
    public static readonly BindableProperty TileHeightProperty = BindableProperty.Create(nameof(TileHeight), typeof(float), typeof(GridView), 220f);//adjusted here reuired height

    public GridView()
    {
        PropertyChanged += GridView_PropertyChanged;
        PropertyChanging += GridView_PropertyChanging;
    }

    public IList ItemsSource
    {
        get { return (IList)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public ICommand ItemTappedCommand
    {
        get { return (ICommand)GetValue(ItemTappedCommandProperty); }
        set { SetValue(ItemTappedCommandProperty, value); }
    }

    public ICommand ItemLongTappedCommand
    {
        get { return (ICommand)GetValue(ItemLongTappedCommandProperty); }
        set { SetValue(ItemLongTappedCommandProperty, value); }
    }

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    public int MaxColumns
    {
        get { return (int)GetValue(MaxColumnsProperty); }
        set { SetValue(MaxColumnsProperty, value); }
    }

    public float TileHeight
    {
        get { return (float)GetValue(TileHeightProperty); }
        set { SetValue(TileHeightProperty, value); }
    }

    private void BuildColumns()
    {
        ColumnDefinitions.Clear();
        for (var i = 0; i < MaxColumns; i++)
        {
            ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        }
    }

    private View BuildTile(object item1)
    {
        var template = ItemTemplate.CreateContent() as View;
        template.BindingContext = item1;

        if (ItemTappedCommand != null)
        {
            var tapGestureRecognizer = new TapGestureRecognizer
            {
                Command = ItemTappedCommand,
                CommandParameter = item1,
            };

            template.GestureRecognizers.Add(tapGestureRecognizer);
        }

        // Tap komutu eziyor.
        if (ItemLongTappedCommand != null)
        {
            template.Effects.Add(new LongPressedEffect());
            LongPressedEffect.SetCommand(template, ItemLongTappedCommand);
            //LongPressedEffect.SetCommandParameter(template, item1);
        }

        return template;
    }

    private void BuildTiles()
    {
        // Wipe out the previous row & Column definitions if they're there.
        if (RowDefinitions.Any())
        {
            RowDefinitions.Clear();
        }

        BuildColumns();
        Children.Clear();
        var tiles = ItemsSource;
        if (tiles != null)
        {
            var numberOfRows = Math.Ceiling(tiles.Count / (float)MaxColumns);
            for (var i = 0; i < numberOfRows; i++)
            {
                RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Auto) });
            }

            for (var index = 0; index < tiles.Count; index++)
            {
                var column = index % MaxColumns;
                var row = (int)Math.Floor(index / (float)MaxColumns);
                var tile = BuildTile(tiles[index]);
                Children.Add(tile, column, row);
            }
        }
    }

    private void GridView_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == ItemsSourceProperty.PropertyName)
        {
            var items = ItemsSource as INotifyCollectionChanged;
            if (items != null)
                items.CollectionChanged += ItemsCollectionChanged;
            BuildTiles();
        }

        if (e.PropertyName == MaxColumnsProperty.PropertyName || e.PropertyName == TileHeightProperty.PropertyName)
        {
            BuildTiles();
        }
    }

    private void GridView_PropertyChanging(object sender, Xamarin.Forms.PropertyChangingEventArgs e)
    {
        if (e.PropertyName == ItemsSourceProperty.PropertyName)
        {
            var items = ItemsSource as INotifyCollectionChanged;
            if (items != null)
                items.CollectionChanged -= ItemsCollectionChanged;
        }
    }

    private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        BuildTiles();
    }
}


public class LongPressedEffect : RoutingEffect
{
    public LongPressedEffect() : base("MyApp.LongPressedEffect")
    { }

    public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
    public static ICommand GetCommand(BindableObject view)
    {
        //do something you want 
        Console.WriteLine("long press Gesture recognizer has been striked");

        return (ICommand)view.GetValue(CommandProperty);
    }

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


    public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
    public static object GetCommandParameter(BindableObject view)
    {
        return view.GetValue(CommandParameterProperty);
    }

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

Solution

  • I noticed you used Effect to create your own long pressed command. But if you consumed a TapGestureRecognizer at the same time, it will intercept the effect. Then your long pressed command won't be triggered.

    You can either define the single tap click event in the effect to achieve them both. Here is my effect:

    public class PressedEffect : RoutingEffect
    {
        public PressedEffect() : base("MyApp.PressedEffect")
        {
        }
    
        public static readonly BindableProperty LongTapCommandProperty = BindableProperty.CreateAttached("LongTapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
        public static ICommand GetLongTapCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(LongTapCommandProperty);
        }
    
        public static void SetLongTapCommand(BindableObject view, ICommand value)
        {
            view.SetValue(LongTapCommandProperty, value);
        }
    
    
        public static readonly BindableProperty LongParameterProperty = BindableProperty.CreateAttached("LongParameter", typeof(object), typeof(PressedEffect), (object)null);
        public static object GetLongParameter(BindableObject view)
        {
            return view.GetValue(LongParameterProperty);
        }
    
        public static void SetLongParameter(BindableObject view, object value)
        {
            view.SetValue(LongParameterProperty, value);
        }
    
        public static readonly BindableProperty TapCommandProperty = BindableProperty.CreateAttached("TapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
        public static ICommand GetTapCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(TapCommandProperty);
        }
    
        public static void SetTapCommand(BindableObject view, ICommand value)
        {
            view.SetValue(TapCommandProperty, value);
        }
    
        public static readonly BindableProperty TapParameterProperty = BindableProperty.CreateAttached("TapParameter", typeof(object), typeof(PressedEffect), (object)null);
        public static object GetTapParameter(BindableObject view)
        {
            return view.GetValue(TapParameterProperty);
        }
    
        public static void SetTapParameter(BindableObject view, object value)
        {
            view.SetValue(TapParameterProperty, value);
        }
    }
    

    Android implementation:

    [assembly: ResolutionGroupName("MyApp")]
    [assembly: ExportEffect(typeof(AndroidPressedEffect), "PressedEffect")]
    namespace PressedEffectDemo.Droid
    {
        public class AndroidPressedEffect : PlatformEffect
        {
            private bool _attached;
    
            public static void Initialize() { }
    
            public AndroidPressedEffect()
            {
            }
    
            protected override void OnAttached()
            {
                if (!_attached)
                {
                    if (Control != null)
                    {
                        Control.LongClickable = true;
                        Control.LongClick += Control_LongClick;
                        Control.Click += Control_Click;
                    }
                    else
                    {
                        Container.LongClickable = true;
                        Container.LongClick += Control_LongClick;
                        Container.Click += Control_Click;
                    }
                    _attached = true;
                }
            }
    
            private void Control_Click(object sender, EventArgs e)
            {
                var command = PressedEffect.GetTapCommand(Element);
                command?.Execute(PressedEffect.GetTapParameter(Element));
            }
    
            private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
            {
                var command = PressedEffect.GetLongTapCommand(Element);
                command?.Execute(PressedEffect.GetLongParameter(Element));
            }
    
            protected override void OnDetached()
            {
                if (_attached)
                {
                    if (Control != null)
                    {
                        Control.LongClickable = true;
                        Control.LongClick -= Control_LongClick;
                        Control.Click -= Control_Click;
                    }
                    else
                    {
                        Container.LongClickable = true;
                        Container.LongClick -= Control_LongClick;
                        Control.Click -= Control_Click;
                    }
                    _attached = false;
                }
            }
        }
    }
    

    iOS implementation:

    [assembly: ResolutionGroupName("MyApp")]
    [assembly: ExportEffect(typeof(iOSPressedEffect), "PressedEffect")]
    namespace PressedEffectDemo.iOS
    {
        public class iOSPressedEffect : PlatformEffect
        {
            private bool _attached;
            private readonly UILongPressGestureRecognizer _longPressRecognizer;
            private readonly UITapGestureRecognizer _tapRecognizer;
            public iOSPressedEffect()
            {
                _longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
                _tapRecognizer = new UITapGestureRecognizer(HandleClick);
            }
    
            protected override void OnAttached()
            {
                if (!_attached)
                {
                    Container.AddGestureRecognizer(_longPressRecognizer);
                    Container.AddGestureRecognizer(_tapRecognizer);
                    _attached = true;
                }
            }
    
            private void HandleClick()
            {
                var command = PressedEffect.GetTapCommand(Element);
                command?.Execute(PressedEffect.GetTapParameter(Element));
            }
            private void HandleLongClick(UILongPressGestureRecognizer recognizer)
            {
                if (recognizer.State == UIGestureRecognizerState.Ended)
                {
                    var command = PressedEffect.GetLongTapCommand(Element);
                    command?.Execute(PressedEffect.GetLongParameter(Element));
                }           
            }
    
            protected override void OnDetached()
            {
                if (_attached)
                {
                    Container.RemoveGestureRecognizer(_longPressRecognizer);
                    Container.RemoveGestureRecognizer(_tapRecognizer);
                    _attached = false;
                }
            }
        }
    }
    

    At last, you can consume them on XAML like:

    <StackLayout>        
        <Grid HeightRequest="200" 
                BackgroundColor="Green" 
                local:PressedEffect.TapCommand="{Binding TapCommand}" 
                local:PressedEffect.LongTapCommand="{Binding LongTapCommand}">
            <Grid.Effects>
                <local:PressedEffect />
            </Grid.Effects>
        </Grid>
    </StackLayout>
    

    You could refer to my sample here.